Skip to content

Add dashboard bike snapshot aggregate endpoint (#132)#133

Merged
MasterOfEVNSolution merged 1 commit into
devfrom
cc-120-dashboard-bike-snapshot-endpoint
May 8, 2026
Merged

Add dashboard bike snapshot aggregate endpoint (#132)#133
MasterOfEVNSolution merged 1 commit into
devfrom
cc-120-dashboard-bike-snapshot-endpoint

Conversation

@tkdgusqkr

Copy link
Copy Markdown
Contributor

Summary

  • 신규 `GET /api/v1/dashboard/bikes/{bikeId}/snapshot` — 디테일 패널이 한 번의 호출로 차량 + 활성 계약 + 라이더(+교육 요약) + 활성 보험 + 활성 장비 정보를 받음.
  • 4 쿼리 (bike / contract+template / rider+latest education / insurances+items / equipments+types) 가 한 `@Transactional(readOnly=true)` 안에서 실행 → 일관된 스냅샷.
  • Telemetry `currentState` 는 의도적으로 분리 (기존 `/api/v1/telemetry/bikes/{id}/current-state` 에서 별도 fetch).

Trace

Scope

Backend-only. 다음 슬라이스 (③-2) 가 프론트 디테일 패널을 이 endpoint 에 연결.

Included:

  • `DashboardBikeSnapshotController` + `Service` + `QueryRepository` + `Response` DTO.
  • 통합 테스트 `DashboardBikeSnapshotApiTests` (7 케이스).

Excluded:

  • 프론트 디테일 패널 변경 (③-2).
  • currentState 통합 (별도 endpoint 사용).
  • 차량 운영 상태 이력 / 디바이스 설치 이력 / 충전소 / 라이더 다중 활성 계약.

응답 구조

```
{
bikeId, generatedAt,
bike: { id, idx, plateNumber, vin, modelName, operationStatus, memo, createdAt, updatedAt },
activeContract: null | { id, idx, contractTemplateId, templateName,
templateCategory, templateReturnType,
templateDurationUnit, templateDurationValue,
templateIncludesInsurance,
startAt, endAt, terminatedAt, terminatedReason, memo },
rider: null | { id, idx, name, phoneNumber, teamName, areaName,
appLinkStatus, memo,
educationCompleted, latestEducationType,
latestEducationCompletedAt, latestEducationExpiresAt,
educationExpired },
insurances: [ { id, insuranceItemId, itemName, category, coverageType,
startsAt, endsAt, riderBikeContractId, memo } ],
equipments: [ { id, equipmentTypeId, typeName, equipmentLabel, modelName,
serialNumber, installedAt, removedAt,
managementDueDate, memo } ]
}
```

Validation

항목 결과
`./gradlew compileJava compileTestJava` ✅ BUILD SUCCESSFUL (20s)
`./gradlew test --tests ArchitectureBoundaryTests ScaffoldPackageSkeletonTests` ✅ BUILD SUCCESSFUL (25s)
Testcontainers 통합 테스트 (전체) ⚠️ Docker 미설치로 sandbox 미실행. 리뷰어/IDE 환경 검증 필요.

Test plan

  • IDE에서 `./gradlew test` 전체 실행 → `DashboardBikeSnapshotApiTests` 7 케이스 통과 + 다른 통합 테스트 회귀 없음.
  • dev DB에 시드 스크립트 실행 후 `GET /api/v1/dashboard/bikes/{seedBikeId}/snapshot` 호출 → bike 정보가 정확히 반환되고 (활성 계약/라이더/장비/보험이 시드된 경우 채워짐).
  • 존재하지 않는 bikeId → 404 + `code: RESOURCE_NOT_FOUND`.
  • 인증 없이 호출 → 401 + `code: AUTHENTICATION_FAILED`.
  • removed/disabled 장비·보험은 응답에서 제외.

Concurrent work gate

Note

이번 PR 머지 후 `Slice ③-2 (프론트 디테일 패널 join 탭)` 로 이어집니다. 그 슬라이스가 머지되면 운영자가 마커 클릭 한 번으로 차량 + 라이더 + 계약 + 보험 + 장비 + 교육 정보를 모두 화면에서 확인 가능.

Slice ③-1 of the detail-panel join-tab work. Adds a single backend
endpoint that returns bike + active rider-bike contract + rider (with
education summary) + active rider insurances + active bike equipments in
one read-only transaction. The dashboard detail panel will call this
once on marker-click instead of fan-out fetching every domain endpoint
from the browser.

GET /api/v1/dashboard/bikes/{bikeId}/snapshot

Pieces:
- DashboardBikeSnapshotResponse DTO with five nested record summaries
  (bike / activeContract / rider / riderInsuranceSummary[] /
  bikeEquipmentSummary[]).
- DashboardBikeSnapshotQueryRepository with four targeted SQL queries:
  bike read, active contract + template join, rider + lateral latest
  education record, active rider insurances + insurance items, active
  bike equipments + equipment types. Each query uses partial indexes
  already present from V1–V9.
- DashboardBikeSnapshotService runs the queries inside a single
  @transactional(readOnly = true) so the operator sees a coherent view
  even if a rider link mutates while the panel is opening.
- DashboardBikeSnapshotController exposes the GET endpoint; ArchUnit
  dashboard-read-only rule passes (no PUT/POST/PATCH/DELETE).

Telemetry currentState is intentionally NOT bundled here. The detail
panel keeps fetching that from the existing
`GET /api/v1/telemetry/bikes/{id}/current-state` so polling and the
snapshot stay independently cacheable.

Tests:
- New DashboardBikeSnapshotApiTests covers full-active happy path,
  no-active-contract path (rider/insurances null/empty), no-insurance
  path, removed equipment + disabled rider insurance filtering,
  unknown-bike → 404, missing-auth → 401, and education-expired
  computation against the latest record.

Trace:
- Target issue: #132
- Change-control issue: EVNSolution/clever-change-control#120

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

2 participants