Skip to content

feat: sales API 데이터 레이어 (network/DTO/repository, USE_API 플래그) - #3

Merged
userri merged 2 commits into
mainfrom
feat/api-sales-data-layer
Jun 21, 2026
Merged

feat: sales API 데이터 레이어 (network/DTO/repository, USE_API 플래그)#3
userri merged 2 commits into
mainfrom
feat/api-sales-data-layer

Conversation

@userri

@userri userri commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

무엇

sales 서비스 연동을 위한 데이터 레이어(네트워크·DTO·API·repository)를 추가합니다. 계약은 sales 백엔드 컨트롤러/DTO로 검증했습니다.

  • data/remote/Net.kt — OkHttp+Retrofit+Gson 단일 설정 + Bearer 인터셉터(토큰은 Net.bearer로 주입)
  • data/remote/UiState.kt — Loading/Success/Error
  • data/remote/SalesApi.ktGET /api/v1/sales-orders(검색), PATCH /{soNumber}/receive(도착 확인)
  • data/remote/dto/SalesOrderDto.ktSalesOrderPageDto·SalesOrderSummaryDto·PaginationDto (백엔드 record와 1:1)
  • data/repo/SalesOrderRepository.ktarrivals / branchOrders / receivedByMe / receive
  • build.gradle.ktsBuildConfig.BASE_URL·USE_API(gradle property 주입), buildConfig=true, 의존성(Retrofit/OkHttp/Coroutines)
  • AndroidManifest.xml — INTERNET 권한, dev용 cleartext

안전장치

  • 화면 미배선. USE_API 기본 false → 앱은 기존과 100% 동일(목업). 이 PR은 순수 additive 데이터 레이어.
  • 의존성은 컴파일 플러그인 없는 최소셋(Retrofit+Gson+OkHttp+Coroutines) — kotlinx-serialization/ksp 미사용으로 AGP9 충돌 회피.
  • 공개 레포라 host는 코드에 하드코딩하지 않고 -PBBD_BASE_URL로 주입(기본 emulator localhost).

⚠️ 빌드 검증 필요

작성 환경에 Android SDK가 없어 작성자가 컴파일하지 못했습니다. 머지 전 확인 부탁:

./gradlew assembleDebug
# 실 API로 켜서 실행:
./gradlew assembleDebug -PBBD_USE_API=true -PBBD_BASE_URL="http://10.0.2.2:8080/sales/"

다음 PR에서 결정 필요 — 모델 매핑

모바일 모델이 백엔드와 1:1이 아님:

모바일 백엔드
Pr(부품 1개=요청 1건) SalesOrder(다중 라인 주문) 라인/수량은 요약 응답에 없음 → 상세(GET /{soNumber}) 필요
PrStatus(5상태) SalesOrderStatus(7상태) BACKORDERED/CANCELED 매핑 미정
Movement(입/출고) StockMovement(inventory) / SO 작업이력 소스 결정 필요
출고(OUT) (엔드포인트 없음) 모바일 출고 보류

연동 가능/차단 매트릭스

  • sales: 도착 대기·보충 발주·입고 확정·작업이력(received_by) — 이 PR의 repository로 커버
  • ⚠️ 재고 조회: inventory 서비스(타 레포) — 계약 별도 확인
  • ⚠️ /me(프로필·권한): user 서비스 — 로그인 게이팅 대체에 필요
  • 🚫 출고 스캔: 모바일 출고 엔드포인트 없음

로드맵

  1. (이 PR) 데이터 레이어
  2. 인증 — Keycloak OIDC(AppAuth) → Net.bearer 주입
  3. 화면 배선 — 도착 대기 큐 → 입고 확정 → 작업이력(모델 매핑 합의 후)
  4. 로딩/에러/빈 상태(UX 감사 재고 조회(Inventory) API 연동 + 게이트웨이 멀티서비스 base URL #8)

Summary by CodeRabbit

  • New Features
    • Added an authenticated backend integration for sales orders with search and filtering by status, warehouse, and user.
    • Added receipt confirmation for specific sales orders.
    • Introduced unified UI state handling for network calls (loading, success, error).
  • Bug Fixes / Improvements
    • Added internet permission to support REST/gateway network requests.
  • Debug
    • Enabled HTTP cleartext traffic for debug builds (release builds remain HTTPS-focused).

…플래그)

