Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
13abf18
feat(farming): 작물-농장 등록 검증용 에러코드 추가
Kimseungin0529 Jul 19, 2026
023fdef
feat(farming): 기록 작물이 해당 농장 등록 작물인지 검증
Kimseungin0529 Jul 19, 2026
f7edd68
test(farming): 미등록 작물 기록 거부 회귀 테스트 추가
Kimseungin0529 Jul 19, 2026
4c23dea
refactor(voice): 음성 지침을 역할별 섹션으로 분해
Kimseungin0529 Jul 20, 2026
8ad40ed
feat(voice): 완료 확인·ETC 폴백·예외 인지 지침 추가
Kimseungin0529 Jul 20, 2026
ea32566
test(voice): 완료 확인·ETC·예외 인지 지침 회귀 테스트 추가
Kimseungin0529 Jul 20, 2026
631c86a
fix(record): 기록 목록 2번째 뱃지에 workType별 상세값 표시
iyungui Jul 20, 2026
a09059b
fix(auth): 갱신 실패 시 네트워크 오류로 세션이 풀리는 문제 수정
iyungui Jul 20, 2026
6d68b5d
[fix] historylink 주석처리
iyungui Jul 20, 2026
c0e530c
[design] report donut
iyungui Jul 20, 2026
156174d
fix(farm): 재배지 주소 수정 400·빈 주소 저장·첫 진입 미표시 수정
iyungui Jul 20, 2026
f3fba5e
Merge branch 'main' of https://github.com/wingwogus/ChamChamCham into…
iyungui Jul 20, 2026
2c5bc7d
fix(home): 홈 날씨 카드 간헐적 실패 자동 재시도 및 재시도 버튼 추가
iyungui Jul 20, 2026
de009bc
revert(onboarding): 재배지 지도 대한민국 권역 제한 제거
iyungui Jul 20, 2026
df53934
fix(record): 이미지 업로드 중 완료 버튼 비활성화
iyungui Jul 20, 2026
5e18212
fix(mypage): 회원탈퇴 확인 다이얼로그 문구 개선
iyungui Jul 20, 2026
880e82c
[fix] 개인정보처리방침 추가
iyungui Jul 20, 2026
dfa1aae
fix(onboarding): 재배지 지도 하단 카드가 키보드에 밀려 겹치는 문제 수정
iyungui Jul 20, 2026
6d88e71
fix(onboarding): 재배지 주소 조회가 해외 네트워크에서 응답 없어도 항상 진행 가능하도록 수정
iyungui Jul 21, 2026
e60a50e
fix(onboarding): 재배지 작도 후 주소 조회 실패 시 기존 주소 필드를 직접 입력으로 전환
iyungui Jul 21, 2026
5603ecb
Merge remote-tracking branch 'origin/dev' into feat/record-crop-farm-…
Kimseungin0529 Jul 21, 2026
ead7102
feat(weather): 외부 날씨 API 폴백 상수/팩토리 추가
Kimseungin0529 Jul 21, 2026
d237656
fix(weather): 실황 실패 시 503 대신 폴백으로 채워 조회를 계속 진행
Kimseungin0529 Jul 21, 2026
8c63948
fix(weather): getDaily가 provider 장애 시 폴백을 반환하도록 수정
Kimseungin0529 Jul 21, 2026
f41e2ec
fix(coaching): 기록 피드백 라이브 날씨도 장애 시 폴백 사용
Kimseungin0529 Jul 21, 2026
9f1cd37
Merge pull request #57 from wingwogus/feat/weather-fallback
Kimseungin0529 Jul 21, 2026
485ea3c
Merge remote-tracking branch 'origin/dev' into feat/record-crop-farm-…
Kimseungin0529 Jul 21, 2026
bde7f8e
Merge pull request #53 from wingwogus/feat/record-crop-farm-validation
Kimseungin0529 Jul 21, 2026
41f3436
Merge remote-tracking branch 'origin/dev' into feat/voice-prompt-cases
Kimseungin0529 Jul 21, 2026
4591d4c
Merge pull request #54 from wingwogus/feat/voice-prompt-cases
Kimseungin0529 Jul 21, 2026
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 @@ -12,10 +12,12 @@ import com.chamchamcham.domain.farming.PlantingRecordRepository
import com.chamchamcham.domain.farming.WateringRecordRepository
import com.chamchamcham.domain.farming.WeedingRecordRepository
import com.chamchamcham.domain.farming.WorkType
import com.chamchamcham.application.weather.WeatherFallback
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Transactional
import java.time.Clock
import java.time.LocalDate
import java.time.LocalDateTime
import java.util.UUID

@Component
Expand Down Expand Up @@ -154,19 +156,35 @@ class RecordFeedbackContextAssembler(
weatherPort.fetch(latitude, longitude, WEATHER_LIMIT_DAYS) to emptyList()
} catch (exception: BusinessException) {
when (exception.errorCode) {
// 좌표 문제는 외부 API 장애가 아니라 데이터 부재라 폴백 대상이 아니다(null 유지).
ErrorCode.WEATHER_LOCATION_REQUIRED -> null to listOf(WEATHER_LOCATION_UNAVAILABLE)
ErrorCode.WEATHER_PROVIDER_UNAVAILABLE -> null to listOf(WEATHER_PROVIDER_UNAVAILABLE)
// 외부 provider 장애는 서비스가 멈추지 않도록 폴백 날씨를 제공한다. degraded 사유는 남긴다.
ErrorCode.WEATHER_PROVIDER_UNAVAILABLE -> fallbackWeather() to listOf(WEATHER_PROVIDER_UNAVAILABLE)
else -> throw exception
}
} catch (_: RuntimeException) {
null to listOf(WEATHER_PROVIDER_UNAVAILABLE)
fallbackWeather() to listOf(WEATHER_PROVIDER_UNAVAILABLE)
}
}

