diff --git a/app/src/main/java/com/sampoom/android/core/ui/component/OrderItem.kt b/app/src/main/java/com/sampoom/android/core/ui/component/OrderItem.kt deleted file mode 100644 index e971cc9..0000000 --- a/app/src/main/java/com/sampoom/android/core/ui/component/OrderItem.kt +++ /dev/null @@ -1,72 +0,0 @@ -package com.sampoom.android.core.ui.component - -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width -import androidx.compose.material3.Card -import androidx.compose.material3.CardDefaults -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import com.sampoom.android.R -import com.sampoom.android.core.ui.theme.backgroundCardColor -import com.sampoom.android.core.ui.theme.textSecondaryColor -import com.sampoom.android.core.util.buildOrderTitle -import com.sampoom.android.core.util.formatDate -import com.sampoom.android.feature.order.domain.model.Order - -@Composable -fun OrderItem( - order: Order, - onClick: () -> Unit -) { - Card( - onClick = { onClick() }, - modifier = Modifier.fillMaxWidth(), - colors = CardDefaults.cardColors(containerColor = backgroundCardColor()) - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Column( - modifier = Modifier - .weight(1f) - ) { - Text( - text = buildOrderTitle(order), - style = MaterialTheme.typography.bodyMedium, - maxLines = 1 - ) - Spacer(Modifier.height(4.dp)) - Text( - text = order.agencyName ?: stringResource(R.string.common_slash), - style = MaterialTheme.typography.labelMedium, - color = textSecondaryColor() - ) - } - - Spacer(Modifier.width(12.dp)) - - Column(horizontalAlignment = Alignment.End) { - Text( - text = order.createdAt?.let { formatDate(it) } ?: stringResource(R.string.common_slash), - style = MaterialTheme.typography.labelMedium, - color = textSecondaryColor() - ) - Spacer(Modifier.height(6.dp)) - StatusChip(status = order.status) - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/core/util/FormatPrice.kt b/app/src/main/java/com/sampoom/android/core/util/FormatPrice.kt new file mode 100644 index 0000000..91adf0f --- /dev/null +++ b/app/src/main/java/com/sampoom/android/core/util/FormatPrice.kt @@ -0,0 +1,7 @@ +package com.sampoom.android.core.util + +import java.text.NumberFormat +import java.util.Locale + +fun formatWon(value: Long): String = + NumberFormat.getInstance(Locale.KOREA).format(value) + "원" \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/auth/data/mapper/AuthMappers.kt b/app/src/main/java/com/sampoom/android/feature/auth/data/mapper/AuthMappers.kt index a6d9427..206707c 100644 --- a/app/src/main/java/com/sampoom/android/feature/auth/data/mapper/AuthMappers.kt +++ b/app/src/main/java/com/sampoom/android/feature/auth/data/mapper/AuthMappers.kt @@ -2,8 +2,10 @@ package com.sampoom.android.feature.auth.data.mapper import com.sampoom.android.core.model.UserPosition import com.sampoom.android.feature.auth.data.remote.dto.GetProfileResponseDto +import com.sampoom.android.feature.auth.data.remote.dto.GetVendorsResponseDto import com.sampoom.android.feature.auth.data.remote.dto.LoginResponseDto import com.sampoom.android.feature.auth.domain.model.User +import com.sampoom.android.feature.auth.domain.model.Vendor fun LoginResponseDto.toModel(): User = User( userId = userId, @@ -37,15 +39,18 @@ fun GetProfileResponseDto.toModel(): User = User( endedAt = endedAt ) -fun User.mergeWith(profile: User): User = this.copy( - userName = profile.userName, - position = profile.position, - workspace = profile.workspace, - branch = profile.branch -) - private fun String.toUserPosition(): UserPosition = try { UserPosition.valueOf(this.uppercase()) } catch (_: IllegalArgumentException) { UserPosition.STAFF -} \ No newline at end of file +} + +fun GetVendorsResponseDto.toModel(): Vendor = Vendor( + id = id, + vendorCode = vendorCode, + name = name, + businessNumber = businessNumber, + ceoName = ceoName, + address = address, + status = status +) \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/auth/data/remote/api/AuthApi.kt b/app/src/main/java/com/sampoom/android/feature/auth/data/remote/api/AuthApi.kt index 7f70e50..fbbf1fb 100644 --- a/app/src/main/java/com/sampoom/android/feature/auth/data/remote/api/AuthApi.kt +++ b/app/src/main/java/com/sampoom/android/feature/auth/data/remote/api/AuthApi.kt @@ -11,6 +11,7 @@ import com.sampoom.android.feature.auth.data.remote.dto.RefreshResponseDto import com.sampoom.android.feature.auth.data.remote.dto.UpdateProfileRequestDto import com.sampoom.android.feature.auth.data.remote.dto.UpdateProfileResponseDto import com.sampoom.android.feature.auth.data.remote.dto.GetProfileResponseDto +import com.sampoom.android.feature.auth.data.remote.dto.GetVendorsResponseDto import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.Headers @@ -38,4 +39,7 @@ interface AuthApi { @PATCH("user/profile") suspend fun updateProfile(@Body body: UpdateProfileRequestDto): ApiResponse + + @GET("site/vendors") + suspend fun getVendors(): ApiResponse> } \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/auth/data/remote/dto/GetVendorsResponseDto.kt b/app/src/main/java/com/sampoom/android/feature/auth/data/remote/dto/GetVendorsResponseDto.kt new file mode 100644 index 0000000..46e260f --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/auth/data/remote/dto/GetVendorsResponseDto.kt @@ -0,0 +1,11 @@ +package com.sampoom.android.feature.auth.data.remote.dto + +data class GetVendorsResponseDto( + val id: Long, + val vendorCode: String, + val name: String, + val businessNumber: String, + val ceoName: String, + val address: String, + val status: String +) diff --git a/app/src/main/java/com/sampoom/android/feature/auth/data/repository/AuthRepositoryImpl.kt b/app/src/main/java/com/sampoom/android/feature/auth/data/repository/AuthRepositoryImpl.kt index 5273dce..cdc8642 100644 --- a/app/src/main/java/com/sampoom/android/feature/auth/data/repository/AuthRepositoryImpl.kt +++ b/app/src/main/java/com/sampoom/android/feature/auth/data/repository/AuthRepositoryImpl.kt @@ -8,7 +8,10 @@ import com.sampoom.android.feature.auth.data.remote.dto.LoginRequestDto import com.sampoom.android.feature.auth.data.remote.dto.RefreshRequestDto import com.sampoom.android.feature.auth.data.remote.dto.SignUpRequestDto import com.sampoom.android.feature.auth.domain.model.User +import com.sampoom.android.feature.auth.domain.model.VendorList import com.sampoom.android.feature.auth.domain.repository.AuthRepository +import com.sampoom.android.feature.outbound.data.mapper.toModel +import kotlinx.coroutines.delay import javax.inject.Inject class AuthRepositoryImpl @Inject constructor( @@ -36,7 +39,11 @@ class AuthRepositoryImpl @Inject constructor( ) ) if (!signUpRes.success) throw Exception(signUpRes.message) - signIn(email, password).getOrThrow() + + delay(500) + retry(times = 5, initialDelay = 500) { + signIn(email, password).getOrThrow() + } } } @@ -126,4 +133,13 @@ class AuthRepositoryImpl @Inject constructor( dto.data.toModel() } } + + override suspend fun getVendorList(): Result { + return runCatching { + val dto = api.getVendors() + if (!dto.success) throw Exception(dto.message) + val vendorItems = dto.data.map { it.toModel() } + VendorList(items = vendorItems) + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/auth/domain/model/Vendor.kt b/app/src/main/java/com/sampoom/android/feature/auth/domain/model/Vendor.kt new file mode 100644 index 0000000..2b47188 --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/auth/domain/model/Vendor.kt @@ -0,0 +1,11 @@ +package com.sampoom.android.feature.auth.domain.model + +data class Vendor( + val id: Long, + val vendorCode: String, + val name: String, + val businessNumber: String, + val ceoName: String, + val address: String, + val status: String +) diff --git a/app/src/main/java/com/sampoom/android/feature/auth/domain/model/VendorList.kt b/app/src/main/java/com/sampoom/android/feature/auth/domain/model/VendorList.kt new file mode 100644 index 0000000..0f3431c --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/auth/domain/model/VendorList.kt @@ -0,0 +1,11 @@ +package com.sampoom.android.feature.auth.domain.model + +data class VendorList( + val items: List, + val totalCount: Int = items.size, + val isEmpty: Boolean = items.isEmpty() +) { + companion object Companion { + fun empty() = VendorList(emptyList()) + } +} diff --git a/app/src/main/java/com/sampoom/android/feature/auth/domain/repository/AuthRepository.kt b/app/src/main/java/com/sampoom/android/feature/auth/domain/repository/AuthRepository.kt index 866028a..7d9e833 100644 --- a/app/src/main/java/com/sampoom/android/feature/auth/domain/repository/AuthRepository.kt +++ b/app/src/main/java/com/sampoom/android/feature/auth/domain/repository/AuthRepository.kt @@ -1,6 +1,7 @@ package com.sampoom.android.feature.auth.domain.repository import com.sampoom.android.feature.auth.domain.model.User +import com.sampoom.android.feature.auth.domain.model.VendorList interface AuthRepository { suspend fun signUp( @@ -18,4 +19,5 @@ interface AuthRepository { suspend fun clearTokens(): Result suspend fun isSignedIn(): Boolean suspend fun getProfile(workspace: String): Result + suspend fun getVendorList(): Result } \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/auth/domain/usecase/GetVendorUseCase.kt b/app/src/main/java/com/sampoom/android/feature/auth/domain/usecase/GetVendorUseCase.kt new file mode 100644 index 0000000..e41b1f1 --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/auth/domain/usecase/GetVendorUseCase.kt @@ -0,0 +1,11 @@ +package com.sampoom.android.feature.auth.domain.usecase + +import com.sampoom.android.feature.auth.domain.model.VendorList +import com.sampoom.android.feature.auth.domain.repository.AuthRepository +import javax.inject.Inject + +class GetVendorUseCase @Inject constructor( + private val repository: AuthRepository +) { + suspend operator fun invoke(): Result = repository.getVendorList() +} \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpScreen.kt b/app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpScreen.kt index 9ab5c3d..9ead5b9 100644 --- a/app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpScreen.kt +++ b/app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpScreen.kt @@ -82,6 +82,7 @@ fun SignUpScreen( val positionItems = remember { UserPosition.entries } var positionMenuExpanded by remember { mutableStateOf(false) } + var vendorMenuExpanded by remember { mutableStateOf(false) } Scaffold( topBar = { @@ -144,16 +145,50 @@ fun SignUpScreen( text = stringResource(R.string.signup_title_branch), fontSize = labelTextSize ) - CommonTextField( - modifier = Modifier.fillMaxWidth().focusRequester(branchFocus), - value = state.branch, - onValueChange = { viewModel.onEvent(SignUpUiEvent.BranchChanged(it)) }, - placeholder = stringResource(R.string.signup_placeholder_branch), - isError = state.branchError != null, - errorMessage = state.branchError, - imeAction = ImeAction.Next, - keyboardActions = KeyboardActions(onNext = { positionFocus.requestFocus() }) - ) + ExposedDropdownMenuBox( + expanded = vendorMenuExpanded, + onExpandedChange = { vendorMenuExpanded = it && !state.vendorsLoading } + ) { + CommonTextField( + modifier = Modifier + .menuAnchor( + type = ExposedDropdownMenuAnchorType.PrimaryNotEditable, + enabled = true + ) + .fillMaxWidth() + .focusRequester(branchFocus), + readOnly = true, + value = state.selectedVendor?.name ?: "", + onValueChange = {}, + placeholder = stringResource(R.string.signup_placeholder_branch), + isError = state.branchError != null, + errorMessage = state.branchError, + singleLine = true, + enabled = !state.vendorsLoading + ) + ExposedDropdownMenu( + expanded = vendorMenuExpanded, + onDismissRequest = { vendorMenuExpanded = false } + ) { + if (state.vendorsLoading) { + DropdownMenuItem( + text = { Text("로딩 중...") }, + onClick = {} + ) + } else { + state.vendors.forEach { vendor -> + DropdownMenuItem( + text = { Text(vendor.name) }, + onClick = { + viewModel.onEvent(SignUpUiEvent.VendorChanged(vendor)) + vendorMenuExpanded = false + positionFocus.requestFocus() + } + ) + } + } + } + } Spacer(Modifier.height(8.dp)) Text( text = stringResource(R.string.signup_title_position), diff --git a/app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpUiEvent.kt b/app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpUiEvent.kt index dda938b..90bfe1d 100644 --- a/app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpUiEvent.kt +++ b/app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpUiEvent.kt @@ -1,10 +1,12 @@ package com.sampoom.android.feature.auth.ui import com.sampoom.android.core.model.UserPosition +import com.sampoom.android.feature.auth.domain.model.Vendor sealed interface SignUpUiEvent { data class NameChanged(val name: String) : SignUpUiEvent - data class BranchChanged(val branch: String) : SignUpUiEvent +// data class BranchChanged(val branch: String) : SignUpUiEvent + data class VendorChanged(val vendor: Vendor) : SignUpUiEvent data class PositionChanged(val position: UserPosition) : SignUpUiEvent data class EmailChanged(val email: String) : SignUpUiEvent data class PasswordChanged(val password: String) : SignUpUiEvent diff --git a/app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpUiState.kt b/app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpUiState.kt index f8c7383..25a1814 100644 --- a/app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpUiState.kt +++ b/app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpUiState.kt @@ -1,6 +1,7 @@ package com.sampoom.android.feature.auth.ui import com.sampoom.android.core.model.UserPosition +import com.sampoom.android.feature.auth.domain.model.Vendor data class SignUpUiState( val name: String = "", @@ -11,6 +12,11 @@ data class SignUpUiState( val password: String = "", val passwordCheck: String = "", + // Vendor + val vendors: List = emptyList(), + val selectedVendor: Vendor? = null, + val vendorsLoading: Boolean = false, + // Error message val nameError: String? = null, val branchError: String? = null, diff --git a/app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpViewModel.kt b/app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpViewModel.kt index 50d5633..a448e7b 100644 --- a/app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpViewModel.kt +++ b/app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpViewModel.kt @@ -12,6 +12,7 @@ import com.sampoom.android.feature.auth.domain.AuthValidator.validateEmail import com.sampoom.android.feature.auth.domain.AuthValidator.validatePassword import com.sampoom.android.feature.auth.domain.AuthValidator.validatePasswordCheck import com.sampoom.android.feature.auth.domain.ValidationResult +import com.sampoom.android.feature.auth.domain.usecase.GetVendorUseCase import com.sampoom.android.feature.auth.domain.usecase.SignUpUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow @@ -24,6 +25,7 @@ import javax.inject.Inject class SignUpViewModel @Inject constructor( private val messageHandler: GlobalMessageHandler, private val singUp: SignUpUseCase, + private val getVendorUseCase: GetVendorUseCase, private val application: Application ) : ViewModel() { @@ -39,6 +41,10 @@ class SignUpViewModel @Inject constructor( private var positionLabel: String = "" private var errorLabel: String = "" + init { + loadVendors() + } + fun bindLabels(name: String, branch: String, position: String, error: String) { nameLabel = name branchLabel = branch @@ -51,8 +57,15 @@ class SignUpViewModel @Inject constructor( _state.value = _state.value.copy(name = e.name) validateName() } - is SignUpUiEvent.BranchChanged -> { - _state.value = _state.value.copy(branch = e.branch) +// is SignUpUiEvent.BranchChanged -> { +// _state.value = _state.value.copy(branch = e.branch) +// validateBranch() +// } + is SignUpUiEvent.VendorChanged -> { + _state.value = _state.value.copy( + selectedVendor = e.vendor, + branch = e.vendor.name + ) validateBranch() } is SignUpUiEvent.PositionChanged -> { @@ -169,4 +182,25 @@ class SignUpViewModel @Inject constructor( } Log.d(TAG, "submit: ${_state.value}") } + + private fun loadVendors() { + viewModelScope.launch { + _state.update { it.copy(vendorsLoading = true) } + getVendorUseCase() + .onSuccess { vendorList -> + _state.update { + it.copy( + vendors = vendorList.items, + vendorsLoading = false + ) + } + } + .onFailure { throwable -> + val backendMessage = throwable.serverMessageOrNull() + val error = backendMessage ?: (throwable.message ?: errorLabel) + messageHandler.showMessage(message = error, isError = true) + _state.update { it.copy(vendorsLoading = false) } + } + } + } } diff --git a/app/src/main/java/com/sampoom/android/feature/cart/data/mapper/CartMappers.kt b/app/src/main/java/com/sampoom/android/feature/cart/data/mapper/CartMappers.kt index e6729c3..9f59d29 100644 --- a/app/src/main/java/com/sampoom/android/feature/cart/data/mapper/CartMappers.kt +++ b/app/src/main/java/com/sampoom/android/feature/cart/data/mapper/CartMappers.kt @@ -9,4 +9,4 @@ import com.sampoom.android.feature.cart.domain.model.CartPart fun CartDto.toModel(): Cart = Cart(categoryId, categoryName, groups.map { it.toModel() }) fun CartGroupDto.toModel(): CartGroup = CartGroup(groupId, groupName, parts.map { it.toModel() }) -fun CartPartDto.toModel(): CartPart = CartPart(cartItemId, partId, code, name, quantity) \ No newline at end of file +fun CartPartDto.toModel(): CartPart = CartPart(cartItemId, partId, code, name, quantity, standardCost) \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/cart/data/remote/dto/CartDto.kt b/app/src/main/java/com/sampoom/android/feature/cart/data/remote/dto/CartDto.kt index 6f4b211..d144195 100644 --- a/app/src/main/java/com/sampoom/android/feature/cart/data/remote/dto/CartDto.kt +++ b/app/src/main/java/com/sampoom/android/feature/cart/data/remote/dto/CartDto.kt @@ -17,5 +17,6 @@ data class CartPartDto( val partId: Long, val code: String, val name: String, - val quantity: Long + val quantity: Long, + val standardCost: Long ) \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/cart/domain/model/Cart.kt b/app/src/main/java/com/sampoom/android/feature/cart/domain/model/Cart.kt index 1caf613..41a5a32 100644 --- a/app/src/main/java/com/sampoom/android/feature/cart/domain/model/Cart.kt +++ b/app/src/main/java/com/sampoom/android/feature/cart/domain/model/Cart.kt @@ -17,5 +17,10 @@ data class CartPart( val partId: Long, val code: String, val name: String, - val quantity: Long -) \ No newline at end of file + val quantity: Long, + val standardCost: Long +) + +// 품목 합계(단가 x 수량) +val CartPart.subtotal: Long + get() = standardCost * quantity \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/cart/ui/CartListScreen.kt b/app/src/main/java/com/sampoom/android/feature/cart/ui/CartListScreen.kt index 28acaaf..d17be0e 100644 --- a/app/src/main/java/com/sampoom/android/feature/cart/ui/CartListScreen.kt +++ b/app/src/main/java/com/sampoom/android/feature/cart/ui/CartListScreen.kt @@ -46,7 +46,9 @@ import com.sampoom.android.core.ui.theme.FailRed import com.sampoom.android.core.ui.theme.backgroundCardColor import com.sampoom.android.core.ui.theme.textColor import com.sampoom.android.core.ui.theme.textSecondaryColor +import com.sampoom.android.core.util.formatWon import com.sampoom.android.feature.cart.domain.model.CartPart +import com.sampoom.android.feature.cart.domain.model.subtotal import com.sampoom.android.feature.order.ui.OrderResultBottomSheet @Composable @@ -68,7 +70,7 @@ fun CartListScreen( uiState.processedOrder?.let { order -> OrderResultBottomSheet( order = order, - onDismiss = { viewModel.onEvent(CartListUiEvent.DismissOrderResult)} + onDismiss = { viewModel.onEvent(CartListUiEvent.DismissOrderResult) } ) } @@ -87,7 +89,9 @@ fun CartListScreen( ) } ) { - Column(Modifier.fillMaxSize().padding(paddingValues)) { + Column(Modifier + .fillMaxSize() + .padding(paddingValues)) { Row( modifier = Modifier .fillMaxWidth() @@ -192,7 +196,7 @@ fun CartListScreen( variant = ButtonVariant.Primary, size = ButtonSize.Large, onClick = { showConfirmDialog = true } - ) { Text(stringResource(R.string.cart_order_parts)) } + ) { Text("${formatWon(uiState.totalCost)} ${stringResource(R.string.cart_order_parts)}") } } } } @@ -364,30 +368,6 @@ private fun CartPartItem( style = MaterialTheme.typography.titleMedium, modifier = Modifier.padding(horizontal = 24.dp) ) -// OutlinedTextField( -// value = part.quantity.toString(), -// onValueChange = { newValue -> -// when { -// newValue.isEmpty() -> onEvent(OutboundListUiEvent.UpdateQuantity(part.outboundId, 1)) -// newValue == "0" -> onEvent(OutboundListUiEvent.UpdateQuantity(part.outboundId, 1)) -// else -> { -// val newQuantity = newValue.toLongOrNull() -// if (newQuantity != null && newQuantity > 0) onEvent(OutboundListUiEvent.UpdateQuantity(part.outboundId, newQuantity)) -// else onEvent(OutboundListUiEvent.UpdateQuantity(part.outboundId, 1)) -// } -// } -// }, -// modifier = Modifier.width(100.dp), -// singleLine = true, -// keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), -// textStyle = MaterialTheme.typography.titleMedium.copy(textAlign = TextAlign.Center), -// colors = OutlinedTextFieldDefaults.colors( -// focusedBorderColor = Color.Transparent, -// unfocusedBorderColor = Color.Transparent, -// disabledBorderColor = Color.Transparent, -// errorBorderColor = Color.Transparent -// ) -// ) CommonButton( variant = ButtonVariant.Neutral, size = ButtonSize.Large, @@ -408,6 +388,21 @@ private fun CartPartItem( } } } + + val subtotal = part.subtotal + + Spacer(Modifier.height(8.dp)) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = formatWon(subtotal), + style = MaterialTheme.typography.titleLarge + ) + } } } } \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/cart/ui/CartListUiState.kt b/app/src/main/java/com/sampoom/android/feature/cart/ui/CartListUiState.kt index 37e10e5..ade7798 100644 --- a/app/src/main/java/com/sampoom/android/feature/cart/ui/CartListUiState.kt +++ b/app/src/main/java/com/sampoom/android/feature/cart/ui/CartListUiState.kt @@ -15,4 +15,13 @@ data class CartListUiState( val isProcessing: Boolean = false, val processError: String? = null, val processedOrder: Order? = null -) +) { + val totalCost: Long + get() = cartList.sumOf { category -> + category.groups.sumOf { group -> + group.parts.sumOf { part -> + part.standardCost * part.quantity + } + } + } +} diff --git a/app/src/main/java/com/sampoom/android/feature/dashboard/ui/DashboardScreen.kt b/app/src/main/java/com/sampoom/android/feature/dashboard/ui/DashboardScreen.kt index cc5eef5..096f645 100644 --- a/app/src/main/java/com/sampoom/android/feature/dashboard/ui/DashboardScreen.kt +++ b/app/src/main/java/com/sampoom/android/feature/dashboard/ui/DashboardScreen.kt @@ -50,7 +50,7 @@ import com.sampoom.android.R import com.sampoom.android.core.model.UserPosition import com.sampoom.android.core.ui.component.EmptyContent import com.sampoom.android.core.ui.component.ErrorContent -import com.sampoom.android.core.ui.component.OrderItem +import com.sampoom.android.feature.order.ui.OrderItem import com.sampoom.android.core.ui.theme.Main500 import com.sampoom.android.core.ui.theme.backgroundCardColor import com.sampoom.android.core.ui.theme.textColor diff --git a/app/src/main/java/com/sampoom/android/feature/order/data/mapper/OrderMappers.kt b/app/src/main/java/com/sampoom/android/feature/order/data/mapper/OrderMappers.kt index fd25e78..e2d37c9 100644 --- a/app/src/main/java/com/sampoom/android/feature/order/data/mapper/OrderMappers.kt +++ b/app/src/main/java/com/sampoom/android/feature/order/data/mapper/OrderMappers.kt @@ -12,4 +12,4 @@ import com.sampoom.android.feature.order.domain.model.OrderPart fun OrderDto.toModel(): Order = Order(orderId, orderNumber, createdAt, status, agencyName, items.map { it.toModel() }) fun OrderCategoryDto.toModel(): OrderCategory = OrderCategory(categoryId, categoryName, groups.map { it.toModel() }) fun OrderGroupDto.toModel(): OrderGroup = OrderGroup(groupId, groupName, parts.map { it.toModel() }) -fun OrderPartDto.toModel(): OrderPart = OrderPart(partId, code, name, quantity) \ No newline at end of file +fun OrderPartDto.toModel(): OrderPart = OrderPart(partId, code, name, quantity, standardCost) \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/order/data/remote/api/OrderApi.kt b/app/src/main/java/com/sampoom/android/feature/order/data/remote/api/OrderApi.kt index 7b77436..f168398 100644 --- a/app/src/main/java/com/sampoom/android/feature/order/data/remote/api/OrderApi.kt +++ b/app/src/main/java/com/sampoom/android/feature/order/data/remote/api/OrderApi.kt @@ -5,6 +5,7 @@ import com.sampoom.android.core.model.ApiSuccessResponse import com.sampoom.android.feature.order.data.remote.dto.OrderDto import com.sampoom.android.feature.order.data.remote.dto.OrderListDto import com.sampoom.android.feature.order.data.remote.dto.OrderRequestDto +import com.sampoom.android.feature.order.data.remote.dto.ReceiveStockRequestDto import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.PATCH @@ -30,10 +31,10 @@ interface OrderApi { suspend fun completeOrder(@Path("orderId") orderId: Long): ApiSuccessResponse // 주문 입고 처리 (대리점) - @PATCH("agency/{agencyId}/orders/{orderId}/receive") + @PATCH("agency/{agencyId}/stock") suspend fun receiveOrder( @Path("agencyId") agencyId: Long, - @Path("orderId") orderId: Long + @Body body: ReceiveStockRequestDto ): ApiSuccessResponse // 주문 상세 조회 diff --git a/app/src/main/java/com/sampoom/android/feature/order/data/remote/dto/OrderDto.kt b/app/src/main/java/com/sampoom/android/feature/order/data/remote/dto/OrderDto.kt index 50a9129..6fe631d 100644 --- a/app/src/main/java/com/sampoom/android/feature/order/data/remote/dto/OrderDto.kt +++ b/app/src/main/java/com/sampoom/android/feature/order/data/remote/dto/OrderDto.kt @@ -27,5 +27,6 @@ data class OrderPartDto( val partId: Long, val code: String, val name: String, - val quantity: Long + val quantity: Long, + val standardCost: Long ) \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/order/data/remote/dto/ReceiveStockItemDto.kt b/app/src/main/java/com/sampoom/android/feature/order/data/remote/dto/ReceiveStockItemDto.kt new file mode 100644 index 0000000..677fe9e --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/order/data/remote/dto/ReceiveStockItemDto.kt @@ -0,0 +1,6 @@ +package com.sampoom.android.feature.order.data.remote.dto + +data class ReceiveStockItemDto( + val partId: Long, + val quantity: Long +) diff --git a/app/src/main/java/com/sampoom/android/feature/order/data/remote/dto/ReceiveStockRequestDto.kt b/app/src/main/java/com/sampoom/android/feature/order/data/remote/dto/ReceiveStockRequestDto.kt new file mode 100644 index 0000000..4e658be --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/order/data/remote/dto/ReceiveStockRequestDto.kt @@ -0,0 +1,5 @@ +package com.sampoom.android.feature.order.data.remote.dto + +data class ReceiveStockRequestDto( + val items: List +) diff --git a/app/src/main/java/com/sampoom/android/feature/order/data/repository/OrderRepositoryImpl.kt b/app/src/main/java/com/sampoom/android/feature/order/data/repository/OrderRepositoryImpl.kt index 4d10f48..8190a7c 100644 --- a/app/src/main/java/com/sampoom/android/feature/order/data/repository/OrderRepositoryImpl.kt +++ b/app/src/main/java/com/sampoom/android/feature/order/data/repository/OrderRepositoryImpl.kt @@ -12,6 +12,8 @@ import com.sampoom.android.feature.order.data.remote.dto.OrderCategoryDto import com.sampoom.android.feature.order.data.remote.dto.OrderGroupDto import com.sampoom.android.feature.order.data.remote.dto.OrderPartDto import com.sampoom.android.feature.order.data.remote.dto.OrderRequestDto +import com.sampoom.android.feature.order.data.remote.dto.ReceiveStockItemDto +import com.sampoom.android.feature.order.data.remote.dto.ReceiveStockRequestDto import com.sampoom.android.feature.order.domain.model.Order import com.sampoom.android.feature.order.domain.repository.OrderRepository import kotlinx.coroutines.flow.Flow @@ -45,7 +47,8 @@ class OrderRepositoryImpl @Inject constructor( partId = part.partId, code = part.code, name = part.name, - quantity = part.quantity + quantity = part.quantity, + standardCost = part.standardCost ) } ) @@ -71,10 +74,15 @@ class OrderRepositoryImpl @Inject constructor( } } - override suspend fun receiveOrder(orderId: Long): Result { + override suspend fun receiveOrder(items: List>): Result { return runCatching { val agencyId = authPreferences.getStoredUser()?.agencyId ?: throw Exception() - val dto = api.receiveOrder(agencyId = agencyId, orderId = orderId) + val body = ReceiveStockRequestDto( + items = items.map { (partId, quantity) -> + ReceiveStockItemDto(partId = partId, quantity = quantity) + } + ) + val dto = api.receiveOrder(agencyId = agencyId, body = body) if (!dto.success) throw Exception(dto.message) } } diff --git a/app/src/main/java/com/sampoom/android/feature/order/domain/model/Order.kt b/app/src/main/java/com/sampoom/android/feature/order/domain/model/Order.kt index 90da7f8..5258756 100644 --- a/app/src/main/java/com/sampoom/android/feature/order/domain/model/Order.kt +++ b/app/src/main/java/com/sampoom/android/feature/order/domain/model/Order.kt @@ -25,5 +25,17 @@ data class OrderPart( val partId: Long, val code: String, val name: String, - val quantity: Long + val quantity: Long, + val standardCost: Long ) + +// 파일 하단에 추가 +val OrderPart.subtotal: Long + get() = standardCost * quantity + +val Order.totalCost: Long + get() = items.sumOf { category -> + category.groups.sumOf { group -> + group.parts.sumOf { it.subtotal } + } + } \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/order/domain/repository/OrderRepository.kt b/app/src/main/java/com/sampoom/android/feature/order/domain/repository/OrderRepository.kt index f103d37..3ea34cd 100644 --- a/app/src/main/java/com/sampoom/android/feature/order/domain/repository/OrderRepository.kt +++ b/app/src/main/java/com/sampoom/android/feature/order/domain/repository/OrderRepository.kt @@ -10,7 +10,7 @@ interface OrderRepository { fun getOrderList(): Flow> suspend fun createOrder(cartList: CartList): Result suspend fun completeOrder(orderId: Long): Result - suspend fun receiveOrder(orderId: Long): Result + suspend fun receiveOrder(items: List>): Result suspend fun getOrderDetail(orderId: Long): Result suspend fun cancelOrder(orderId: Long): Result } \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/order/domain/usecase/ReceiveOrderUseCase.kt b/app/src/main/java/com/sampoom/android/feature/order/domain/usecase/ReceiveOrderUseCase.kt index 5a05f03..c8829ce 100644 --- a/app/src/main/java/com/sampoom/android/feature/order/domain/usecase/ReceiveOrderUseCase.kt +++ b/app/src/main/java/com/sampoom/android/feature/order/domain/usecase/ReceiveOrderUseCase.kt @@ -6,5 +6,5 @@ import javax.inject.Inject class ReceiveOrderUseCase @Inject constructor( private val repository: OrderRepository ) { - suspend operator fun invoke(orderId: Long): Result = repository.receiveOrder(orderId) + suspend operator fun invoke(items: List>): Result = repository.receiveOrder(items) } \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/order/ui/OrderDetailContent.kt b/app/src/main/java/com/sampoom/android/feature/order/ui/OrderDetailContent.kt index f9d0c00..6109771 100644 --- a/app/src/main/java/com/sampoom/android/feature/order/ui/OrderDetailContent.kt +++ b/app/src/main/java/com/sampoom/android/feature/order/ui/OrderDetailContent.kt @@ -1,12 +1,16 @@ package com.sampoom.android.feature.order.ui +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.Divider import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.MaterialTheme @@ -16,14 +20,19 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.sampoom.android.R import com.sampoom.android.core.ui.component.StatusChip import com.sampoom.android.core.ui.theme.backgroundCardColor +import com.sampoom.android.core.ui.theme.disableColor import com.sampoom.android.core.ui.theme.textColor import com.sampoom.android.core.ui.theme.textSecondaryColor +import com.sampoom.android.core.util.formatWon import com.sampoom.android.feature.order.domain.model.Order import com.sampoom.android.feature.order.domain.model.OrderPart +import com.sampoom.android.feature.order.domain.model.subtotal +import com.sampoom.android.feature.order.domain.model.totalCost import kotlin.collections.forEach @Composable @@ -59,6 +68,7 @@ fun OrderDetailContent( } } } + item { Spacer(Modifier.height(50.dp).fillMaxWidth()) } } } @@ -99,6 +109,22 @@ private fun OrderInfoCard(order: Order) { StatusChip(status = order.status) } + Row( + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = stringResource(R.string.order_detail_total_amount), + style = MaterialTheme.typography.bodyMedium, + color = textSecondaryColor() + ) + Text( + text = formatWon(order.totalCost), + style = MaterialTheme.typography.bodyMedium + ) + } } } } @@ -109,7 +135,9 @@ private fun OrderInfoRow( value: String ) { Row( - modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp), + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 4.dp), horizontalArrangement = Arrangement.SpaceBetween ) { Text( @@ -154,32 +182,53 @@ private fun OrderPartItem( modifier = Modifier.fillMaxWidth(), colors = CardDefaults.cardColors(containerColor = backgroundCardColor()) ) { - Row( + Column( modifier = Modifier .fillMaxWidth() - .padding(16.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically + .padding(16.dp) ) { - Column( - modifier = Modifier.weight(1F) + Row( + modifier = Modifier + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically ) { - Text( - text = part.name, - style = MaterialTheme.typography.titleMedium, - color = textColor() - ) - Text( - text = part.code, - style = MaterialTheme.typography.bodySmall, - color = textSecondaryColor() - ) + Column( + modifier = Modifier.weight(1F) + ) { + Text( + text = part.name, + style = MaterialTheme.typography.titleMedium, + color = textColor() + ) + Text( + text = part.code, + style = MaterialTheme.typography.bodySmall, + color = textSecondaryColor() + ) + } + + Column(horizontalAlignment = Alignment.End) { + Text( + text = formatWon(part.standardCost), + style = MaterialTheme.typography.bodyMedium + ) + Text( + text = "x ${part.quantity}", + style = MaterialTheme.typography.bodyMedium + ) + } } + Spacer(Modifier.padding(4.dp)) + Divider(Modifier.background(textSecondaryColor())) + Spacer(Modifier.padding(4.dp)) + Text( - text = part.quantity.toString(), + text = formatWon(part.subtotal), style = MaterialTheme.typography.titleMedium, - color = textColor() + textAlign = TextAlign.End, + modifier = Modifier.fillMaxWidth() ) } } diff --git a/app/src/main/java/com/sampoom/android/feature/order/ui/OrderDetailScreen.kt b/app/src/main/java/com/sampoom/android/feature/order/ui/OrderDetailScreen.kt index 8c64f2d..fb72131 100644 --- a/app/src/main/java/com/sampoom/android/feature/order/ui/OrderDetailScreen.kt +++ b/app/src/main/java/com/sampoom/android/feature/order/ui/OrderDetailScreen.kt @@ -156,15 +156,6 @@ fun OrderDetailScreen( ) } -// uiState.orderDetail -> { -// EmptyContent( -// message = stringResource(R.string.order_empty_list), -// modifier = Modifier -// .height(200.dp) -// .fillMaxWidth() -// ) -// } - else -> { uiState.orderDetail?.let { order -> OrderDetailContent( diff --git a/app/src/main/java/com/sampoom/android/feature/order/ui/OrderDetailViewModel.kt b/app/src/main/java/com/sampoom/android/feature/order/ui/OrderDetailViewModel.kt index d7122ea..494ed25 100644 --- a/app/src/main/java/com/sampoom/android/feature/order/ui/OrderDetailViewModel.kt +++ b/app/src/main/java/com/sampoom/android/feature/order/ui/OrderDetailViewModel.kt @@ -133,8 +133,26 @@ class OrderDetailViewModel @Inject constructor( viewModelScope.launch { _uiState.update { it.copy(isProcessing = true) } try { - receiveOrderUseCase(orderId).getOrThrow() // 1단계 - completeOrderUseCase(orderId).getOrThrow() // 2단계 + val order = _uiState.value.orderDetail ?: throw Exception() + val items: List> = order.items.flatMap { category -> + category.groups.flatMap { group -> + group.parts.map { part -> part.partId to part.quantity } + } + } + completeOrderUseCase(orderId) + .onFailure { t -> + val error = t.serverMessageOrNull() ?: (t.message ?: errorLabel) + messageHandler.showMessage(message = error, isError = true) + _uiState.update { it.copy(isProcessing = false) } + return@launch + } + receiveOrderUseCase(items) + .onFailure { t -> + val error = t.serverMessageOrNull() ?: (t.message ?: errorLabel) + messageHandler.showMessage(message = error, isError = true) + _uiState.update { it.copy(isProcessing = false) } + return@launch + } messageHandler.showMessage(message = receiveLabel, isError = false) _uiState.update { diff --git a/app/src/main/java/com/sampoom/android/feature/order/ui/OrderItem.kt b/app/src/main/java/com/sampoom/android/feature/order/ui/OrderItem.kt new file mode 100644 index 0000000..8614242 --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/order/ui/OrderItem.kt @@ -0,0 +1,92 @@ +package com.sampoom.android.feature.order.ui + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import com.sampoom.android.R +import com.sampoom.android.core.ui.component.StatusChip +import com.sampoom.android.core.ui.theme.backgroundCardColor +import com.sampoom.android.core.ui.theme.textSecondaryColor +import com.sampoom.android.core.util.buildOrderTitle +import com.sampoom.android.core.util.formatDate +import com.sampoom.android.core.util.formatWon +import com.sampoom.android.feature.order.domain.model.Order +import com.sampoom.android.feature.order.domain.model.totalCost + +@Composable +fun OrderItem( + order: Order, + onClick: () -> Unit +) { + Card( + onClick = { onClick() }, + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors(containerColor = backgroundCardColor()) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + ) { + Row( + modifier = Modifier + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Column( + modifier = Modifier + .weight(1f) + ) { + Text( + text = buildOrderTitle(order), + style = MaterialTheme.typography.bodyMedium, + maxLines = 1 + ) + Spacer(Modifier.height(4.dp)) + Text( + text = order.agencyName ?: stringResource(R.string.common_slash), + style = MaterialTheme.typography.labelMedium, + color = textSecondaryColor() + ) + } + + Spacer(Modifier.width(12.dp)) + + Column(horizontalAlignment = Alignment.End) { + Text( + text = order.createdAt?.let { formatDate(it) } + ?: stringResource(R.string.common_slash), + style = MaterialTheme.typography.labelMedium, + color = textSecondaryColor() + ) + Spacer(Modifier.height(6.dp)) + StatusChip(status = order.status) + } + } + + Spacer(Modifier.height(4.dp)) + + Text( + text = formatWon(order.totalCost), + style = MaterialTheme.typography.bodyMedium, + textAlign = TextAlign.End, + modifier = Modifier.fillMaxWidth() + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/order/ui/OrderListScreen.kt b/app/src/main/java/com/sampoom/android/feature/order/ui/OrderListScreen.kt index 58d2cc7..1e93c4e 100644 --- a/app/src/main/java/com/sampoom/android/feature/order/ui/OrderListScreen.kt +++ b/app/src/main/java/com/sampoom/android/feature/order/ui/OrderListScreen.kt @@ -35,7 +35,6 @@ import androidx.paging.compose.itemKey import com.sampoom.android.R import com.sampoom.android.core.ui.component.EmptyContent import com.sampoom.android.core.ui.component.ErrorContent -import com.sampoom.android.core.ui.component.OrderItem import com.sampoom.android.core.ui.theme.FailRed import com.sampoom.android.core.ui.theme.textColor import com.sampoom.android.feature.order.domain.model.Order @@ -118,6 +117,20 @@ fun OrderListScreen( } else -> { + // 빈 상태 처리 + if (orderListPaged.loadState.refresh !is LoadState.Loading && orderListPaged.itemCount == 0) { + Box( + modifier = Modifier + .fillMaxSize() + .padding(16.dp), + contentAlignment = Alignment.Center + ) { + EmptyContent( + message = stringResource(R.string.order_empty_list), + modifier = Modifier.height(200.dp) + ) + } + } LazyColumn( state = listState, modifier = Modifier @@ -168,23 +181,6 @@ fun OrderListScreen( } } - // 빈 상태 처리 - if (orderListPaged.loadState.refresh !is LoadState.Loading && orderListPaged.itemCount == 0) { - item { - Box( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - contentAlignment = Alignment.Center - ) { - EmptyContent( - message = stringResource(R.string.order_empty_list), - modifier = Modifier.height(200.dp) - ) - } - } - } - item { Spacer(Modifier.height(100.dp)) } } } diff --git a/app/src/main/java/com/sampoom/android/feature/outbound/data/mapper/OutboundMappers.kt b/app/src/main/java/com/sampoom/android/feature/outbound/data/mapper/OutboundMappers.kt index 1f38519..ab25cfa 100644 --- a/app/src/main/java/com/sampoom/android/feature/outbound/data/mapper/OutboundMappers.kt +++ b/app/src/main/java/com/sampoom/android/feature/outbound/data/mapper/OutboundMappers.kt @@ -9,4 +9,4 @@ import com.sampoom.android.feature.outbound.domain.model.OutboundPart fun OutboundDto.toModel(): Outbound = Outbound(categoryId, categoryName, groups.map { it.toModel() }) fun OutboundGroupDto.toModel(): OutboundGroup = OutboundGroup(groupId, groupName, parts.map { it.toModel() }) -fun OutboundPartDto.toModel(): OutboundPart = OutboundPart(outboundId, partId, code, name, quantity) \ No newline at end of file +fun OutboundPartDto.toModel(): OutboundPart = OutboundPart(outboundId, partId, code, name, quantity, standardCost) \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/outbound/data/remote/dto/OutboundDto.kt b/app/src/main/java/com/sampoom/android/feature/outbound/data/remote/dto/OutboundDto.kt index 462b63e..5c7174b 100644 --- a/app/src/main/java/com/sampoom/android/feature/outbound/data/remote/dto/OutboundDto.kt +++ b/app/src/main/java/com/sampoom/android/feature/outbound/data/remote/dto/OutboundDto.kt @@ -17,5 +17,6 @@ data class OutboundPartDto( val partId: Long, val code: String, val name: String, - val quantity: Long + val quantity: Long, + val standardCost: Long ) \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/outbound/domain/model/Outbound.kt b/app/src/main/java/com/sampoom/android/feature/outbound/domain/model/Outbound.kt index 1033bfc..2610cfe 100644 --- a/app/src/main/java/com/sampoom/android/feature/outbound/domain/model/Outbound.kt +++ b/app/src/main/java/com/sampoom/android/feature/outbound/domain/model/Outbound.kt @@ -17,5 +17,10 @@ data class OutboundPart( val partId: Long, val code: String, val name: String, - val quantity: Long -) \ No newline at end of file + val quantity: Long, + val standardCost: Long +) + +// 품목 합계(단가 x 수량) +val OutboundPart.subtotal: Long + get() = standardCost * quantity \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/outbound/ui/OutboundListScreen.kt b/app/src/main/java/com/sampoom/android/feature/outbound/ui/OutboundListScreen.kt index 2d681ee..dabc090 100644 --- a/app/src/main/java/com/sampoom/android/feature/outbound/ui/OutboundListScreen.kt +++ b/app/src/main/java/com/sampoom/android/feature/outbound/ui/OutboundListScreen.kt @@ -49,7 +49,10 @@ import com.sampoom.android.core.ui.theme.FailRed import com.sampoom.android.core.ui.theme.backgroundCardColor import com.sampoom.android.core.ui.theme.textColor import com.sampoom.android.core.ui.theme.textSecondaryColor +import com.sampoom.android.core.util.formatWon +import com.sampoom.android.feature.cart.domain.model.subtotal import com.sampoom.android.feature.outbound.domain.model.OutboundPart +import com.sampoom.android.feature.outbound.domain.model.subtotal @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -198,7 +201,7 @@ fun OutboundListScreen( variant = ButtonVariant.Error, size = ButtonSize.Large, onClick = { showConfirmDialog = true } - ) { Text(stringResource(R.string.outbound_order_parts)) } + ) { Text("${formatWon(uiState.totalCost)} ${stringResource(R.string.outbound_order_parts)}") } } } } @@ -370,30 +373,6 @@ private fun OutboundPartItem( style = MaterialTheme.typography.titleMedium, modifier = Modifier.padding(horizontal = 24.dp) ) -// OutlinedTextField( -// value = part.quantity.toString(), -// onValueChange = { newValue -> -// when { -// newValue.isEmpty() -> onEvent(OutboundListUiEvent.UpdateQuantity(part.outboundId, 1)) -// newValue == "0" -> onEvent(OutboundListUiEvent.UpdateQuantity(part.outboundId, 1)) -// else -> { -// val newQuantity = newValue.toLongOrNull() -// if (newQuantity != null && newQuantity > 0) onEvent(OutboundListUiEvent.UpdateQuantity(part.outboundId, newQuantity)) -// else onEvent(OutboundListUiEvent.UpdateQuantity(part.outboundId, 1)) -// } -// } -// }, -// modifier = Modifier.width(100.dp), -// singleLine = true, -// keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), -// textStyle = MaterialTheme.typography.titleMedium.copy(textAlign = TextAlign.Center), -// colors = OutlinedTextFieldDefaults.colors( -// focusedBorderColor = Color.Transparent, -// unfocusedBorderColor = Color.Transparent, -// disabledBorderColor = Color.Transparent, -// errorBorderColor = Color.Transparent -// ) -// ) CommonButton( variant = ButtonVariant.Neutral, size = ButtonSize.Large, @@ -414,6 +393,20 @@ private fun OutboundPartItem( } } } + val subtotal = part.subtotal + + Spacer(Modifier.height(8.dp)) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = formatWon(subtotal), + style = MaterialTheme.typography.titleLarge + ) + } } } } \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/outbound/ui/OutboundListUiState.kt b/app/src/main/java/com/sampoom/android/feature/outbound/ui/OutboundListUiState.kt index 3a49bc7..27758c9 100644 --- a/app/src/main/java/com/sampoom/android/feature/outbound/ui/OutboundListUiState.kt +++ b/app/src/main/java/com/sampoom/android/feature/outbound/ui/OutboundListUiState.kt @@ -10,4 +10,13 @@ data class OutboundListUiState( val isUpdating: Boolean = false, val isDeleting: Boolean = false, val isOrderSuccess: Boolean = false -) \ No newline at end of file +) { + val totalCost: Long + get() = outboundList.sumOf { category -> + category.groups.sumOf { group -> + group.parts.sumOf { part -> + part.standardCost * part.quantity + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/part/data/mapper/PartMappers.kt b/app/src/main/java/com/sampoom/android/feature/part/data/mapper/PartMappers.kt index 998e90f..777918a 100644 --- a/app/src/main/java/com/sampoom/android/feature/part/data/mapper/PartMappers.kt +++ b/app/src/main/java/com/sampoom/android/feature/part/data/mapper/PartMappers.kt @@ -12,7 +12,7 @@ import com.sampoom.android.feature.part.domain.model.SearchResult fun CategoryDto.toModel(): Category = Category(id, code, name) fun GroupDto.toModel(): Group = Group(id, code, name, categoryId) -fun PartDto.toModel(): Part = Part(partId, code, name, quantity) +fun PartDto.toModel(): Part = Part(partId, code, name, quantity, standardCost) fun SearchCategoryDto.toModel(): List { return groups.flatMap { group -> diff --git a/app/src/main/java/com/sampoom/android/feature/part/data/remote/dto/PartDto.kt b/app/src/main/java/com/sampoom/android/feature/part/data/remote/dto/PartDto.kt index 41872f7..5af2766 100644 --- a/app/src/main/java/com/sampoom/android/feature/part/data/remote/dto/PartDto.kt +++ b/app/src/main/java/com/sampoom/android/feature/part/data/remote/dto/PartDto.kt @@ -4,5 +4,6 @@ data class PartDto( val partId: Long, val code: String, val name: String, - val quantity: Long + val quantity: Long, + val standardCost: Long ) diff --git a/app/src/main/java/com/sampoom/android/feature/part/domain/model/Part.kt b/app/src/main/java/com/sampoom/android/feature/part/domain/model/Part.kt index 019b63b..4899956 100644 --- a/app/src/main/java/com/sampoom/android/feature/part/domain/model/Part.kt +++ b/app/src/main/java/com/sampoom/android/feature/part/domain/model/Part.kt @@ -4,5 +4,6 @@ data class Part( val partId: Long, val code: String, val name: String, - val quantity: Long + val quantity: Long, + val standardCost: Long ) diff --git a/app/src/main/java/com/sampoom/android/feature/part/ui/PartDetailBottomSheet.kt b/app/src/main/java/com/sampoom/android/feature/part/ui/PartDetailBottomSheet.kt index 6ee3f3f..2afbcfa 100644 --- a/app/src/main/java/com/sampoom/android/feature/part/ui/PartDetailBottomSheet.kt +++ b/app/src/main/java/com/sampoom/android/feature/part/ui/PartDetailBottomSheet.kt @@ -27,6 +27,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp @@ -38,6 +39,7 @@ import com.sampoom.android.core.ui.component.ButtonVariant import com.sampoom.android.core.ui.component.CommonButton import com.sampoom.android.core.ui.theme.textColor import com.sampoom.android.core.ui.theme.textSecondaryColor +import com.sampoom.android.core.util.formatWon import com.sampoom.android.feature.part.domain.model.Part @Composable @@ -114,11 +116,24 @@ fun PartDetailBottomSheet( modifier = Modifier.fillMaxWidth() ) } - Text( - stringResource(R.string.part_current_quantity) + - part.quantity.toString() + - stringResource(R.string.common_EA) - ) + + Column( + horizontalAlignment = Alignment.End + ) { + Text( + text = formatWon(part.standardCost), + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold, + textAlign = TextAlign.End, + color = textColor() + ) + Text( + stringResource(R.string.part_current_quantity) + + part.quantity.toString() + + stringResource(R.string.common_EA) + ) + } + } Spacer(Modifier.height(16.dp)) diff --git a/app/src/main/java/com/sampoom/android/feature/part/ui/PartListScreen.kt b/app/src/main/java/com/sampoom/android/feature/part/ui/PartListScreen.kt index 2d16093..cadb1ae 100644 --- a/app/src/main/java/com/sampoom/android/feature/part/ui/PartListScreen.kt +++ b/app/src/main/java/com/sampoom/android/feature/part/ui/PartListScreen.kt @@ -50,6 +50,7 @@ import com.sampoom.android.core.ui.theme.backgroundCardColor import com.sampoom.android.core.ui.theme.disableColor import com.sampoom.android.core.ui.theme.textColor import com.sampoom.android.core.ui.theme.textSecondaryColor +import com.sampoom.android.core.util.formatWon import com.sampoom.android.feature.part.domain.model.Part import kotlinx.coroutines.launch @@ -238,11 +239,18 @@ private fun PartListItemCard( ) } - Text( - text = part.quantity.toString(), - color = textColor(), - style = MaterialTheme.typography.titleMedium - ) + Column(horizontalAlignment = Alignment.End) { + Text( + text = formatWon(part.standardCost), + color = textColor(), + style = MaterialTheme.typography.titleMedium + ) + Text( + text = part.quantity.toString(), + color = textColor(), + style = MaterialTheme.typography.titleMedium + ) + } Icon( painterResource(R.drawable.chevron_right), diff --git a/app/src/main/java/com/sampoom/android/feature/part/ui/PartScreen.kt b/app/src/main/java/com/sampoom/android/feature/part/ui/PartScreen.kt index 7c10cf2..2791fed 100644 --- a/app/src/main/java/com/sampoom/android/feature/part/ui/PartScreen.kt +++ b/app/src/main/java/com/sampoom/android/feature/part/ui/PartScreen.kt @@ -63,6 +63,7 @@ import com.sampoom.android.core.ui.theme.backgroundColor import com.sampoom.android.core.ui.theme.disableColor import com.sampoom.android.core.ui.theme.textColor import com.sampoom.android.core.ui.theme.textSecondaryColor +import com.sampoom.android.core.util.formatWon import com.sampoom.android.feature.part.domain.model.Category import com.sampoom.android.feature.part.domain.model.Group import com.sampoom.android.feature.part.domain.model.Part @@ -579,6 +580,8 @@ fun SearchResultsList( else -> {} } } + + item { Spacer(Modifier.height(50.dp))} } } @@ -627,11 +630,19 @@ private fun SearchPartItem( ) } - Text( - text = "${part.quantity}", - style = MaterialTheme.typography.bodyMedium, - color = textColor() - ) + Column(horizontalAlignment = Alignment.End) { + Text( + text = formatWon(part.standardCost), + style = MaterialTheme.typography.titleMedium, + color = textColor() + ) + + Text( + text = "${part.quantity}", + style = MaterialTheme.typography.bodyMedium, + color = textColor() + ) + } Icon( painterResource(R.drawable.chevron_right), diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8aa7007..29599f8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -81,7 +81,7 @@ 출고 처리하시겠습니까? 출고목록을 비우시겠습니까? 출고 처리되었습니다 - 부품 출고처리 + 출고처리 장바구니 @@ -92,7 +92,7 @@ 장바구니 목록의 상품을 주문하시겠습니까? 장바구니를 비우시겠습니까? 주문이 완료되었습니다 - 부품 주문 + 주문하기 주문관리 @@ -109,6 +109,7 @@ 입고처리 입고 처리하시겠습니까? 입고 처리되었습니다 + 총 가격 대기중