- data/remote: Net(OkHttp+Retrofit+Gson, Bearer 인터셉터), UiState, SalesApi, DTO
- data/repo: SalesOrderRepository (arrivals/branchOrders/receivedByMe/receive)
- build: BuildConfig BASE_URL/USE_API(gradle property), INTERNET 권한
- 화면 미배선(USE_API 기본 false) → 앱 동작 무변경. 모델 매핑은 후속 PR.
@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR introduces a complete networking and data layer for sales orders. It configures Retrofit and OkHttp via Gradle properties injected into BuildConfig, adds the necessary manifest permissions, defines a Net singleton for HTTP client construction, declares a SalesApi Retrofit interface with DTOs, introduces a UiState sealed type, and implements SalesOrderRepository wrapping all API calls with coroutine-based error handling.

Changes

Sales Order Networking & Data Layer

Layer / File(s) Summary
Build config, dependencies, and manifest permissions
gradle/libs.versions.toml, app/build.gradle.kts, app/src/main/AndroidManifest.xml, app/src/debug/AndroidManifest.xml
Declares pinned versions and library aliases for Retrofit, OkHttp, and coroutines; reads BBD_BASE_URL/BBD_USE_API from Gradle properties with hardcoded defaults and injects them as BuildConfig fields; enables buildConfig generation; adds all four dependencies; declares INTERNET permission in main manifest and enables usesCleartextTraffic="true" in debug manifest.
Net singleton — OkHttp + Retrofit client
app/src/main/java/com/example/bbd/data/remote/Net.kt
Defines the Net object with a @Volatile nullable bearer token, an OkHttp client with connect/read timeouts and a conditional Authorization: Bearer interceptor, optional HttpLoggingInterceptor in debug builds, and lazy Retrofit construction from BuildConfig.BASE_URL with a GsonConverterFactory and a generic create() factory method.
SalesApi interface, DTOs, and UiState
app/src/main/java/com/example/bbd/data/remote/SalesApi.kt, app/src/main/java/com/example/bbd/data/remote/dto/SalesOrderDto.kt, app/src/main/java/com/example/bbd/data/remote/UiState.kt
Declares the SalesApi Retrofit interface with search (GET with optional status/warehouse/user filters and pagination) and receive (PATCH by soNumber); adds SalesOrderPageDto, SalesOrderSummaryDto, and PaginationDto response models; introduces the UiState<T> sealed interface with Loading, Success<T>, and Error variants.
SalesOrderRepository
app/src/main/java/com/example/bbd/data/repo/SalesOrderRepository.kt
Implements SalesOrderRepository with suspend methods arrivals, branchOrders, and receivedByMe (each filtering api.search(...) through a shared private searchAll pagination helper) and receive (explicit success/error branching on the PATCH response); all calls run on Dispatchers.IO and map results to UiState via try/catch with CancellationException passthrough.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 Hop, hop through the network maze,
A Bearer token lights my ways,
SalesApi calls with Gson delight,
UiState wraps each loading night,
The repo catches every fall —
One coroutine to handle all! 🌐

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: introducing a sales API data layer with network configuration, DTOs, repository, and the USE_API flag.
Docstring Coverage ✅ Passed Docstring coverage is 88.89% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/api-sales-data-layer

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/build.gradle.kts`:
- Around line 11-12: The `bbdUseApi` variable currently stores the property as a
String value ("true" or "false"), but when emitted to BuildConfig at line 31, it
needs to be an actual Boolean instead of a string literal to avoid breaking code
generation. Convert the `bbdUseApi` variable from a String to a real Boolean by
parsing the string value to determine if it evaluates to true or false, ensuring
that the BuildConfig field receives a proper boolean literal rather than a raw
string value.

In `@app/src/main/AndroidManifest.xml`:
- Around line 9-11: The android:usesCleartextTraffic="true" attribute in the
main AndroidManifest.xml enables cleartext HTTP traffic for all build variants
including release builds, which compromises transport security in production.
Remove the android:usesCleartextTraffic="true" attribute from the application
element in the main AndroidManifest.xml. Then add this attribute only to the
debug variant's AndroidManifest.xml located in src/debug/AndroidManifest.xml, or
alternatively create a debug-only networkSecurityConfig resource file that
permits cleartext traffic exclusively for debug builds. This ensures HTTP
cleartext is allowed only during development and testing, not in release builds.

In `@app/src/main/java/com/example/bbd/data/remote/Net.kt`:
- Around line 42-45: The `retrofit` lazy property is using
`BuildConfig.BASE_URL` directly with the `.baseUrl()` method, but Retrofit
requires URLs to end with a trailing slash and the gradle property doesn't
enforce this. Normalize `BuildConfig.BASE_URL` before passing it to `.baseUrl()`
by checking if it ends with a forward slash and appending one if it doesn't,
ensuring Retrofit receives a properly formatted URL that won't cause an
`IllegalArgumentException` at runtime.

In `@app/src/main/java/com/example/bbd/data/repo/SalesOrderRepository.kt`:
- Around line 21-30: The three methods arrivals, branchOrders, and receivedByMe
are calling api.search() without pagination parameters, which means they only
fetch the first page of results (default size 50) and silently drop all
remaining data. Update each of these methods to iterate through all available
pages by looping while the current page number is less than
pagination.totalPages, accumulating all items from each page into a single list
before returning it. Pass page and size parameters to api.search() in each
iteration to fetch subsequent pages.
- Around line 35-41: The runCatching block with the fold call for
api.receive(soNumber) is catching CancellationException along with other
exceptions, which prevents proper coroutine cancellation. In the onFailure
lambda, add a check at the beginning to detect if the thrown exception is a
CancellationException and re-throw it to preserve structured concurrency,
otherwise wrap the exception in UiState.Error as currently done. Apply this same
fix to the second runCatching block mentioned in lines 48-51.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9fa47cd7-f069-4f37-ac14-9e701b816801

📥 Commits

Reviewing files that changed from the base of the PR and between 91a3dea and 6f098f6.

📒 Files selected for processing (8)
  • app/build.gradle.kts
  • app/src/main/AndroidManifest.xml
  • app/src/main/java/com/example/bbd/data/remote/Net.kt
  • app/src/main/java/com/example/bbd/data/remote/SalesApi.kt
  • app/src/main/java/com/example/bbd/data/remote/UiState.kt
  • app/src/main/java/com/example/bbd/data/remote/dto/SalesOrderDto.kt
  • app/src/main/java/com/example/bbd/data/repo/SalesOrderRepository.kt
  • gradle/libs.versions.toml

Comment thread app/build.gradle.kts Outdated
Comment thread app/src/main/AndroidManifest.xml Outdated
Comment thread app/src/main/java/com/example/bbd/data/remote/Net.kt
Comment thread app/src/main/java/com/example/bbd/data/repo/SalesOrderRepository.kt Outdated
Comment thread app/src/main/java/com/example/bbd/data/repo/SalesOrderRepository.kt Outdated
- build.gradle: BBD_USE_API → boolean 리터럴 정규화(임의 문자열 codegen 깨짐 방지)
- Net: Retrofit BASE_URL 트레일링 슬래시 정규화(런타임 IllegalArgumentException 방지)
- Manifest: cleartext(http) 허용을 디버그 빌드 전용으로 분리(src/debug), 릴리스는 https만
- Repository: 전체 페이지 수집(첫 페이지 누락 방지, 20p 안전캡) + CancellationException 재던짐(structured concurrency 보존)
- (Copilot Overlays interactionSource=null 지적은 tapNoRipple 사용+Compose 2026 nullable 오버로드라 무효)
- 로컬 assembleDebug GREEN

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/main/java/com/example/bbd/data/repo/SalesOrderRepository.kt`:
- Around line 48-71: The searchAll function silently returns incomplete results
when the MAX_PAGES limit is reached without indicating truncation to the caller.
After the while loop exits in searchAll, add a check to detect if the function
stopped due to hitting the MAX_PAGES cap while there are still more pages
available (by comparing the current page value against MAX_PAGES and checking if
there are remaining pages), and log a warning message to signal that the dataset
has been truncated, making it clear to users that the returned list may be
incomplete.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 43fc4907-4df1-4b95-895c-1adac631b394

