Conversation
🐰 분석 리포트워크스루프로젝트에 QR 스캔 기능 기반의 입고처리 및 사용처리 기능을 추가했습니다. 이는 QR 스캐너 구현체, API 계층, 리포지토리, 뷰모델, UI 컴포넌트를 포함하며, 프로젝트 설정에 카메라 사용 권한 설정을 추가했습니다. 또한 주문 상세 뷰의 레이아웃을 개선했습니다. 변경사항
시퀀스 다이어그램sequenceDiagram
participant User as 사용자
participant IncomingView as IncomingScanView
participant QRScanner as QRScannerView
participant ViewModel as OrderViewModel
participant API as OrderApi
User->>IncomingView: 입고 처리 화면 진입
IncomingView->>QRScanner: QR 스캐너 표시
User->>QRScanner: QR 코드 스캔
QRScanner->>IncomingView: scannedCode 바인딩 업데이트
IncomingView->>IncomingView: handleScannedCode() 호출
IncomingView->>ViewModel: receiveOrder(orderNumber) 실행
ViewModel->>API: receiveOrder 요청
API->>API: POST /api/v1/order/receive
alt 성공
API-->>ViewModel: 성공 응답
ViewModel->>ViewModel: loadOrders() 새로고침
ViewModel-->>IncomingView: 결과 메시지 반환
IncomingView->>User: 성공 알림 표시
else 실패
API-->>ViewModel: 오류 응답
ViewModel-->>IncomingView: 오류 메시지 반환
IncomingView->>User: 오류 알림 표시
end
sequenceDiagram
participant User as 사용자
participant OutgoingView as OutgoingScanView
participant QRScanner as QRScannerView
participant ViewModel as PartViewModel
participant API as PartApi
User->>OutgoingView: 사용 처리 화면 진입
OutgoingView->>QRScanner: QR 스캐너 표시
User->>QRScanner: 부품 QR 코드 스캔
QRScanner->>OutgoingView: scannedCode 바인딩 업데이트
OutgoingView->>OutgoingView: handleScannedCode() 호출
OutgoingView->>OutgoingView: ReleaseItemRequest 생성 (partCode, quantity=1)
OutgoingView->>ViewModel: releaseParts(items) 실행
ViewModel->>API: releaseParts 요청
API->>API: POST /api/v1/store/release
alt 성공
API-->>ViewModel: 성공 응답
ViewModel-->>OutgoingView: 결과 메시지 반환
OutgoingView->>User: 성공 알림 표시
else 실패
API-->>ViewModel: 오류 응답
ViewModel-->>OutgoingView: 오류 메시지 반환
OutgoingView->>User: 오류 알림 표시
end
예상 코드 리뷰 난이도🎯 3 (중간) | ⏱️ ~25분 주의가 필요한 부분:
관련 PR
제안 검수자
시
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
@coderabbitai review |
|
@Yoo-Hyuna: I'll review the changes in this PR right away. ✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
StockMate/StockMate/app/feature/auth/ui/LoginView.swift (1)
121-126: 즉시 제거 필요: 도달 불가능한 코드로 인한 검증 우회Line 125의
return true는 도달할 수 없는 코드입니다. Line 124가 이미 결과를 반환하므로 Line 125는 절대 실행되지 않습니다. 그러나 이 코드는 의도적으로 추가된 것으로 보이며, 실제로는 검증 로직을 완전히 우회하는 심각한 보안 문제를 발생시킵니다.영향:
- 유효하지 않은 이메일 형식으로 로그인 가능
- 8자 미만의 비밀번호로 로그인 가능
- 클라이언트 측 검증 완전 무력화
다음과 같이 수정하세요:
private func isValidForm() -> Bool { emailError = isValidEmail(authViewModel.email) ? nil : "이메일 형식을 확인해주세요" pwError = authViewModel.password.count >= 8 ? nil : "8자 이상 비밀번호를 입력해주세요" return emailError == nil && pwError == nil - return true }
🧹 Nitpick comments (8)
StockMate/StockMate/ContentView.swift (1)
10-49: 주석 처리된 코드 제거 권장주석 처리된 QR 스캐너 예제 코드가 더 이상 필요하지 않다면 제거하는 것을 권장합니다. 코드베이스를 깔끔하게 유지하는 데 도움이 됩니다.
StockMate/StockMate/app/feature/orders/viewmodel/OrderViewModel.swift (1)
99-111: 주석 처리된 코드를 제거하세요.더 이상 사용하지 않는 코드는 버전 관리 시스템에서 확인할 수 있으므로 삭제하는 것이 코드베이스를 깔끔하게 유지하는 데 도움이 됩니다.
-// func receiveOrder(orderNumber: String) async { -// isLoading = true -// defer { isLoading = false } -// -// let result = await repository.receiveOrder(orderNumber: orderNumber) -// switch result { -// case .success(let message): -// print("✅ 입고 처리 성공:", message) -// await loadOrders() // 리스트 갱신 -// case .failure(let error): -// errorMessage = error.message -// } -// }StockMate/StockMate/app/feature/inventory/ui/OutgoingScanView.swift (2)
13-13: 불필요한 nil 초기화를 제거하세요.옵셔널 변수는 명시적으로
nil을 할당하지 않아도 자동으로nil로 초기화됩니다.- @State private var scannedCode: String? = nil + @State private var scannedCode: String?Based on learnings
95-113: MainActor.run 호출을 단순화하세요.
PartViewModel이 이미@MainActor로 선언되어 있고 메인 스레드에서 호출되므로, 명시적인MainActor.run블록은 불필요합니다. 또한isLoading은partViewModel의@Published속성이므로 직접 설정할 수 있습니다.private func handleScannedCode(_ code: String) async { - await MainActor.run { - partViewModel.isLoading = true - } - let request = [ReleaseItemRequest(partCode: code, quantity: 1)] let result = await partViewModel.releaseParts(items: request) - await MainActor.run { - partViewModel.isLoading = false - switch result { - case .success(let message): - alertMessage = message - case .failure(let error): - alertMessage = error.message - } - showAlert = true + switch result { + case .success(let message): + alertMessage = message + case .failure(let error): + alertMessage = error.message } + showAlert = true }StockMate/StockMate/app/feature/inventory/ui/IncomingScanView.swift (2)
12-12: 불필요한 nil 초기화를 제거하세요.옵셔널 변수는 명시적으로
nil을 할당하지 않아도 자동으로nil로 초기화됩니다.- @State private var scannedCode: String? = nil + @State private var scannedCode: String?Based on learnings
90-106: MainActor.run 호출을 단순화하세요.
OrderViewModel이 이미@MainActor로 선언되어 있고, 이 뷰 자체도 메인 스레드에서 실행되므로 명시적인MainActor.run블록은 불필요합니다.private func handleScannedCode(_ code: String) async { - await MainActor.run { - orderViewModel.isLoading = true - } - let result = await orderViewModel.receiveOrder(orderNumber: code) - await MainActor.run { - orderViewModel.isLoading = false - switch result { - case .success(let message): - alertMessage = message - case .failure(let error): - alertMessage = error.message - } - showAlert = true + + switch result { + case .success(let message): + alertMessage = message + case .failure(let error): + alertMessage = error.message } + showAlert = true }StockMate/StockMate/app/feature/orders/ui/OrderDetailView.swift (1)
226-236: 입고 처리 버튼 기능 구현이 필요합니다.입고 처리 버튼이 표시되지만 아직 액션이 구현되지 않았습니다.
IncomingScanView로 이동하거나orderViewModel.receiveOrder()를 직접 호출하는 로직을 추가해야 합니다.입고 처리 버튼의 액션 구현 코드를 생성해드릴까요? 예를 들어, QR 스캔 화면으로 이동하거나 주문번호를 직접 전달하는 방식 중 어떤 방식을 선호하시는지 알려주세요.
StockMate/StockMate/app/feature/orders/data/OrderApi.swift (1)
107-112: 주석 처리된 코드는 제거하는 것이 좋습니다.버전 관리 시스템(Git)을 사용하고 있으므로, 주석 처리된 이전 코드는 제거하고 필요시 Git 히스토리에서 확인할 수 있습니다.
다음 diff를 적용하여 주석 처리된 코드를 제거하세요:
-// Response -//struct OrderCreateResponseData: Decodable { -// let orderId: Int -// let orderNumber: String -// let totalPrice: Int -// let orderStatus: String -//} -
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (18)
StockMate/StockMate.xcodeproj/project.pbxproj(3 hunks)StockMate/StockMate/ContentView.swift(1 hunks)StockMate/StockMate/Info.plist(1 hunks)StockMate/StockMate/app/feature/auth/ui/LoginView.swift(1 hunks)StockMate/StockMate/app/feature/inventory/ui/IncomingScanView.swift(1 hunks)StockMate/StockMate/app/feature/inventory/ui/InventoryView.swift(1 hunks)StockMate/StockMate/app/feature/inventory/ui/OutgoingScanView.swift(1 hunks)StockMate/StockMate/app/feature/orders/data/OrderApi.swift(2 hunks)StockMate/StockMate/app/feature/orders/data/OrderRepositoryImpl.swift(1 hunks)StockMate/StockMate/app/feature/orders/domain/OrderRepositoryProtocol.swift(1 hunks)StockMate/StockMate/app/feature/orders/ui/OrderDetailView.swift(3 hunks)StockMate/StockMate/app/feature/orders/viewmodel/OrderViewModel.swift(1 hunks)StockMate/StockMate/app/feature/parts/data/PartApi.swift(1 hunks)StockMate/StockMate/app/feature/parts/data/PartRepositoryImpl.swift(1 hunks)StockMate/StockMate/app/feature/parts/domain/PartRepositoryProtocol.swift(1 hunks)StockMate/StockMate/app/feature/parts/ui/QRScannerView.swift(1 hunks)StockMate/StockMate/app/feature/parts/ui/QRScannerViewController.swift(1 hunks)StockMate/StockMate/app/feature/parts/viewmodel/PartViewModel.swift(1 hunks)
🧰 Additional context used
🪛 SwiftLint (0.57.0)
StockMate/StockMate/app/feature/inventory/ui/OutgoingScanView.swift
[Warning] 13-13: Initializing an optional variable with nil is redundant
(redundant_optional_initialization)
StockMate/StockMate/app/feature/inventory/ui/IncomingScanView.swift
[Warning] 12-12: Initializing an optional variable with nil is redundant
(redundant_optional_initialization)
🔇 Additional comments (19)
StockMate/StockMate/Info.plist (1)
1-5: LGTM!표준 plist 구조이며 project.pbxproj에서 카메라 권한 설명이 올바르게 구성되어 있습니다.
StockMate/StockMate/app/feature/inventory/ui/InventoryView.swift (1)
129-129: LGTM!사용 처리 흐름을 별도의 OutgoingScanView로 분리한 것이 논리적으로 적절합니다.
StockMate/StockMate/app/feature/parts/domain/PartRepositoryProtocol.swift (1)
10-12: LGTM!레포지토리 패턴을 올바르게 따르고 있으며, 기존 OrderRepositoryProtocol과 일관된 구조를 가지고 있습니다.
StockMate/StockMate.xcodeproj/project.pbxproj (2)
17-25: LGTM!Info.plist 파일 예외 처리가 올바르게 구성되어 있습니다.
Also applies to: 30-32
279-280: LGTM!카메라 권한 설명이 Debug 및 Release 구성 모두에 적절하게 추가되었습니다. QR 스캔 기능을 위한 필수 설정입니다.
Also applies to: 309-310
StockMate/StockMate/app/feature/orders/domain/OrderRepositoryProtocol.swift (1)
27-31: LGTM!입고 처리 메서드가 프로토콜에 적절하게 추가되었습니다. 기존 메서드들과 일관된 네이밍과 시그니처를 따르고 있습니다.
StockMate/StockMate/app/feature/parts/data/PartRepositoryImpl.swift (1)
11-16: LGTM!레포지토리 구현이 깔끔하며 기존 OrderRepositoryImpl 패턴을 일관되게 따르고 있습니다. safeApi 유틸리티를 활용한 에러 처리도 적절합니다.
StockMate/StockMate/app/feature/orders/data/OrderRepositoryImpl.swift (1)
93-105: 입고 처리 메서드 구현이 잘 되었습니다.기존
cancelOrder메서드와 동일한 패턴으로 일관성 있게 구현되었으며, 에러 처리와 기본 메시지 처리가 적절합니다.StockMate/StockMate/app/feature/orders/viewmodel/OrderViewModel.swift (1)
82-96: 입고 처리 메서드가 올바르게 구현되었습니다.
AppResult<String>을 반환하여 호출자가 성공/실패를 직접 처리할 수 있도록 했으며, 성공 시loadOrders()를 호출하여 목록을 갱신하는 로직이 적절합니다.StockMate/StockMate/app/feature/inventory/ui/OutgoingScanView.swift (1)
100-100: 기본 수량 1개 설정을 확인하세요.현재 스캔된 부품의 수량이 항상 1개로 하드코딩되어 있습니다. 향후 사용자가 수량을 입력할 수 있는 기능이 필요할 수 있습니다.
이것이 의도된 동작인지 확인하고, 향후 수량 입력 기능 추가가 필요한지 검토하시기 바랍니다.
StockMate/StockMate/app/feature/parts/viewmodel/PartViewModel.swift (1)
23-43: 부품 사용 처리 메서드가 잘 구현되었습니다.로딩 상태 관리, 에러 처리, 인증 오류 감지(401/403) 등이 모두 적절하게 구현되어 있으며,
@MainActor를 사용한 스레드 안정성도 확보되었습니다.StockMate/StockMate/app/feature/parts/ui/QRScannerViewController.swift (1)
61-67: QR 코드 재스캔 기능을 고려하세요.현재 QR 코드를 한 번 스캔하면 세션이 중지되어 다시 스캔할 수 없습니다. 사용자가 여러 개의 QR 코드를 연속으로 스캔해야 하는 경우를 고려하여 세션 재시작 로직을 추가하는 것을 검토하시기 바랍니다.
현재 UX 요구사항에서 한 번의 스캔 후 뷰가 닫히는 것이 의도된 동작인지 확인하세요.
StockMate/StockMate/app/feature/orders/ui/OrderDetailView.swift (1)
28-118: UI 레이아웃 개선이 잘 되었습니다.주문 정보, 반려 메시지, 배송정보, 요청사항 섹션이 일관된 스타일로 분리되어 가독성과 사용자 경험이 향상되었습니다.
StockMate/StockMate/app/feature/orders/data/OrderApi.swift (3)
121-124: LGTM!입고 처리를 위한 요청 구조체가 올바르게 정의되었습니다. 단일 필드 구조로 명확하고 간결합니다.
180-190: LGTM!입고 처리 API 함수가 올바르게 구현되었습니다. 기존 API 함수들과 동일한 패턴을 따르고 있으며, URL 구성과 파라미터 인코딩이 적절합니다.
114-119: 코드베이스에 영향 없음 - 변경사항이 안전하게 격리되어 있습니다.검증 결과,
OrderCreateResponseData의 필드 변경이 기존 코드에 영향을 주지 않습니다:
- 뷰에서 접근하는
orderStatus는OrderResponseItem모델에서 오며, 이 모델은 여전히orderStatus필드를 유지하고 있습니다.createOrder()응답 처리 시response.orderId만 사용되므로,OrderCreateResponseData의 필드명 변경은 영향을 주지 않습니다.paymentType은 이미OrderResponseItem에 존재하므로 일관성이 유지됩니다.StockMate/StockMate/app/feature/parts/ui/QRScannerView.swift (3)
10-17: LGTM!UIViewControllerRepresentable 구현이 올바릅니다. QRScannerViewController를 생성하고 coordinator를 delegate로 설정하는 표준 패턴을 따르고 있습니다.
19-19: LGTM!
updateUIViewController가 비어있는 것은 이 경우 적절합니다. QR 스캐너는 생성 후 동적으로 업데이트할 상태가 없으며, 스캔 결과는 delegate 패턴을 통해 전달됩니다.
21-35: LGTM!Coordinator 구현이 올바릅니다. QRScannerDelegate를 통해 스캔된 코드를 받아 parent의 binding을 업데이트하는 표준 패턴을 따르고 있습니다.
final키워드 사용으로 성능도 최적화되어 있습니다.
| static func releaseParts(items: [ReleaseItemRequest]) -> DataRequest { | ||
| let url = ApiClient.baseURL + "api/v1/store/release" | ||
| let body: [String: Any] = [ | ||
| "items": items.map { ["partCode": $0.partCode, "quantity": $0.quantity] } | ||
| ] | ||
| return ApiClient.shared.request( | ||
| url, | ||
| method: .post, | ||
| parameters: body, | ||
| encoding: JSONEncoding.default | ||
| ) | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Encodable 프로토콜을 활용하여 타입 안정성을 개선하세요.
ReleaseItemRequest가 이미 Encodable을 준수하고 있지만, 현재 코드는 수동으로 딕셔너리를 구성하고 있습니다. 이는 타입 안정성이 떨어지고 오타 가능성이 있습니다.
+struct ReleasePartsRequest: Encodable {
+ let items: [ReleaseItemRequest]
+}
+
enum PartApi {
static func releaseParts(items: [ReleaseItemRequest]) -> DataRequest {
let url = ApiClient.baseURL + "api/v1/store/release"
- let body: [String: Any] = [
- "items": items.map { ["partCode": $0.partCode, "quantity": $0.quantity] }
- ]
+ let body = ReleasePartsRequest(items: items)
return ApiClient.shared.request(
url,
method: .post,
parameters: body,
- encoding: JSONEncoding.default
+ encoder: JSONParameterEncoder.default
)
}
}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In StockMate/StockMate/app/feature/parts/data/PartApi.swift around lines 20–31,
the code manually constructs a [String: Any] body from ReleaseItemRequest;
instead create a small Encodable wrapper (e.g. struct ReleaseItemsRequest:
Encodable { let items: [ReleaseItemRequest] }) and send that directly with
Alamofire's JSON encoder instead of building dictionaries. Replace the body
dictionary and items.map call with an instance of ReleaseItemsRequest(items:
items) and call ApiClient.shared.request(url, method: .post, parameters:
releaseItemsRequest, encoder: JSONParameterEncoder.default) (or use JSONEncoder
to encode Data into the request body) so the request uses Encodable for
type-safe JSON encoding.
| private func setupCamera() { | ||
| captureSession = AVCaptureSession() | ||
|
|
||
| guard let videoCaptureDevice = AVCaptureDevice.default(for: .video), | ||
| let videoInput = try? AVCaptureDeviceInput(device: videoCaptureDevice), | ||
| captureSession.canAddInput(videoInput) | ||
| else { | ||
| print("카메라 입력 불가") | ||
| return | ||
| } | ||
|
|
||
| captureSession.addInput(videoInput) | ||
|
|
||
| let metadataOutput = AVCaptureMetadataOutput() | ||
| guard captureSession.canAddOutput(metadataOutput) else { | ||
| print("출력 연결 불가") | ||
| return | ||
| } | ||
|
|
||
| captureSession.addOutput(metadataOutput) | ||
| metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main) | ||
| metadataOutput.metadataObjectTypes = [.qr] | ||
|
|
||
| previewLayer = AVCaptureVideoPreviewLayer(session: captureSession) | ||
| previewLayer.frame = view.layer.bounds | ||
| previewLayer.videoGravity = .resizeAspectFill | ||
| view.layer.addSublayer(previewLayer) | ||
|
|
||
| // ⚠️ 백그라운드에서 실행 | ||
| DispatchQueue.global(qos: .userInitiated).async { | ||
| self.captureSession.startRunning() | ||
| } | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
카메라 권한 확인 및 에러 처리를 개선하세요.
현재 카메라 설정 실패 시 콘솔에만 출력되고 사용자에게 피드백이 제공되지 않습니다. 카메라 권한 확인과 사용자 친화적인 에러 처리가 필요합니다.
private func setupCamera() {
+ // 카메라 권한 확인
+ switch AVCaptureDevice.authorizationStatus(for: .video) {
+ case .authorized:
+ break
+ case .notDetermined:
+ AVCaptureDevice.requestAccess(for: .video) { granted in
+ if granted {
+ DispatchQueue.main.async {
+ self.setupCamera()
+ }
+ }
+ }
+ return
+ case .denied, .restricted:
+ // 사용자에게 설정으로 이동하도록 안내
+ DispatchQueue.main.async {
+ // delegate를 통해 에러 전달하거나 alert 표시
+ }
+ return
+ @unknown default:
+ return
+ }
+
captureSession = AVCaptureSession()
guard let videoCaptureDevice = AVCaptureDevice.default(for: .video),
let videoInput = try? AVCaptureDeviceInput(device: videoCaptureDevice),
captureSession.canAddInput(videoInput)
else {
- print("카메라 입력 불가")
+ // 사용자에게 에러 피드백 제공
+ DispatchQueue.main.async {
+ // delegate를 통해 에러 전달
+ }
return
}🤖 Prompt for AI Agents
In StockMate/StockMate/app/feature/parts/ui/QRScannerViewController.swift around
lines 26 to 58, the camera setup currently only prints errors and does not
check/request camera permission or present user-facing errors; update
setupCamera to first check AVCaptureDevice.authorizationStatus(for: .video) and
if .notDetermined call requestAccess and proceed only on granted, if .denied or
.restricted present a UIAlertController on the main thread explaining the need
for camera access with an action to open UIApplication.openSettingsURLString,
and replace print(...) failure paths with user-facing alerts (also ensure any UI
changes like adding previewLayer or presenting alerts are done on the main
thread); keep startRunning on a background queue but guard against nil
captureSession and call captureSession.stopRunning in failure paths.
📣 Related Issue
📝 Summary
Summary by CodeRabbit
릴리스 노트