// 외부 날씨 API 장애 시의 고정 폴백 라이브 날씨. 가짜 데이터에 위험경보를 붙이지 않으려고
// forecastDays는 비우고 riskFlags도 없다. source로 폴백임을 추적 가능하게 한다.
private fun fallbackWeather(): RecordFeedbackLiveWeather =
RecordFeedbackLiveWeather(
current = RecordFeedbackCurrentWeather(
temperatureC = WeatherFallback.TEMPERATURE,
skyCondition = WeatherFallback.CONDITION.text,
observedAt = LocalDateTime.now(clock),
),
forecastDays = emptyList(),
source = FALLBACK_SOURCE,
)

private companion object {
const val WEATHER_LIMIT_DAYS = 7
const val WEATHER_LOCATION_UNAVAILABLE = "weather_location_unavailable"
const val WEATHER_PROVIDER_UNAVAILABLE = "weather_provider_unavailable"
const val WEATHER_SKIPPED_FOR_HISTORICAL_RECORD = "weather_skipped_for_historical_record"
const val FALLBACK_SOURCE = "FALLBACK"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ enum class ErrorCode(
FARMING_RECORD_DETAIL_REQUIRED("FARMING_002", "error.farming_record_detail_required", 400),
FARMING_RECORD_INVALID_DETAIL("FARMING_003", "error.farming_record_invalid_detail", 400),
FARMING_RECORD_FORBIDDEN("FARMING_004", "error.farming_record_forbidden", 403),
FARMING_RECORD_CROP_NOT_IN_FARM("FARMING_006", "error.farming_record_crop_not_in_farm", 400),
WEATHER_LOCATION_REQUIRED("WEATHER_001", "error.weather_location_required", 400),
WEATHER_PROVIDER_UNAVAILABLE("WEATHER_002", "error.weather_provider_unavailable", 503),
WEATHER_DATE_IN_FUTURE("WEATHER_003", "error.weather_date_in_future", 400),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.chamchamcham.application.report.ReportScope
import com.chamchamcham.domain.crop.Crop
import com.chamchamcham.domain.crop.CropRepository
import com.chamchamcham.domain.crop.CropUsePartCategory
import com.chamchamcham.domain.crop.MemberCropRepository
import com.chamchamcham.domain.farm.Farm
import com.chamchamcham.domain.farm.FarmRepository
import com.chamchamcham.domain.farming.FarmingRecord
Expand Down Expand Up @@ -49,6 +50,7 @@ class FarmingRecordService(
private val memberRepository: MemberRepository,
private val farmRepository: FarmRepository,
private val cropRepository: CropRepository,
private val memberCropRepository: MemberCropRepository,
private val farmingRecordRepository: FarmingRecordRepository,
private val farmingRecordMediaRepository: FarmingRecordMediaRepository,
private val farmingRecordQueryRepository: FarmingRecordQueryRepository,
Expand All @@ -72,6 +74,7 @@ class FarmingRecordService(
val member = findMember(command.memberId)
val farm = findFarm(command.farmId, command.memberId)
val crop = findCrop(command.cropId)
assertCropRegisteredToFarm(command.memberId, command.farmId, command.cropId)
val media = validateMedia(command.memberId, command.mediaIds)

val record = farmingRecordRepository.save(
Expand Down Expand Up @@ -162,6 +165,7 @@ class FarmingRecordService(

val farm = findFarm(command.farmId, command.memberId)
val crop = findCrop(command.cropId)
assertCropRegisteredToFarm(command.memberId, command.farmId, command.cropId)
val existingRecordMedia = farmingRecordMediaRepository.findByRecord_Id(command.recordId)
val media = validateUpdatedMedia(record, command.memberId, command.mediaIds)

Expand Down Expand Up @@ -575,6 +579,14 @@ class FarmingRecordService(
BusinessException(ErrorCode.CROP_NOT_FOUND)
}

// 회원이 해당 농장에 등록한(MemberCrop) 작물만 기록에 쓸 수 있다.
// farm 소유는 findFarm에서, 전역 작물 존재는 findCrop에서 확인하고, 여기서 (농장,작물) 등록 조합을 확인한다.
private fun assertCropRegisteredToFarm(memberId: UUID, farmId: UUID, cropId: UUID) {
if (!memberCropRepository.existsByMemberIdAndFarmIdAndCropId(memberId, farmId, cropId)) {
throw BusinessException(ErrorCode.FARMING_RECORD_CROP_NOT_IN_FARM)
}
}

private fun findPesticide(pesticideId: UUID): Pesticide =
pesticideRepository.findById(pesticideId).orElseThrow {
BusinessException(ErrorCode.PESTICIDE_NOT_FOUND)
Expand Down
Loading
Loading