Skip to content
Merged

Dev #12

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
3 changes: 3 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ dependencies {
debugImplementation(libs.androidx.compose.ui.test.manifest)
// Testing dependencies
testImplementation(libs.junit)
testImplementation(libs.mockk)
testImplementation(libs.turbine)
testImplementation(libs.kotlinx.coroutines.test)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
Expand Down
34 changes: 29 additions & 5 deletions app/src/main/java/com/autoever/everp/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,21 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import com.autoever.everp.ui.MainScreen
import com.autoever.everp.ui.navigation.AppNavGraph
import com.autoever.everp.ui.splash.SplashScreen
import com.autoever.everp.ui.theme.EverpTheme
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.delay

@AndroidEntryPoint
class MainActivity : ComponentActivity() {
Expand All @@ -20,12 +28,28 @@ class MainActivity : ComponentActivity() {
enableEdgeToEdge()
setContent {
EverpTheme {
MainScreen()
Surface(modifier = Modifier.fillMaxSize()) {
AppNavGraph()
}
AppContent()
}
}
}
}

@Composable
private fun AppContent() {
var showSplash by remember { mutableStateOf(true) }

LaunchedEffect(Unit) {
delay(2000) // 2초간 SplashScreen 표시
showSplash = false
}

Box(modifier = Modifier.fillMaxSize()) {
if (showSplash) {
SplashScreen()
} else {
Surface(modifier = Modifier.fillMaxSize()) {
AppNavGraph()
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ interface FcmRemoteDataSource {
request: InvoiceUpdateRequestDto,
): Result<Unit>

suspend fun requestReceivable(
suspend fun updateCustomerInvoiceStatus(
invoiceId: String,
): Result<Unit>

Expand All @@ -50,7 +50,7 @@ interface FcmRemoteDataSource {
request: InvoiceUpdateRequestDto,
): Result<Unit>

suspend fun completeReceivable(
suspend fun updateSupplierInvoiceStatus(
invoiceId: String,
): Result<Unit>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,16 +63,14 @@ class FcmHttpRemoteDataSourceImpl @Inject constructor(
}

/**
* 공급사(Supplier)
* 매입 전표 상태 수정
* 확인 요청 -> 완납
* 매입 전표 수정
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

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

Documentation comment is vague and incomplete. It should specify what type of updates are supported and match the function signature which includes an InvoiceUpdateRequestDto parameter.

Suggested change
* 매입 전표 수정
* Updates an AP invoice (매입 전표) with the specified changes.
*
* @param invoiceId The ID of the AP invoice to update.
* @param request The update request containing the fields to be modified. Supported updates include changes to invoice details such as amount, date, vendor information, and other editable fields defined in [InvoiceUpdateRequestDto].
* @return [Result] indicating success or failure of the update operation.

Copilot uses AI. Check for mistakes.
*/
override suspend fun updateApInvoice(
invoiceId: String,
request: InvoiceUpdateRequestDto,
): Result<Unit> = withContext(Dispatchers.IO) {
try {
val response = fcmApi.updateApInvoice(invoiceId)
val response = fcmApi.updateApInvoice(invoiceId, request)
if (response.success) {
Result.success(Unit)
} else {
Expand All @@ -84,22 +82,6 @@ class FcmHttpRemoteDataSourceImpl @Inject constructor(
}
}

override suspend fun requestReceivable(
invoiceId: String,
): Result<Unit> = withContext(Dispatchers.IO) {
try {
val response = fcmApi.requestReceivable(invoiceId)
if (response.success) {
Result.success(Unit)
} else {
Result.failure(Exception(response.message ?: "수취 요청 실패"))
}
} catch (e: Exception) {
Timber.e(e, "수취 요청 실패")
Result.failure(e)
}
}

// ========== AR 인보이스 (매출) ==========
override suspend fun getArInvoiceList(
// companyName: String?,
Expand Down Expand Up @@ -143,10 +125,9 @@ class FcmHttpRemoteDataSourceImpl @Inject constructor(
}
}


/**
* 고객사(Customer)
* 매출 전표 상태 수정
* 미납 -> 확인 요청
* 매출 전표 수정
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

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

Documentation comment is vague and incomplete. It should specify what type of updates are supported and match the function signature which includes an InvoiceUpdateRequestDto parameter.

Suggested change
* 매출 전표 수정
* 매출 전표(AR Invoice)를 수정합니다.
*
* @param invoiceId 수정할 매출 전표의 고유 ID입니다.
* @param request 매출 전표의 수정 내용을 담은 [InvoiceUpdateRequestDto] 객체입니다.
* 예를 들어, 금액, 날짜, 상태 등 매출 전표의 주요 필드를 수정할 수 있습니다.
* @return 성공 시 [Result.success(Unit)], 실패 시 [Result.failure]를 반환합니다.

Copilot uses AI. Check for mistakes.
*/
override suspend fun updateArInvoice(
invoiceId: String,
Expand All @@ -165,9 +146,37 @@ class FcmHttpRemoteDataSourceImpl @Inject constructor(
}
}

override suspend fun completeReceivable(invoiceId: String): Result<Unit> {
// ========== 인보이스 상태 수정 ==========

/**
* 고객사(Customer)
* 매출 전표 상태 수정
* 미납 -> 확인 요청
*/
override suspend fun updateCustomerInvoiceStatus(
invoiceId: String,
): Result<Unit> = withContext(Dispatchers.IO) {
try {
val response = fcmApi.updateCustomerInvoiceStatus(invoiceId)
if (response.success) {
Result.success(Unit)
} else {
Result.failure(Exception(response.message))
}
} catch (e: Exception) {
Timber.e(e, "수취 요청 실패")
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

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

Error message "수취 요청 실패" (receivable request failed) is misleading. This function updates customer invoice status, so the error message should reflect that, e.g., "고객사 인보이스 상태 업데이트 실패" (customer invoice status update failed).

Suggested change
Timber.e(e, "수취 요청 실패")
Timber.e(e, "고객사 인보이스 상태 업데이트 실패")

Copilot uses AI. Check for mistakes.
Result.failure(e)
}
}

/**
* 공급사(Supplier)
* 매입 전표 상태 수정
* 확인 요청 -> 완납
*/
override suspend fun updateSupplierInvoiceStatus(invoiceId: String): Result<Unit> {
return try {
val response = fcmApi.completeReceivable(invoiceId)
val response = fcmApi.updateSupplierInvoiceStatus(invoiceId)
if (response.success) {
Result.success(Unit)
} else {
Comment on lines +177 to 182
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

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

Error message "완납 처리 실패" (payment completion failed) is misleading. This function updates supplier invoice status, so the error message should be more specific, e.g., "공급사 인보이스 상태 업데이트 실패" (supplier invoice status update failed).

Copilot uses AI. Check for mistakes.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,12 @@ interface FcmApi {
): ApiResponse<InvoiceDetailResponseDto>

/**
* 공급사(Supplier)
* 매입 전표 상태 수정
* 확인 요청 -> 완납
*
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

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

Empty documentation comment. Either provide meaningful documentation for this endpoint or remove the empty comment block.

Suggested change
*
* 매입 인보이스(AP) 상세 정보 수정

Copilot uses AI. Check for mistakes.
*/
@PATCH("$BASE_URL/invoice/ap/{invoiceId}")
suspend fun updateApInvoice(
@Path("invoiceId") invoiceId: String,
// @Body request: InvoiceUpdateRequestDto,
@Body request: InvoiceUpdateRequestDto,
): ApiResponse<Any>

/**
Expand Down Expand Up @@ -86,6 +84,24 @@ interface FcmApi {
@Path("invoiceId") invoiceId: String,
): ApiResponse<Any>

/**
* 공급사(Supplier)
* 매출 전표 상태 수정(확인 요청 -> 완료)
*/
@POST("$BASE_URL/invoice/ap/{invoiceId}/payable/complete")
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

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

Incorrect API endpoint path. The comment states this is for "공급사(Supplier) 매출 전표 상태 수정" (Supplier AR invoice status update), but the endpoint path uses /invoice/ap/ (AP = Accounts Payable/매입). It should be /invoice/ar/ (AR = Accounts Receivable/매출) to match the documented purpose.

Suggested change
@POST("$BASE_URL/invoice/ap/{invoiceId}/payable/complete")
@POST("$BASE_URL/invoice/ar/{invoiceId}/payable/complete")

Copilot uses AI. Check for mistakes.
suspend fun updateSupplierInvoiceStatus(
@Path("invoiceId") invoiceId: String,
): ApiResponse<Unit>

/**
* 고객사(Customer)
* 매입 전표 상태 수정(미납 -> 확인 요청)
*/
@POST("$BASE_URL/invoice/ap/receivable/request")
suspend fun updateCustomerInvoiceStatus(
@Query("invoiceId") invoiceId: String,
): ApiResponse<Unit>

companion object {
private const val BASE_URL = "business/fcm"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ data class QuotationDetailResponseDto(
val quotationDate: LocalDate,
@Serializable(with = LocalDateSerializer::class)
@SerialName("dueDate")
val dueDate: LocalDate,
val dueDate: LocalDate? = null,
@SerialName("statusCode")
val statusCode: QuotationStatusEnum,
@SerialName("customerName")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ class FcmRepositoryImpl @Inject constructor(
}
}

override suspend fun requestReceivable(invoiceId: String): Result<Unit> {
return fcmFinanceRemoteDataSource.requestReceivable(invoiceId)
override suspend fun updateCustomerInvoiceStatus(invoiceId: String): Result<Unit> {
return fcmFinanceRemoteDataSource.updateCustomerInvoiceStatus(invoiceId)
}

// ========== AR 인보이스 (매출) ==========
Expand Down Expand Up @@ -135,8 +135,8 @@ class FcmRepositoryImpl @Inject constructor(
}
}

override suspend fun completeReceivable(invoiceId: String): Result<Unit> {
return fcmFinanceRemoteDataSource.completeReceivable(invoiceId)
override suspend fun updateSupplierInvoiceStatus(invoiceId: String): Result<Unit> {
return fcmFinanceRemoteDataSource.updateSupplierInvoiceStatus(invoiceId)
.onSuccess {
// 완료 성공 시 로컬 캐시 갱신
refreshArInvoiceDetail(invoiceId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ data class QuotationDetail(
val id: String, // 견적서 ID
val number: String, // 견적서 코드
val issueDate: LocalDate, // 발행일, 견적일자
val dueDate: LocalDate, // 납기일
val dueDate: LocalDate? = null, // 납기일
val status: QuotationStatusEnum, // 상태 값은 Enum으로 따로 관리
val totalAmount: Long, // 총 금액
val customer: QuotationDetailCustomer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ interface FcmRepository {
suspend fun getApInvoiceDetail(invoiceId: String): Result<InvoiceDetail>

suspend fun updateApInvoice(invoiceId: String, request: InvoiceUpdateRequestDto): Result<Unit>
suspend fun requestReceivable(invoiceId: String): Result<Unit>
suspend fun updateCustomerInvoiceStatus(invoiceId: String): Result<Unit>

// ========== AR 인보이스 (매출) ==========
fun observeArInvoiceList(): Flow<PageResponse<InvoiceListItem>>
Expand All @@ -33,6 +33,6 @@ interface FcmRepository {
suspend fun getArInvoiceDetail(invoiceId: String): Result<InvoiceDetail>

suspend fun updateArInvoice(invoiceId: String, request: InvoiceUpdateRequestDto): Result<Unit>
suspend fun completeReceivable(invoiceId: String): Result<Unit>
suspend fun updateSupplierInvoiceStatus(invoiceId: String): Result<Unit>
}

Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Person
import androidx.compose.material.icons.outlined.Logout
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Icon
Expand Down Expand Up @@ -180,7 +182,19 @@ fun CustomerProfileScreen(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 24.dp),
) { }
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.error,
contentColor = MaterialTheme.colorScheme.onError,
),
shape = MaterialTheme.shapes.large,
) {
Icon(
imageVector = Icons.Outlined.Logout,
contentDescription = "로그아웃",
)
androidx.compose.foundation.layout.Spacer(modifier = androidx.compose.ui.Modifier.size(8.dp))
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

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

Inconsistent use of fully qualified class names. Use simple names with proper imports for Spacer and Modifier instead of androidx.compose.foundation.layout.Spacer and androidx.compose.ui.Modifier.

Suggested change
androidx.compose.foundation.layout.Spacer(modifier = androidx.compose.ui.Modifier.size(8.dp))
Spacer(modifier = Modifier.size(8.dp))

Copilot uses AI. Check for mistakes.
Text(text = "로그아웃")
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,7 @@ import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavController
import com.autoever.everp.ui.common.components.StatusBadge
import com.autoever.everp.utils.state.UiResult
import java.text.NumberFormat
import java.time.format.DateTimeFormatter
import java.util.Locale

@Composable
fun InvoiceDetailScreen(
Expand Down Expand Up @@ -261,10 +259,11 @@ fun InvoiceDetailScreen(
}

// 납부 확인 요청 버튼 (UNPAID 상태일 때만 표시)
// if (detail.status == InvoiceStatusEnum.UNPAID) {
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

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

Remove commented-out code. If the condition is no longer needed, delete the comment entirely to keep the codebase clean.

Suggested change
// if (detail.status == InvoiceStatusEnum.UNPAID) {

Copilot uses AI. Check for mistakes.
if (!isAp && detail.status == InvoiceStatusEnum.UNPAID) {
Spacer(modifier = Modifier.height(16.dp))
Button(
onClick = { viewModel.requestReceivable(invoiceId) },
onClick = { viewModel.updateCustomerInvoiceStatus(invoiceId) },
modifier = Modifier.fillMaxWidth(),
) {
Text("납부 확인 요청")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ class InvoiceDetailViewModel @Inject constructor(
loadInvoiceDetail(invoiceId, isAp)
}

fun requestReceivable(invoiceId: String) {
fun updateCustomerInvoiceStatus(invoiceId: String) {
viewModelScope.launch {
_requestResult.value = fcmRepository.requestReceivable(invoiceId)
_requestResult.value = fcmRepository.updateCustomerInvoiceStatus(invoiceId)
.onSuccess {
// 성공 시 상세 정보 다시 로드
loadInvoiceDetail(invoiceId, false) // AR 인보이스
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
import com.autoever.everp.domain.model.notification.Notification
import com.autoever.everp.domain.model.notification.NotificationLinkEnum
import com.autoever.everp.domain.model.notification.NotificationSourceEnum
import com.autoever.everp.ui.common.components.StatusBadge
import com.autoever.everp.ui.supplier.SupplierSubNavigationItem
import java.time.Duration
Expand Down Expand Up @@ -172,10 +173,12 @@ private fun NotificationItem(
fontWeight = if (notification.isRead) FontWeight.Normal else FontWeight.Bold,
)

StatusBadge(
text = notification.source.toKorean(),
color = notification.source.toColor(),
)
if (notification.source != NotificationSourceEnum.UNKNOWN) {
StatusBadge(
text = notification.source.toKorean(),
color = notification.source.toColor(),
)
}
}

Text(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ fun QuotationDetailScreen(
)
DetailRow(
label = "납기일자",
value = detail.dueDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")),
value = detail.dueDate?.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")) ?: "-",
)
DetailRow(
label = "총 금액",
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/com/autoever/everp/ui/main/MainScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ fun MainScreen(
popUpTo(Routes.HOME) { inclusive = true }
launchSingleTop = true
}
} else {
homeVm.refreshUserIfAuthenticated()
}
}
.collect()
Expand Down
Loading
Loading