diff --git a/app/src/main/java/com/homebox/lens/ui/screen/itemedit/ItemEditScreen.kt b/app/src/main/java/com/homebox/lens/ui/screen/itemedit/ItemEditScreen.kt index 32480f6..3ccc148 100644 --- a/app/src/main/java/com/homebox/lens/ui/screen/itemedit/ItemEditScreen.kt +++ b/app/src/main/java/com/homebox/lens/ui/screen/itemedit/ItemEditScreen.kt @@ -5,28 +5,48 @@ package com.homebox.lens.ui.screen.itemedit // [IMPORTS] +import androidx.compose.foundation.clickable +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.rememberScrollState import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowDropDown +import androidx.compose.material.icons.filled.DateRange import androidx.compose.material.icons.filled.Save +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Checkbox import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.DatePicker +import androidx.compose.material3.DatePickerDialog +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Switch import androidx.compose.material3.Text +import androidx.compose.material3.rememberDatePickerState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType @@ -36,13 +56,16 @@ import com.homebox.lens.R import com.homebox.lens.navigation.NavigationActions import com.homebox.lens.ui.common.MainScaffold import timber.log.Timber +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale // [END_IMPORTS] -// [ENTITY: Function('ItemEditScreen')] -// [RELATION: Function('ItemEditScreen')] -> [DEPENDS_ON] -> [Class('NavigationActions')] -// [RELATION: Function('ItemEditScreen')] -> [DEPENDS_ON] -> [ViewModel('ItemEditViewModel')] -// [RELATION: Function('ItemEditScreen')] -> [CONSUMES_STATE] -> [DataClass('ItemEditUiState')] -// [RELATION: Function('ItemEditScreen')] -> [CALLS] -> [Function('MainScaffold')] +// [ENTITY: Composable('ItemEditScreen')] +// [RELATION: Composable('ItemEditScreen')] -> [DEPENDS_ON] -> [Class('NavigationActions')] +// [RELATION: Composable('ItemEditScreen')] -> [DEPENDS_ON] -> [ViewModel('ItemEditViewModel')] +// [RELATION: Composable('ItemEditScreen')] -> [CONSUMES_STATE] -> [DataClass('ItemEditUiState')] +// [RELATION: Composable('ItemEditScreen')] -> [CALLS] -> [Composable('MainScaffold')] /** * @summary Composable-функция для экрана "Редактирование элемента". * @param currentRoute Текущий маршрут для подсветки активного элемента в Drawer. @@ -51,6 +74,7 @@ import timber.log.Timber * @param viewModel ViewModel для управления состоянием экрана. * @param onSaveSuccess Callback, вызываемый после успешного сохранения товара. */ +@OptIn(ExperimentalMaterial3Api::class) @Composable fun ItemEditScreen( currentRoute: String?, @@ -75,7 +99,7 @@ fun ItemEditScreen( } LaunchedEffect(Unit) { - viewModel.saveCompleted.collect { + viewModel.saveCompleted.collect { Timber.i("[INFO][ACTION][save_completed_callback] Item save completed. Triggering onSaveSuccess.") onSaveSuccess() } @@ -85,7 +109,7 @@ fun ItemEditScreen( topBarTitle = stringResource(id = R.string.item_edit_title), currentRoute = currentRoute, navigationActions = navigationActions - ) { + ) { paddingValues -> Scaffold( snackbarHost = { SnackbarHost(snackbarHostState) }, floatingActionButton = { @@ -100,40 +124,389 @@ fun ItemEditScreen( Column( modifier = Modifier .fillMaxSize() - .padding(it) + .padding(paddingValues) .padding(16.dp) + .verticalScroll(rememberScrollState()) ) { if (uiState.isLoading) { CircularProgressIndicator(modifier = Modifier.fillMaxWidth()) } else { uiState.item?.let { item -> - OutlinedTextField( - value = item.name, - onValueChange = { viewModel.updateName(it) }, - label = { Text(stringResource(R.string.item_name)) }, - modifier = Modifier.fillMaxWidth() - ) - Spacer(modifier = Modifier.height(8.dp)) - OutlinedTextField( - value = item.description ?: "", - onValueChange = { viewModel.updateDescription(it) }, - label = { Text(stringResource(R.string.item_description)) }, - modifier = Modifier.fillMaxWidth() - ) - Spacer(modifier = Modifier.height(8.dp)) - OutlinedTextField( - value = item.quantity.toString(), - onValueChange = { viewModel.updateQuantity(it.toIntOrNull() ?: 0) }, - label = { Text(stringResource(R.string.item_quantity)) }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), - modifier = Modifier.fillMaxWidth() - ) - // Add more fields as needed + // [AI_NOTE]: General Information section for basic item details. + Card( + modifier = Modifier.fillMaxWidth(), + elevation = CardDefaults.cardElevation(4.dp) + ) { + Column(modifier = Modifier.padding(16.dp)) { + Text( + text = stringResource(R.string.item_general_information), + style = MaterialTheme.typography.headlineSmall + ) + Spacer(modifier = Modifier.height(16.dp)) + OutlinedTextField( + value = item.name, + onValueChange = { viewModel.updateName(it) }, + label = { Text(stringResource(R.string.item_name)) }, + modifier = Modifier.fillMaxWidth() + ) + Spacer(modifier = Modifier.height(8.dp)) + OutlinedTextField( + value = item.description ?: "", + onValueChange = { viewModel.updateDescription(it) }, + label = { Text(stringResource(R.string.item_description)) }, + modifier = Modifier.fillMaxWidth() + ) + Spacer(modifier = Modifier.height(8.dp)) + OutlinedTextField( + value = item.quantity.toString(), + onValueChange = { viewModel.updateQuantity(it.toIntOrNull() ?: 0) }, + label = { Text(stringResource(R.string.item_quantity)) }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + modifier = Modifier.fillMaxWidth() + ) + Spacer(modifier = Modifier.height(8.dp)) + // [AI_NOTE]: Location selection will require a separate component or screen. + OutlinedTextField( + value = item.location?.name ?: "", + onValueChange = { /* TODO: Implement location selection */ }, + label = { Text(stringResource(R.string.item_location)) }, + readOnly = true, + trailingIcon = { + IconButton(onClick = { /* TODO: Implement location selection */ }) { + Icon(Icons.Filled.ArrowDropDown, contentDescription = stringResource(R.string.select_location)) + } + }, + modifier = Modifier.fillMaxWidth() + ) + Spacer(modifier = Modifier.height(8.dp)) + // [AI_NOTE]: Label selection will require a separate component or screen. + OutlinedTextField( + value = item.labels.joinToString { it.name }, + onValueChange = { /* TODO: Implement label selection */ }, + label = { Text(stringResource(R.string.item_labels)) }, + readOnly = true, + trailingIcon = { + IconButton(onClick = { /* TODO: Implement label selection */ }) { + Icon(Icons.Filled.ArrowDropDown, contentDescription = stringResource(R.string.select_labels)) + } + }, + modifier = Modifier.fillMaxWidth() + ) + } + } + + Spacer(modifier = Modifier.height(16.dp)) + + // [AI_NOTE]: Purchase Information section. + Card( + modifier = Modifier.fillMaxWidth(), + elevation = CardDefaults.cardElevation(4.dp) + ) { + Column(modifier = Modifier.padding(16.dp)) { + Text( + text = stringResource(R.string.item_purchase_information), + style = MaterialTheme.typography.headlineSmall + ) + Spacer(modifier = Modifier.height(16.dp)) + OutlinedTextField( + value = item.purchasePrice?.toString() ?: "", + onValueChange = { viewModel.updatePurchasePrice(it.toBigDecimalOrNull()) }, + label = { Text(stringResource(R.string.item_purchase_price)) }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + modifier = Modifier.fillMaxWidth() + ) + Spacer(modifier = Modifier.height(8.dp)) + OutlinedTextField( + value = item.purchaseFrom ?: "", + onValueChange = { viewModel.updatePurchaseFrom(it) }, + label = { Text(stringResource(R.string.item_purchase_from)) }, + modifier = Modifier.fillMaxWidth() + ) + Spacer(modifier = Modifier.height(8.dp)) + // [AI_NOTE]: Date picker for purchase time. + var showPurchaseDatePicker by remember { mutableStateOf(false) } + val purchaseDateState = rememberDatePickerState() + OutlinedTextField( + value = item.purchaseTime ?: "", + onValueChange = { }, // Read-only, handled by date picker + label = { Text(stringResource(R.string.item_purchase_time)) }, + readOnly = true, + trailingIcon = { + IconButton(onClick = { showPurchaseDatePicker = true }) { + Icon(Icons.Filled.DateRange, contentDescription = stringResource(R.string.select_date)) + } + }, + modifier = Modifier + .fillMaxWidth() + .clickable { showPurchaseDatePicker = true } + ) + if (showPurchaseDatePicker) { + DatePickerDialog( + onDismissRequest = { showPurchaseDatePicker = false }, + confirmButton = { + Text(stringResource(R.string.ok), modifier = Modifier.clickable { + val selectedDate = purchaseDateState.selectedDateMillis?.let { + SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(Date(it)) + } + if (selectedDate != null) { + viewModel.updatePurchaseTime(selectedDate) + } + showPurchaseDatePicker = false + }) + }, + dismissButton = { + Text(stringResource(R.string.cancel), modifier = Modifier.clickable { showPurchaseDatePicker = false }) + } + ) { + DatePicker(state = purchaseDateState) + } + } + } + } + + Spacer(modifier = Modifier.height(16.dp)) + + // [AI_NOTE]: Warranty Information section. + Card( + modifier = Modifier.fillMaxWidth(), + elevation = CardDefaults.cardElevation(4.dp) + ) { + Column(modifier = Modifier.padding(16.dp)) { + Text( + text = stringResource(R.string.item_warranty_information), + style = MaterialTheme.typography.headlineSmall + ) + Spacer(modifier = Modifier.height(16.dp)) + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text(stringResource(R.string.item_lifetime_warranty)) + Switch( + checked = item.lifetimeWarranty, + onCheckedChange = { viewModel.updateLifetimeWarranty(it) } + ) + } + Spacer(modifier = Modifier.height(8.dp)) + OutlinedTextField( + value = item.warrantyDetails ?: "", + onValueChange = { viewModel.updateWarrantyDetails(it) }, + label = { Text(stringResource(R.string.item_warranty_details)) }, + modifier = Modifier.fillMaxWidth() + ) + Spacer(modifier = Modifier.height(8.dp)) + // [AI_NOTE]: Date picker for warranty expiration. + var showWarrantyDatePicker by remember { mutableStateOf(false) } + val warrantyDateState = rememberDatePickerState() + OutlinedTextField( + value = item.warrantyExpires ?: "", + onValueChange = { }, // Read-only, handled by date picker + label = { Text(stringResource(R.string.item_warranty_expires)) }, + readOnly = true, + trailingIcon = { + IconButton(onClick = { showWarrantyDatePicker = true }) { + Icon(Icons.Filled.DateRange, contentDescription = stringResource(R.string.select_date)) + } + }, + modifier = Modifier + .fillMaxWidth() + .clickable { showWarrantyDatePicker = true } + ) + if (showWarrantyDatePicker) { + DatePickerDialog( + onDismissRequest = { showWarrantyDatePicker = false }, + confirmButton = { + Text(stringResource(R.string.ok), modifier = Modifier.clickable { + val selectedDate = warrantyDateState.selectedDateMillis?.let { + SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(Date(it)) + } + if (selectedDate != null) { + viewModel.updateWarrantyExpires(selectedDate) + } + showWarrantyDatePicker = false + }) + }, + dismissButton = { + Text(stringResource(R.string.cancel), modifier = Modifier.clickable { showWarrantyDatePicker = false }) + } + ) { + DatePicker(state = warrantyDateState) + } + } + } + } + + Spacer(modifier = Modifier.height(16.dp)) + + // [AI_NOTE]: Identification section. + Card( + modifier = Modifier.fillMaxWidth(), + elevation = CardDefaults.cardElevation(4.dp) + ) { + Column(modifier = Modifier.padding(16.dp)) { + Text( + text = stringResource(R.string.item_identification), + style = MaterialTheme.typography.headlineSmall + ) + Spacer(modifier = Modifier.height(16.dp)) + OutlinedTextField( + value = item.assetId ?: "", + onValueChange = { viewModel.updateAssetId(it) }, + label = { Text(stringResource(R.string.item_asset_id)) }, + modifier = Modifier.fillMaxWidth() + ) + Spacer(modifier = Modifier.height(8.dp)) + OutlinedTextField( + value = item.serialNumber ?: "", + onValueChange = { viewModel.updateSerialNumber(it) }, + label = { Text(stringResource(R.string.item_serial_number)) }, + modifier = Modifier.fillMaxWidth() + ) + Spacer(modifier = Modifier.height(8.dp)) + OutlinedTextField( + value = item.manufacturer ?: "", + onValueChange = { viewModel.updateManufacturer(it) }, + label = { Text(stringResource(R.string.item_manufacturer)) }, + modifier = Modifier.fillMaxWidth() + ) + Spacer(modifier = Modifier.height(8.dp)) + OutlinedTextField( + value = item.modelNumber ?: "", + onValueChange = { viewModel.updateModelNumber(it) }, + label = { Text(stringResource(R.string.item_model_number)) }, + modifier = Modifier.fillMaxWidth() + ) + } + } + + Spacer(modifier = Modifier.height(16.dp)) + + // [AI_NOTE]: Status & Notes section. + Card( + modifier = Modifier.fillMaxWidth(), + elevation = CardDefaults.cardElevation(4.dp) + ) { + Column(modifier = Modifier.padding(16.dp)) { + Text( + text = stringResource(R.string.item_status_notes), + style = MaterialTheme.typography.headlineSmall + ) + Spacer(modifier = Modifier.height(16.dp)) + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text(stringResource(R.string.item_archived)) + Switch( + checked = item.archived, + onCheckedChange = { viewModel.updateArchived(it) } + ) + } + Spacer(modifier = Modifier.height(8.dp)) + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text(stringResource(R.string.item_insured)) + Switch( + checked = item.insured, + onCheckedChange = { viewModel.updateInsured(it) } + ) + } + Spacer(modifier = Modifier.height(8.dp)) + OutlinedTextField( + value = item.notes ?: "", + onValueChange = { viewModel.updateNotes(it) }, + label = { Text(stringResource(R.string.item_notes)) }, + modifier = Modifier.fillMaxWidth() + ) + } + } + + Spacer(modifier = Modifier.height(16.dp)) + + // [AI_NOTE]: Sold Information section (conditionally displayed). + if (item.soldTime != null || item.soldPrice != null || item.soldTo != null || item.soldNotes != null) { + Card( + modifier = Modifier.fillMaxWidth(), + elevation = CardDefaults.cardElevation(4.dp) + ) { + Column(modifier = Modifier.padding(16.dp)) { + Text( + text = stringResource(R.string.item_sold_information), + style = MaterialTheme.typography.headlineSmall + ) + Spacer(modifier = Modifier.height(16.dp)) + OutlinedTextField( + value = item.soldPrice?.toString() ?: "", + onValueChange = { viewModel.updateSoldPrice(it.toBigDecimalOrNull()) }, + label = { Text(stringResource(R.string.item_sold_price)) }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + modifier = Modifier.fillMaxWidth() + ) + Spacer(modifier = Modifier.height(8.dp)) + OutlinedTextField( + value = item.soldTo ?: "", + onValueChange = { viewModel.updateSoldTo(it) }, + label = { Text(stringResource(R.string.item_sold_to)) }, + modifier = Modifier.fillMaxWidth() + ) + Spacer(modifier = Modifier.height(8.dp)) + OutlinedTextField( + value = item.soldNotes ?: "", + onValueChange = { viewModel.updateSoldNotes(it) }, + label = { Text(stringResource(R.string.item_sold_notes)) }, + modifier = Modifier.fillMaxWidth() + ) + Spacer(modifier = Modifier.height(8.dp)) + // [AI_NOTE]: Date picker for sold time. + var showSoldDatePicker by remember { mutableStateOf(false) } + val soldDateState = rememberDatePickerState() + OutlinedTextField( + value = item.soldTime ?: "", + onValueChange = { }, // Read-only, handled by date picker + label = { Text(stringResource(R.string.item_sold_time)) }, + readOnly = true, + trailingIcon = { + IconButton(onClick = { showSoldDatePicker = true }) { + Icon(Icons.Filled.DateRange, contentDescription = stringResource(R.string.select_date)) + } + }, + modifier = Modifier + .fillMaxWidth() + .clickable { showSoldDatePicker = true } + ) + if (showSoldDatePicker) { + DatePickerDialog( + onDismissRequest = { showSoldDatePicker = false }, + confirmButton = { + Text(stringResource(R.string.ok), modifier = Modifier.clickable { + val selectedDate = soldDateState.selectedDateMillis?.let { + SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(Date(it)) + } + if (selectedDate != null) { + viewModel.updateSoldTime(selectedDate) + } + showSoldDatePicker = false + }) + }, + dismissButton = { + Text(stringResource(R.string.cancel), modifier = Modifier.clickable { showSoldDatePicker = false }) + } + ) { + DatePicker(state = soldDateState) + } + } + } + } } } } } } } -// [END_ENTITY: Function('ItemEditScreen')] +// [END_ENTITY: Composable('ItemEditScreen')] // [END_FILE_ItemEditScreen.kt] diff --git a/app/src/main/java/com/homebox/lens/ui/screen/itemedit/ItemEditViewModel.kt b/app/src/main/java/com/homebox/lens/ui/screen/itemedit/ItemEditViewModel.kt index 1b0a5e6..1813ef6 100644 --- a/app/src/main/java/com/homebox/lens/ui/screen/itemedit/ItemEditViewModel.kt +++ b/app/src/main/java/com/homebox/lens/ui/screen/itemedit/ItemEditViewModel.kt @@ -7,8 +7,10 @@ package com.homebox.lens.ui.screen.itemedit // [IMPORTS] import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.homebox.lens.data.db.entity.toDomainItem import com.homebox.lens.domain.model.Item import com.homebox.lens.domain.model.ItemCreate +import com.homebox.lens.domain.model.ItemUpdate import com.homebox.lens.domain.model.Label import com.homebox.lens.domain.model.Location import com.homebox.lens.domain.usecase.CreateItemUseCase @@ -23,6 +25,7 @@ import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import timber.log.Timber +import java.math.BigDecimal import javax.inject.Inject // [END_IMPORTS] @@ -73,24 +76,45 @@ class ItemEditViewModel @Inject constructor( _uiState.value = _uiState.value.copy(isLoading = true, error = null) if (itemId == null) { Timber.i("[INFO][ACTION][new_item_preparation] Preparing for new item creation.") - _uiState.value = _uiState.value.copy(isLoading = false, item = Item(id = "", name = "", description = null, quantity = 0, image = null, location = null, labels = emptyList(), value = null, createdAt = null)) + _uiState.value = _uiState.value.copy( + isLoading = false, + item = Item( + id = "", + name = "", + description = null, + quantity = 1, + image = null, + location = null, + labels = emptyList(), + purchasePrice = null, + createdAt = null, + archived = false, + assetId = null, + fields = emptyList(), + insured = false, + lifetimeWarranty = false, + manufacturer = null, + modelNumber = null, + notes = null, + parentId = null, + purchaseFrom = null, + purchaseTime = null, + serialNumber = null, + soldNotes = null, + soldPrice = null, + soldTime = null, + soldTo = null, + syncChildItemsLocations = false, + warrantyDetails = null, + warrantyExpires = null + ) + ) } else { try { Timber.i("[INFO][ACTION][fetching_item_details] Fetching details for item ID: %s", itemId) val itemOut = getItemDetailsUseCase(itemId) Timber.d("[DEBUG][ACTION][mapping_item_out_to_item] Mapping ItemOut to Item for UI state.") - val item = Item( - id = itemOut.id, - name = itemOut.name, - description = itemOut.description, - quantity = itemOut.quantity, - image = itemOut.images.firstOrNull()?.path, // Assuming first image is the main one - location = itemOut.location?.let { Location(it.id, it.name) }, // Simplified mapping - labels = itemOut.labels.map { Label(it.id, it.name) }, // Simplified mapping - value = itemOut.value?.toBigDecimal(), - createdAt = itemOut.createdAt - ) - _uiState.value = _uiState.value.copy(isLoading = false, item = item) + _uiState.value = _uiState.value.copy(isLoading = false, item = itemOut.toDomainItem()) Timber.i("[INFO][ACTION][item_details_fetched] Successfully fetched item details for ID: %s", itemId) } catch (e: Exception) { Timber.e(e, "[ERROR][FALLBACK][item_load_failed] Failed to load item details for ID: %s", itemId) @@ -117,53 +141,72 @@ class ItemEditViewModel @Inject constructor( try { if (currentItem.id.isBlank()) { Timber.i("[INFO][ACTION][creating_new_item] Creating new item: %s", currentItem.name) - val createdItemSummary = createItemUseCase(ItemCreate( - name = currentItem.name, - description = currentItem.description, - quantity = currentItem.quantity, - assetId = null, // Item does not have assetId - notes = null, // Item does not have notes - serialNumber = null, // Item does not have serialNumber - value = currentItem.value?.toDouble(), // Convert BigDecimal to Double - purchasePrice = null, // Item does not have purchasePrice - purchaseDate = null, // Item does not have purchaseDate - warrantyUntil = null, // Item does not have warrantyUntil - locationId = currentItem.location?.id, - parentId = null, // Item does not have parentId - labelIds = currentItem.labels.map { it.id } - )) - Timber.d("[DEBUG][ACTION][mapping_item_summary_to_item] Mapping ItemSummary to Item for UI state.") - val createdItem = Item( - id = createdItemSummary.id, - name = createdItemSummary.name, - description = null, // ItemSummary does not have description - quantity = 0, // ItemSummary does not have quantity - image = null, // ItemSummary does not have image - location = null, // ItemSummary does not have location - labels = emptyList(), // ItemSummary does not have labels - value = null, // ItemSummary does not have value - createdAt = null // ItemSummary does not have createdAt + val createdItemOut = createItemUseCase( + ItemCreate( + name = currentItem.name, + description = currentItem.description, + quantity = currentItem.quantity, + archived = currentItem.archived, + assetId = currentItem.assetId, + insured = currentItem.insured, + lifetimeWarranty = currentItem.lifetimeWarranty, + manufacturer = currentItem.manufacturer, + modelNumber = currentItem.modelNumber, + notes = currentItem.notes, + parentId = currentItem.parentId, + purchaseFrom = currentItem.purchaseFrom, + purchasePrice = currentItem.purchasePrice, + purchaseTime = currentItem.purchaseTime, + serialNumber = currentItem.serialNumber, + soldNotes = currentItem.soldNotes, + soldPrice = currentItem.soldPrice, + soldTime = currentItem.soldTime, + soldTo = currentItem.soldTo, + syncChildItemsLocations = currentItem.syncChildItemsLocations, + warrantyDetails = currentItem.warrantyDetails, + warrantyExpires = currentItem.warrantyExpires, + locationId = currentItem.location?.id, + labelIds = currentItem.labels.map { it.id } + ) ) - _uiState.value = _uiState.value.copy(isLoading = false, item = createdItem) - Timber.i("[INFO][ACTION][new_item_created] Successfully created new item with ID: %s", createdItem.id) + Timber.d("[DEBUG][ACTION][mapping_item_out_to_item] Mapping ItemOut to Item for UI state.") + _uiState.value = _uiState.value.copy(isLoading = false, item = createdItemOut.toDomainItem()) + Timber.i("[INFO][ACTION][new_item_created] Successfully created new item with ID: %s", createdItemOut.id) _saveCompleted.emit(Unit) } else { Timber.i("[INFO][ACTION][updating_existing_item] Updating existing item with ID: %s", currentItem.id) - val updatedItemOut = updateItemUseCase(currentItem) - Timber.d("[DEBUG][ACTION][mapping_item_out_to_item] Mapping ItemOut to Item for UI state.") - val updatedItem = Item( - id = updatedItemOut.id, - name = updatedItemOut.name, - description = updatedItemOut.description, - quantity = updatedItemOut.quantity, - image = updatedItemOut.images.firstOrNull()?.path, - location = updatedItemOut.location?.let { Location(it.id, it.name) }, - labels = updatedItemOut.labels.map { Label(it.id, it.name) }, - value = updatedItemOut.value.toBigDecimal(), - createdAt = updatedItemOut.createdAt + val updatedItemOut = updateItemUseCase( + ItemUpdate( + id = currentItem.id, + name = currentItem.name, + description = currentItem.description, + quantity = currentItem.quantity, + archived = currentItem.archived, + assetId = currentItem.assetId, + insured = currentItem.insured, + lifetimeWarranty = currentItem.lifetimeWarranty, + manufacturer = currentItem.manufacturer, + modelNumber = currentItem.modelNumber, + notes = currentItem.notes, + parentId = currentItem.parentId, + purchaseFrom = currentItem.purchaseFrom, + purchasePrice = currentItem.purchasePrice, + purchaseTime = currentItem.purchaseTime, + serialNumber = currentItem.serialNumber, + soldNotes = currentItem.soldNotes, + soldPrice = currentItem.soldPrice, + soldTime = currentItem.soldTime, + soldTo = currentItem.soldTo, + syncChildItemsLocations = currentItem.syncChildItemsLocations, + warrantyDetails = currentItem.warrantyDetails, + warrantyExpires = currentItem.warrantyExpires, + locationId = currentItem.location?.id, + labelIds = currentItem.labels.map { it.id } + ) ) - _uiState.value = _uiState.value.copy(isLoading = false, item = updatedItem) - Timber.i("[INFO][ACTION][item_updated] Successfully updated item with ID: %s", updatedItem.id) + Timber.d("[DEBUG][ACTION][mapping_item_out_to_item] Mapping ItemOut to Item for UI state.") + _uiState.value = _uiState.value.copy(isLoading = false, item = updatedItemOut.toDomainItem()) + Timber.i("[INFO][ACTION][item_updated] Successfully updated item with ID: %s", updatedItemOut.id) _saveCompleted.emit(Unit) } } catch (e: Exception) { @@ -209,6 +252,234 @@ class ItemEditViewModel @Inject constructor( _uiState.value = _uiState.value.copy(item = _uiState.value.item?.copy(quantity = newQuantity)) } // [END_ENTITY: Function('updateQuantity')] -} -// [END_ENTITY: ViewModel('ItemEditViewModel')] -// [END_FILE_ItemEditViewModel.kt] + + // [ENTITY: Function('updateArchived')] + /** + * @summary Updates the archived status of the item in the UI state. + * @param newArchived The new archived status for the item. + * @sideeffect Updates the `item` in `_uiState`. + */ + fun updateArchived(newArchived: Boolean) { + Timber.d("[DEBUG][ACTION][updating_item_archived] Updating item archived status to: %s", newArchived) + _uiState.value = _uiState.value.copy(item = _uiState.value.item?.copy(archived = newArchived)) + } + // [END_ENTITY: Function('updateArchived')] + + // [ENTITY: Function('updateAssetId')] + /** + * @summary Updates the asset ID of the item in the UI state. + * @param newAssetId The new asset ID for the item. + * @sideeffect Updates the `item` in `_uiState`. + */ + fun updateAssetId(newAssetId: String) { + Timber.d("[DEBUG][ACTION][updating_item_assetId] Updating item asset ID to: %s", newAssetId) + _uiState.value = _uiState.value.copy(item = _uiState.value.item?.copy(assetId = newAssetId)) + } + // [END_ENTITY: Function('updateAssetId')] + + // [ENTITY: Function('updateInsured')] + /** + * @summary Updates the insured status of the item in the UI state. + * @param newInsured The new insured status for the item. + * @sideeffect Updates the `item` in `_uiState`. + */ + fun updateInsured(newInsured: Boolean) { + Timber.d("[DEBUG][ACTION][updating_item_insured] Updating item insured status to: %s", newInsured) + _uiState.value = _uiState.value.copy(item = _uiState.value.item?.copy(insured = newInsured)) + } + // [END_ENTITY: Function('updateInsured')] + + // [ENTITY: Function('updateLifetimeWarranty')] + /** + * @summary Updates the lifetime warranty status of the item in the UI state. + * @param newLifetimeWarranty The new lifetime warranty status for the item. + * @sideeffect Updates the `item` in `_uiState`. + */ + fun updateLifetimeWarranty(newLifetimeWarranty: Boolean) { + Timber.d("[DEBUG][ACTION][updating_item_lifetime_warranty] Updating item lifetime warranty status to: %s", newLifetimeWarranty) + _uiState.value = _uiState.value.copy(item = _uiState.value.item?.copy(lifetimeWarranty = newLifetimeWarranty)) + } + // [END_ENTITY: Function('updateLifetimeWarranty')] + + // [ENTITY: Function('updateManufacturer')] + /** + * @summary Updates the manufacturer of the item in the UI state. + * @param newManufacturer The new manufacturer for the item. + * @sideeffect Updates the `item` in `_uiState`. + */ + fun updateManufacturer(newManufacturer: String) { + Timber.d("[DEBUG][ACTION][updating_item_manufacturer] Updating item manufacturer to: %s", newManufacturer) + _uiState.value = _uiState.value.copy(item = _uiState.value.item?.copy(manufacturer = newManufacturer)) + } + // [END_ENTITY: Function('updateManufacturer')] + + // [ENTITY: Function('updateModelNumber')] + /** + * @summary Updates the model number of the item in the UI state. + * @param newModelNumber The new model number for the item. + * @sideeffect Updates the `item` in `_uiState`. + */ + fun updateModelNumber(newModelNumber: String) { + Timber.d("[DEBUG][ACTION][updating_item_model_number] Updating item model number to: %s", newModelNumber) + _uiState.value = _uiState.value.copy(item = _uiState.value.item?.copy(modelNumber = newModelNumber)) + } + // [END_ENTITY: Function('updateModelNumber')] + + // [ENTITY: Function('updateNotes')] + /** + * @summary Updates the notes of the item in the UI state. + * @param newNotes The new notes for the item. + * @sideeffect Updates the `item` in `_uiState`. + */ + fun updateNotes(newNotes: String) { + Timber.d("[DEBUG][ACTION][updating_item_notes] Updating item notes to: %s", newNotes) + _uiState.value = _uiState.value.copy(item = _uiState.value.item?.copy(notes = newNotes)) + } + // [END_ENTITY: Function('updateNotes')] + + // [ENTITY: Function('updateParentId')] + /** + * @summary Updates the parent ID of the item in the UI state. + * @param newParentId The new parent ID for the item. + * @sideeffect Updates the `item` in `_uiState`. + */ + fun updateParentId(newParentId: String) { + Timber.d("[DEBUG][ACTION][updating_item_parent_id] Updating item parent ID to: %s", newParentId) + _uiState.value = _uiState.value.copy(item = _uiState.value.item?.copy(parentId = newParentId)) + } + // [END_ENTITY: Function('updateParentId')] + + // [ENTITY: Function('updatePurchaseFrom')] + /** + * @summary Updates the purchase source of the item in the UI state. + * @param newPurchaseFrom The new purchase source for the item. + * @sideeffect Updates the `item` in `_uiState`. + */ + fun updatePurchaseFrom(newPurchaseFrom: String) { + Timber.d("[DEBUG][ACTION][updating_item_purchase_from] Updating item purchase from to: %s", newPurchaseFrom) + _uiState.value = _uiState.value.copy(item = _uiState.value.item?.copy(purchaseFrom = newPurchaseFrom)) + } + // [END_ENTITY: Function('updatePurchaseFrom')] + + // [ENTITY: Function('updatePurchasePrice')] + /** + * @summary Updates the purchase price of the item in the UI state. + * @param newPurchasePrice The new purchase price for the item. + * @sideeffect Updates the `item` in `_uiState`. + */ + fun updatePurchasePrice(newPurchasePrice: BigDecimal?) { + Timber.d("[DEBUG][ACTION][updating_item_purchase_price] Updating item purchase price to: %s", newPurchasePrice) + _uiState.value = _uiState.value.copy(item = _uiState.value.item?.copy(purchasePrice = newPurchasePrice)) + } + // [END_ENTITY: Function('updatePurchasePrice')] + + // [ENTITY: Function('updatePurchaseTime')] + /** + * @summary Updates the purchase time of the item in the UI state. + * @param newPurchaseTime The new purchase time for the item. + * @sideeffect Updates the `item` in `_uiState`. + */ + fun updatePurchaseTime(newPurchaseTime: String) { + Timber.d("[DEBUG][ACTION][updating_item_purchase_time] Updating item purchase time to: %s", newPurchaseTime) + _uiState.value = _uiState.value.copy(item = _uiState.value.item?.copy(purchaseTime = newPurchaseTime)) + } + // [END_ENTITY: Function('updatePurchaseTime')] + + // [ENTITY: Function('updateSerialNumber')] + /** + * @summary Updates the serial number of the item in the UI state. + * @param newSerialNumber The new serial number for the item. + * @sideeffect Updates the `item` in `_uiState`. + */ + fun updateSerialNumber(newSerialNumber: String) { + Timber.d("[DEBUG][ACTION][updating_item_serial_number] Updating item serial number to: %s", newSerialNumber) + _uiState.value = _uiState.value.copy(item = _uiState.value.item?.copy(serialNumber = newSerialNumber)) + } + // [END_ENTITY: Function('updateSerialNumber')] + + // [ENTITY: Function('updateSoldNotes')] + /** + * @summary Updates the sold notes of the item in the UI state. + * @param newSoldNotes The new sold notes for the item. + * @sideeffect Updates the `item` in `_uiState`. + */ + fun updateSoldNotes(newSoldNotes: String) { + Timber.d("[DEBUG][ACTION][updating_item_sold_notes] Updating item sold notes to: %s", newSoldNotes) + _uiState.value = _uiState.value.copy(item = _uiState.value.item?.copy(soldNotes = newSoldNotes)) + } + // [END_ENTITY: Function('updateSoldNotes')] + + // [ENTITY: Function('updateSoldPrice')] + /** + * @summary Updates the sold price of the item in the UI state. + * @param newSoldPrice The new sold price for the item. + * @sideeffect Updates the `item` in `_uiState`. + */ + fun updateSoldPrice(newSoldPrice: BigDecimal?) { + Timber.d("[DEBUG][ACTION][updating_item_sold_price] Updating item sold price to: %s", newSoldPrice) + _uiState.value = _uiState.value.copy(item = _uiState.value.item?.copy(soldPrice = newSoldPrice)) + } + // [END_ENTITY: Function('updateSoldPrice')] + + // [ENTITY: Function('updateSoldTime')] + /** + * @summary Updates the sold time of the item in the UI state. + * @param newSoldTime The new sold time for the item. + * @sideeffect Updates the `item` in `_uiState`. + */ + fun updateSoldTime(newSoldTime: String) { + Timber.d("[DEBUG][ACTION][updating_item_sold_time] Updating item sold time to: %s", newSoldTime) + _uiState.value = _uiState.value.copy(item = _uiState.value.item?.copy(soldTime = newSoldTime)) + } + // [END_ENTITY: Function('updateSoldTime')] + + // [ENTITY: Function('updateSoldTo')] + /** + * @summary Updates the sold to field of the item in the UI state. + * @param newSoldTo The new sold to for the item. + * @sideeffect Updates the `item` in `_uiState`. + */ + fun updateSoldTo(newSoldTo: String) { + Timber.d("[DEBUG][ACTION][updating_item_sold_to] Updating item sold to to: %s", newSoldTo) + _uiState.value = _uiState.value.copy(item = _uiState.value.item?.copy(soldTo = newSoldTo)) + } + // [END_ENTITY: Function('updateSoldTo')] + + // [ENTITY: Function('updateSyncChildItemsLocations')] + /** + * @summary Updates the sync child items locations status of the item in the UI state. + * @param newSyncChildItemsLocations The new sync child items locations status for the item. + * @sideeffect Updates the `item` in `_uiState`. + */ + fun updateSyncChildItemsLocations(newSyncChildItemsLocations: Boolean) { + Timber.d("[DEBUG][ACTION][updating_item_sync_child_items_locations] Updating item sync child items locations status to: %s", newSyncChildItemsLocations) + _uiState.value = _uiState.value.copy(item = _uiState.value.item?.copy(syncChildItemsLocations = newSyncChildItemsLocations)) + } + // [END_ENTITY: Function('updateSyncChildItemsLocations')] + + // [ENTITY: Function('updateWarrantyDetails')] + /** + * @summary Updates the warranty details of the item in the UI state. + * @param newWarrantyDetails The new warranty details for the item. + * @sideeffect Updates the `item` in `_uiState`. + */ + fun updateWarrantyDetails(newWarrantyDetails: String) { + Timber.d("[DEBUG][ACTION][updating_item_warranty_details] Updating item warranty details to: %s", newWarrantyDetails) + _uiState.value = _uiState.value.copy(item = _uiState.value.item?.copy(warrantyDetails = newWarrantyDetails)) + } + // [END_ENTITY: Function('updateWarrantyDetails')] + + // [ENTITY: Function('updateWarrantyExpires')] + /** + * @summary Updates the warranty expires date of the item in the UI state. + * @param newWarrantyExpires The new warranty expires date for the item. + * @sideeffect Updates the `item` in `_uiState`. + */ + fun updateWarrantyExpires(newWarrantyExpires: String) { + Timber.d("[DEBUG][ACTION][updating_item_warranty_expires] Updating item warranty expires date to: %s", newWarrantyExpires) + _uiState.value = _uiState.value.copy(item = _uiState.value.item?.copy(warrantyExpires = newWarrantyExpires)) + } + // [END_ENTITY: Function('updateWarrantyExpires')] + } + // [END_ENTITY: ViewModel('ItemEditViewModel')] + // [END_FILE_ItemEditViewModel.kt] diff --git a/build.gradle.kts b/build.gradle.kts index 63a48c8..8ce7462 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,7 +3,7 @@ plugins { // [PLUGIN] Android Application plugin - id("com.android.application") version "8.12.2" apply false + id("com.android.application") version "8.13.0" apply false // [PLUGIN] Kotlin Android plugin id("org.jetbrains.kotlin.android") version "1.9.22" apply false // [PLUGIN] Hilt Android plugin diff --git a/data/src/main/java/com/homebox/lens/data/api/dto/ItemCreateDto.kt b/data/src/main/java/com/homebox/lens/data/api/dto/ItemCreateDto.kt index 6932206..020641e 100644 --- a/data/src/main/java/com/homebox/lens/data/api/dto/ItemCreateDto.kt +++ b/data/src/main/java/com/homebox/lens/data/api/dto/ItemCreateDto.kt @@ -17,17 +17,28 @@ import com.homebox.lens.domain.model.ItemCreate @JsonClass(generateAdapter = true) data class ItemCreateDto( @Json(name = "name") val name: String, - @Json(name = "assetId") val assetId: String?, @Json(name = "description") val description: String?, - @Json(name = "notes") val notes: String?, - @Json(name = "serialNumber") val serialNumber: String?, @Json(name = "quantity") val quantity: Int?, - @Json(name = "value") val value: Double?, - @Json(name = "purchasePrice") val purchasePrice: Double?, - @Json(name = "purchaseDate") val purchaseDate: String?, - @Json(name = "warrantyUntil") val warrantyUntil: String?, - @Json(name = "locationId") val locationId: String?, + @Json(name = "archived") val archived: Boolean?, + @Json(name = "assetId") val assetId: String?, + @Json(name = "insured") val insured: Boolean?, + @Json(name = "lifetimeWarranty") val lifetimeWarranty: Boolean?, + @Json(name = "manufacturer") val manufacturer: String?, + @Json(name = "modelNumber") val modelNumber: String?, + @Json(name = "notes") val notes: String?, @Json(name = "parentId") val parentId: String?, + @Json(name = "purchaseFrom") val purchaseFrom: String?, + @Json(name = "purchasePrice") val purchasePrice: Double?, + @Json(name = "purchaseTime") val purchaseTime: String?, + @Json(name = "serialNumber") val serialNumber: String?, + @Json(name = "soldNotes") val soldNotes: String?, + @Json(name = "soldPrice") val soldPrice: Double?, + @Json(name = "soldTime") val soldTime: String?, + @Json(name = "soldTo") val soldTo: String?, + @Json(name = "syncChildItemsLocations") val syncChildItemsLocations: Boolean?, + @Json(name = "warrantyDetails") val warrantyDetails: String?, + @Json(name = "warrantyExpires") val warrantyExpires: String?, + @Json(name = "locationId") val locationId: String?, @Json(name = "labelIds") val labelIds: List? ) // [END_ENTITY: DataClass('ItemCreateDto')] @@ -40,17 +51,28 @@ data class ItemCreateDto( fun ItemCreate.toDto(): ItemCreateDto { return ItemCreateDto( name = this.name, - assetId = this.assetId, description = this.description, - notes = this.notes, - serialNumber = this.serialNumber, quantity = this.quantity, - value = this.value, - purchasePrice = this.purchasePrice, - purchaseDate = this.purchaseDate, - warrantyUntil = this.warrantyUntil, - locationId = this.locationId, + archived = this.archived, + assetId = this.assetId, + insured = this.insured, + lifetimeWarranty = this.lifetimeWarranty, + manufacturer = this.manufacturer, + modelNumber = this.modelNumber, + notes = this.notes, parentId = this.parentId, + purchaseFrom = this.purchaseFrom, + purchasePrice = this.purchasePrice, + purchaseTime = this.purchaseTime, + serialNumber = this.serialNumber, + soldNotes = this.soldNotes, + soldPrice = this.soldPrice, + soldTime = this.soldTime, + soldTo = this.soldTo, + syncChildItemsLocations = this.syncChildItemsLocations, + warrantyDetails = this.warrantyDetails, + warrantyExpires = this.warrantyExpires, + locationId = this.locationId, labelIds = this.labelIds ) } diff --git a/data/src/main/java/com/homebox/lens/data/api/dto/ItemOutDto.kt b/data/src/main/java/com/homebox/lens/data/api/dto/ItemOutDto.kt index 364a6e4..8cc3742 100644 --- a/data/src/main/java/com/homebox/lens/data/api/dto/ItemOutDto.kt +++ b/data/src/main/java/com/homebox/lens/data/api/dto/ItemOutDto.kt @@ -24,10 +24,20 @@ data class ItemOutDto( @Json(name = "serialNumber") val serialNumber: String?, @Json(name = "quantity") val quantity: Int, @Json(name = "isArchived") val isArchived: Boolean, - @Json(name = "value") val value: Double, @Json(name = "purchasePrice") val purchasePrice: Double?, - @Json(name = "purchaseDate") val purchaseDate: String?, - @Json(name = "warrantyUntil") val warrantyUntil: String?, + @Json(name = "purchaseTime") val purchaseTime: String?, + @Json(name = "purchaseFrom") val purchaseFrom: String?, + @Json(name = "warrantyExpires") val warrantyExpires: String?, + @Json(name = "warrantyDetails") val warrantyDetails: String?, + @Json(name = "lifetimeWarranty") val lifetimeWarranty: Boolean?, + @Json(name = "insured") val insured: Boolean?, + @Json(name = "manufacturer") val manufacturer: String?, + @Json(name = "modelNumber") val modelNumber: String?, + @Json(name = "soldPrice") val soldPrice: Double?, + @Json(name = "soldTime") val soldTime: String?, + @Json(name = "soldTo") val soldTo: String?, + @Json(name = "soldNotes") val soldNotes: String?, + @Json(name = "syncChildItemsLocations") val syncChildItemsLocations: Boolean?, @Json(name = "location") val location: LocationOutDto?, @Json(name = "parent") val parent: ItemSummaryDto?, @Json(name = "children") val children: List, @@ -56,10 +66,20 @@ fun ItemOutDto.toDomain(): ItemOut { serialNumber = this.serialNumber, quantity = this.quantity, isArchived = this.isArchived, - value = this.value, purchasePrice = this.purchasePrice, - purchaseDate = this.purchaseDate, - warrantyUntil = this.warrantyUntil, + purchaseTime = this.purchaseTime, + purchaseFrom = this.purchaseFrom, + warrantyExpires = this.warrantyExpires, + warrantyDetails = this.warrantyDetails, + lifetimeWarranty = this.lifetimeWarranty, + insured = this.insured, + manufacturer = this.manufacturer, + modelNumber = this.modelNumber, + soldPrice = this.soldPrice, + soldTime = this.soldTime, + soldTo = this.soldTo, + soldNotes = this.soldNotes, + syncChildItemsLocations = this.syncChildItemsLocations, location = this.location?.toDomain(), parent = this.parent?.toDomain(), children = this.children.map { it.toDomain() }, diff --git a/data/src/main/java/com/homebox/lens/data/api/dto/ItemUpdateDto.kt b/data/src/main/java/com/homebox/lens/data/api/dto/ItemUpdateDto.kt index f6557c0..d3ef199 100644 --- a/data/src/main/java/com/homebox/lens/data/api/dto/ItemUpdateDto.kt +++ b/data/src/main/java/com/homebox/lens/data/api/dto/ItemUpdateDto.kt @@ -17,18 +17,28 @@ import com.homebox.lens.domain.model.ItemUpdate @JsonClass(generateAdapter = true) data class ItemUpdateDto( @Json(name = "name") val name: String?, - @Json(name = "assetId") val assetId: String?, @Json(name = "description") val description: String?, - @Json(name = "notes") val notes: String?, - @Json(name = "serialNumber") val serialNumber: String?, @Json(name = "quantity") val quantity: Int?, - @Json(name = "isArchived") val isArchived: Boolean?, - @Json(name = "value") val value: Double?, - @Json(name = "purchasePrice") val purchasePrice: Double?, - @Json(name = "purchaseDate") val purchaseDate: String?, - @Json(name = "warrantyUntil") val warrantyUntil: String?, - @Json(name = "locationId") val locationId: String?, + @Json(name = "archived") val archived: Boolean?, + @Json(name = "assetId") val assetId: String?, + @Json(name = "insured") val insured: Boolean?, + @Json(name = "lifetimeWarranty") val lifetimeWarranty: Boolean?, + @Json(name = "manufacturer") val manufacturer: String?, + @Json(name = "modelNumber") val modelNumber: String?, + @Json(name = "notes") val notes: String?, @Json(name = "parentId") val parentId: String?, + @Json(name = "purchaseFrom") val purchaseFrom: String?, + @Json(name = "purchasePrice") val purchasePrice: Double?, + @Json(name = "purchaseTime") val purchaseTime: String?, + @Json(name = "serialNumber") val serialNumber: String?, + @Json(name = "soldNotes") val soldNotes: String?, + @Json(name = "soldPrice") val soldPrice: Double?, + @Json(name = "soldTime") val soldTime: String?, + @Json(name = "soldTo") val soldTo: String?, + @Json(name = "syncChildItemsLocations") val syncChildItemsLocations: Boolean?, + @Json(name = "warrantyDetails") val warrantyDetails: String?, + @Json(name = "warrantyExpires") val warrantyExpires: String?, + @Json(name = "locationId") val locationId: String?, @Json(name = "labelIds") val labelIds: List? ) // [END_ENTITY: DataClass('ItemUpdateDto')] @@ -41,18 +51,28 @@ data class ItemUpdateDto( fun ItemUpdate.toDto(): ItemUpdateDto { return ItemUpdateDto( name = this.name, - assetId = this.assetId, description = this.description, - notes = this.notes, - serialNumber = this.serialNumber, quantity = this.quantity, - isArchived = this.isArchived, - value = this.value, - purchasePrice = this.purchasePrice, - purchaseDate = this.purchaseDate, - warrantyUntil = this.warrantyUntil, - locationId = this.locationId, + archived = this.archived, + assetId = this.assetId, + insured = this.insured, + lifetimeWarranty = this.lifetimeWarranty, + manufacturer = this.manufacturer, + modelNumber = this.modelNumber, + notes = this.notes, parentId = this.parentId, + purchaseFrom = this.purchaseFrom, + purchasePrice = this.purchasePrice, + purchaseTime = this.purchaseTime, + serialNumber = this.serialNumber, + soldNotes = this.soldNotes, + soldPrice = this.soldPrice, + soldTime = this.soldTime, + soldTo = this.soldTo, + syncChildItemsLocations = this.syncChildItemsLocations, + warrantyDetails = this.warrantyDetails, + warrantyExpires = this.warrantyExpires, + locationId = this.locationId, labelIds = this.labelIds ) } diff --git a/data/src/main/java/com/homebox/lens/data/db/entity/ItemEntity.kt b/data/src/main/java/com/homebox/lens/data/db/entity/ItemEntity.kt index 8f81ab5..849dd85 100644 --- a/data/src/main/java/com/homebox/lens/data/db/entity/ItemEntity.kt +++ b/data/src/main/java/com/homebox/lens/data/db/entity/ItemEntity.kt @@ -18,10 +18,29 @@ data class ItemEntity( @PrimaryKey val id: String, val name: String, val description: String?, + val quantity: Int, val image: String?, val locationId: String?, - val value: BigDecimal?, - val createdAt: String? + val purchasePrice: BigDecimal?, + val createdAt: String?, + val archived: Boolean, + val assetId: String?, + val insured: Boolean, + val lifetimeWarranty: Boolean, + val manufacturer: String?, + val modelNumber: String?, + val notes: String?, + val parentId: String?, + val purchaseFrom: String?, + val purchaseTime: String?, + val serialNumber: String?, + val soldNotes: String?, + val soldPrice: BigDecimal?, + val soldTime: String?, + val soldTo: String?, + val syncChildItemsLocations: Boolean, + val warrantyDetails: String?, + val warrantyExpires: String? ) // [END_ENTITY: DatabaseTable('ItemEntity')] diff --git a/data/src/main/java/com/homebox/lens/data/db/entity/Mapper.kt b/data/src/main/java/com/homebox/lens/data/db/entity/Mapper.kt index 9994393..c8db7c4 100644 --- a/data/src/main/java/com/homebox/lens/data/db/entity/Mapper.kt +++ b/data/src/main/java/com/homebox/lens/data/db/entity/Mapper.kt @@ -4,39 +4,236 @@ package com.homebox.lens.data.db.entity // [IMPORTS] +import com.homebox.lens.data.api.dto.CustomFieldDto +import com.homebox.lens.data.api.dto.ItemCreateDto +import com.homebox.lens.data.api.dto.ItemOutDto +import com.homebox.lens.data.api.dto.ItemUpdateDto +import com.homebox.lens.domain.model.CustomField import com.homebox.lens.domain.model.Image +import com.homebox.lens.domain.model.Item +import com.homebox.lens.domain.model.ItemCreate +import com.homebox.lens.domain.model.ItemOut import com.homebox.lens.domain.model.ItemSummary +import com.homebox.lens.domain.model.ItemUpdate +import com.homebox.lens.domain.model.Label import com.homebox.lens.domain.model.LabelOut +import com.homebox.lens.domain.model.Location import com.homebox.lens.domain.model.LocationOut +import java.math.BigDecimal // [END_IMPORTS] -// [ENTITY: Function('toDomain')] -// [RELATION: Function('toDomain')] -> [RETURNS] -> [DataClass('ItemSummary')] +// [ENTITY: Function('ItemWithLabels.toDomainItemSummary')] +// [RELATION: Function('ItemWithLabels.toDomainItemSummary')] -> [RETURNS] -> [DataClass('ItemSummary')] /** * @summary Преобразует [ItemWithLabels] (сущность БД) в [ItemSummary] (доменную модель). */ -fun ItemWithLabels.toDomain(): ItemSummary { +fun ItemWithLabels.toDomainItemSummary(): ItemSummary { return ItemSummary( id = this.item.id, name = this.item.name, image = this.item.image?.let { Image(id = "", path = it, isPrimary = true) }, location = this.item.locationId?.let { LocationOut(id = it, name = "", color = "", isArchived = false, createdAt = "", updatedAt = "") }, - labels = this.labels.map { it.toDomain() }, - assetId = null, - isArchived = false, - value = this.item.value?.toDouble() ?: 0.0, + labels = this.labels.map { it.toDomainLabelOut() }, + assetId = this.item.assetId, + isArchived = this.item.archived, + value = this.item.purchasePrice?.toDouble() ?: 0.0, // Assuming value maps to purchasePrice createdAt = this.item.createdAt ?: "", - updatedAt = "" + updatedAt = "" // ItemEntity does not have updatedAt ) } -// [END_ENTITY: Function('toDomain')] +// [END_ENTITY: Function('ItemWithLabels.toDomainItemSummary')] -// [ENTITY: Function('toDomain')] -// [RELATION: Function('toDomain')] -> [RETURNS] -> [DataClass('LabelOut')] +// [ENTITY: Function('ItemEntity.toDomainItem')] +// [RELATION: Function('ItemEntity.toDomainItem')] -> [RETURNS] -> [DataClass('Item')] +/** + * @summary Преобразует [ItemEntity] (сущность БД) в [Item] (доменную модель). + */ +fun ItemEntity.toDomainItem(): Item { + return Item( + id = this.id, + name = this.name, + description = this.description, + quantity = this.quantity, + image = this.image, + location = this.locationId?.let { Location(it, "") }, // Simplified, name is not in ItemEntity + labels = emptyList(), // Labels are handled via ItemWithLabels + purchasePrice = this.purchasePrice, + createdAt = this.createdAt, + archived = this.archived, + assetId = this.assetId, + fields = emptyList(), // Custom fields are not stored in ItemEntity + insured = this.insured, + lifetimeWarranty = this.lifetimeWarranty, + manufacturer = this.manufacturer, + modelNumber = this.modelNumber, + notes = this.notes, + parentId = this.parentId, + purchaseFrom = this.purchaseFrom, + purchaseTime = this.purchaseTime, + serialNumber = this.serialNumber, + soldNotes = this.soldNotes, + soldPrice = this.soldPrice, + soldTime = this.soldTime, + soldTo = this.soldTo, + syncChildItemsLocations = this.syncChildItemsLocations, + warrantyDetails = this.warrantyDetails, + warrantyExpires = this.warrantyExpires + ) +} +// [END_ENTITY: Function('ItemEntity.toDomainItem')] + +// [ENTITY: Function('Item.toItemEntity')] +// [RELATION: Function('Item.toItemEntity')] -> [RETURNS] -> [DataClass('ItemEntity')] +/** + * @summary Преобразует [Item] (доменную модель) в [ItemEntity] (сущность БД). + */ +fun Item.toItemEntity(): ItemEntity { + return ItemEntity( + id = this.id, + name = this.name, + description = this.description, + quantity = this.quantity, + image = this.image, + locationId = this.location?.id, + purchasePrice = this.purchasePrice, + createdAt = this.createdAt, + archived = this.archived, + assetId = this.assetId, + insured = this.insured, + lifetimeWarranty = this.lifetimeWarranty, + manufacturer = this.manufacturer, + modelNumber = this.modelNumber, + notes = this.notes, + parentId = this.parentId, + purchaseFrom = this.purchaseFrom, + purchaseTime = this.purchaseTime, + serialNumber = this.serialNumber, + soldNotes = this.soldNotes, + soldPrice = this.soldPrice, + soldTime = this.soldTime, + soldTo = this.soldTo, + syncChildItemsLocations = this.syncChildItemsLocations, + warrantyDetails = this.warrantyDetails, + warrantyExpires = this.warrantyExpires + ) +} +// [END_ENTITY: Function('Item.toItemEntity')] + +// [ENTITY: Function('ItemOutDto.toDomainItem')] +// [RELATION: Function('ItemOutDto.toDomainItem')] -> [RETURNS] -> [DataClass('Item')] +/** + * @summary Маппер из ItemOutDto в доменную модель Item. + */ +fun ItemOutDto.toDomainItem(): Item { + return Item( + id = this.id, + name = this.name, + description = this.description, + quantity = this.quantity, + image = this.images.firstOrNull()?.path, + location = this.location?.toDomain(), + labels = this.labels.map { Label(it.id, it.name) }, + purchasePrice = this.purchasePrice?.toBigDecimal(), + createdAt = this.createdAt, + archived = this.isArchived, + assetId = this.assetId, + fields = this.fields.map { it.toDomainCustomField() }, + insured = this.insured ?: false, + lifetimeWarranty = this.lifetimeWarranty ?: false, + manufacturer = this.manufacturer, + modelNumber = this.modelNumber, + notes = this.notes, + parentId = this.parent?.id, + purchaseFrom = this.purchaseFrom, + purchaseTime = this.purchaseTime, + serialNumber = this.serialNumber, + soldNotes = this.soldNotes, + soldPrice = this.soldPrice?.toBigDecimal(), + soldTime = this.soldTime, + soldTo = this.soldTo, + syncChildItemsLocations = this.syncChildItemsLocations ?: false, + warrantyDetails = this.warrantyDetails, + warrantyExpires = this.warrantyExpires + ) +} +// [END_ENTITY: Function('ItemOutDto.toDomainItem')] + +// [ENTITY: Function('ItemCreate.toItemCreateDto')] +// [RELATION: Function('ItemCreate.toItemCreateDto')] -> [RETURNS] -> [DataClass('ItemCreateDto')] +/** + * @summary Маппер из доменной модели ItemCreate в ItemCreateDto. + */ +fun ItemCreate.toItemCreateDto(): ItemCreateDto { + return ItemCreateDto( + name = this.name, + description = this.description, + quantity = this.quantity, + archived = null, // Not applicable for creation + assetId = this.assetId, + insured = null, // Not applicable for creation + lifetimeWarranty = null, // Not applicable for creation + manufacturer = this.manufacturer, + modelNumber = this.modelNumber, + notes = this.notes, + parentId = this.parentId, + purchaseFrom = this.purchaseFrom, + purchasePrice = this.purchasePrice?.toDouble(), + purchaseTime = this.purchaseTime, + serialNumber = this.serialNumber, + soldNotes = null, // Not applicable for creation + soldPrice = null, // Not applicable for creation + soldTime = null, // Not applicable for creation + soldTo = null, // Not applicable for creation + syncChildItemsLocations = null, // Not applicable for creation + warrantyDetails = this.warrantyDetails, + warrantyExpires = this.warrantyExpires, + locationId = this.locationId, + labelIds = this.labelIds + ) +} +// [END_ENTITY: Function('ItemCreate.toItemCreateDto')] + +// [ENTITY: Function('ItemUpdate.toItemUpdateDto')] +// [RELATION: Function('ItemUpdate.toItemUpdateDto')] -> [RETURNS] -> [DataClass('ItemUpdateDto')] +/** + * @summary Маппер из доменной модели ItemUpdate в ItemUpdateDto. + */ +fun ItemUpdate.toItemUpdateDto(): ItemUpdateDto { + return ItemUpdateDto( + name = this.name, + description = this.description, + quantity = this.quantity, + archived = this.archived, + assetId = this.assetId, + insured = this.insured, + lifetimeWarranty = this.lifetimeWarranty, + manufacturer = this.manufacturer, + modelNumber = this.modelNumber, + notes = this.notes, + parentId = this.parentId, + purchaseFrom = this.purchaseFrom, + purchasePrice = this.purchasePrice?.toDouble(), + purchaseTime = this.purchaseTime, + serialNumber = this.serialNumber, + soldNotes = this.soldNotes, + soldPrice = this.soldPrice?.toDouble(), + soldTime = this.soldTime, + soldTo = this.soldTo, + syncChildItemsLocations = this.syncChildItemsLocations, + warrantyDetails = this.warrantyDetails, + warrantyExpires = this.warrantyExpires, + locationId = this.locationId, + labelIds = this.labelIds + ) +} +// [END_ENTITY: Function('ItemUpdate.toItemUpdateDto')] + +// [ENTITY: Function('LabelEntity.toDomainLabelOut')] +// [RELATION: Function('LabelEntity.toDomainLabelOut')] -> [RETURNS] -> [DataClass('LabelOut')] /** * @summary Преобразует [LabelEntity] (сущность БД) в [LabelOut] (доменную модель). */ -fun LabelEntity.toDomain(): LabelOut { +fun LabelEntity.toDomainLabelOut(): LabelOut { return LabelOut( id = this.id, name = this.name, @@ -46,4 +243,17 @@ fun LabelEntity.toDomain(): LabelOut { updatedAt = "" ) } -// [END_ENTITY: Function('toDomain')] \ No newline at end of file +// [END_ENTITY: Function('LabelEntity.toDomainLabelOut')] + +// [ENTITY: Function('CustomFieldDto.toDomainCustomField')] +// [RELATION: Function('CustomFieldDto.toDomainCustomField')] -> [RETURNS] -> [DataClass('CustomField')] +/** + * @summary Преобразует [CustomFieldDto] (DTO API) в [CustomField] (доменную модель). + */ +fun CustomFieldDto.toDomainCustomField(): CustomField { + return CustomField( + name = this.name, + value = this.value + ) +} +// [END_ENTITY: Function('CustomFieldDto.toDomainCustomField')] \ No newline at end of file diff --git a/domain/src/main/java/com/homebox/lens/domain/model/Item.kt b/domain/src/main/java/com/homebox/lens/domain/model/Item.kt index 6dfec66..1c150f4 100644 --- a/domain/src/main/java/com/homebox/lens/domain/model/Item.kt +++ b/domain/src/main/java/com/homebox/lens/domain/model/Item.kt @@ -5,6 +5,8 @@ package com.homebox.lens.domain.model // [IMPORTS] import java.math.BigDecimal +import com.homebox.lens.domain.model.CustomField +import com.homebox.lens.domain.model.Image // [END_IMPORTS] // [ENTITY: DataClass('Item')] @@ -18,8 +20,27 @@ import java.math.BigDecimal * @param image Url изображения. * @param location Местоположение вещи. * @param labels Список меток, присвоенных вещи. - * @param value Стоимость вещи. + * @param purchasePrice Цена покупки вещи. * @param createdAt Дата создания. + * @param archived Архивирована ли вещь. + * @param assetId Идентификатор актива. + * @param fields Пользовательские поля. + * @param insured Застрахована ли вещь. + * @param lifetimeWarranty Пожизненная гарантия. + * @param manufacturer Производитель. + * @param modelNumber Номер модели. + * @param notes Дополнительные заметки. + * @param parentId ID родительского элемента. + * @param purchaseFrom Место покупки. + * @param purchaseTime Время покупки. + * @param serialNumber Серийный номер. + * @param soldNotes Заметки о продаже. + * @param soldPrice Цена продажи. + * @param soldTime Время продажи. + * @param soldTo Кому продано. + * @param syncChildItemsLocations Синхронизировать местоположения дочерних элементов. + * @param warrantyDetails Детали гарантии. + * @param warrantyExpires Дата окончания гарантии. */ data class Item( val id: String, @@ -29,8 +50,27 @@ data class Item( val image: String?, val location: Location?, val labels: List