📥 Commits

Reviewing files that changed from the base of the PR and between 6f098f6 and 08c0504.

📒 Files selected for processing (5)
  • app/build.gradle.kts
  • app/src/debug/AndroidManifest.xml
  • app/src/main/AndroidManifest.xml
  • app/src/main/java/com/example/bbd/data/remote/Net.kt
  • app/src/main/java/com/example/bbd/data/repo/SalesOrderRepository.kt
✅ Files skipped from review due to trivial changes (1)
  • app/src/debug/AndroidManifest.xml
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/build.gradle.kts
  • app/src/main/java/com/example/bbd/data/remote/Net.kt

Comment on lines +48 to +71
private suspend fun searchAll(
status: String? = null,
toWarehouseCode: String? = null,
requestedBy: String? = null,
receivedBy: String? = null,
): List<SalesOrderSummaryDto> {
val all = mutableListOf<SalesOrderSummaryDto>()
var page = 0
while (page < MAX_PAGES) {
val resp = api.search(
status = status,
toWarehouseCode = toWarehouseCode,
requestedBy = requestedBy,
receivedBy = receivedBy,
page = page,
size = PAGE_SIZE,
)
all += resp.items
val totalPages = resp.pagination?.totalPages ?: 1
page++
if (page >= totalPages || resp.items.isEmpty()) break
}
return all
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Silent truncation at page cap returns incomplete datasets.

searchAll(...) stops at MAX_PAGES (Line 56) but currently returns partial results without signaling truncation. If total pages exceed 20, list APIs will silently omit orders.

Suggested fix
 private suspend fun searchAll(
@@
 ): List<SalesOrderSummaryDto> {
     val all = mutableListOf<SalesOrderSummaryDto>()
     var page = 0
+    var completed = false
     while (page < MAX_PAGES) {
         val resp = api.search(
@@
         all += resp.items
         val totalPages = resp.pagination?.totalPages ?: 1
         page++
-        if (page >= totalPages || resp.items.isEmpty()) break
+        if (page >= totalPages || resp.items.isEmpty()) {
+            completed = true
+            break
+        }
     }
+    if (!completed) {
+        throw IllegalStateException("조회 건수가 한도를 초과했습니다. 검색 조건을 좁혀주세요.")
+    }
     return all
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/java/com/example/bbd/data/repo/SalesOrderRepository.kt` around
lines 48 - 71, The searchAll function silently returns incomplete results when
the MAX_PAGES limit is reached without indicating truncation to the caller.
After the while loop exits in searchAll, add a check to detect if the function
stopped due to hitting the MAX_PAGES cap while there are still more pages
available (by comparing the current page value against MAX_PAGES and checking if
there are remaining pages), and log a warning message to signal that the dataset
has been truncated, making it clear to users that the returned list may be
incomplete.

userri pushed a commit that referenced this pull request Jun 21, 2026
- 텍스트 자동 머지 + 의미 충돌 1건 해소: #2의 'import 정리'가 지운 Pretendard 복구
  (#4 OrderScreenApi/SoStatusChip 가 사용 — 미복구 시 통합 main 컴파일 실패)
- 통합 assembleDebug GREEN (AS JBR 21)
@userri
userri merged commit db5d2a8 into main Jun 21, 2026
1 check passed
userri added a commit that referenced this pull request Jun 21, 2026
[Feat] : #4 보충발주 API연동+상태UI — #2·#3 통합본
@userri
userri deleted the feat/api-sales-data-layer branch June 21, 2026 08:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant