From ff30462259f17171f27b3851cd19b497c2ba3a6b Mon Sep 17 00:00:00 2001 From: KoreX Labs Date: Mon, 14 Sep 2026 18:40:06 -0300 Subject: [PATCH] fix(android): send inventory attributes by definition id --- android-app/app/build.gradle.kts | 4 +-- .../korexlabs/dhinspeccion/MainViewModel.kt | 2 +- .../data/FieldAttributePayload.kt | 19 ++++++++++++ .../com/korexlabs/dhinspeccion/ui/DhApp.kt | 14 ++------- .../dhinspeccion/ui/DynamicVisitRoot.kt | 18 +++--------- .../korexlabs/dhinspeccion/ui/F3VisitRoot.kt | 14 ++------- .../dhinspeccion/ui/ModernVisitRoot.kt | 14 ++------- .../DynamicFieldFlowContractTest.kt | 2 +- .../dhinspeccion/FieldAttributePayloadTest.kt | 29 +++++++++++++++++++ .../dhinspeccion/ReleaseMetadataTest.kt | 2 +- api-v3/test/unit/f5-android-test-cut.test.ts | 4 +-- 11 files changed, 65 insertions(+), 57 deletions(-) create mode 100644 android-app/app/src/main/java/com/korexlabs/dhinspeccion/data/FieldAttributePayload.kt create mode 100644 android-app/app/src/test/java/com/korexlabs/dhinspeccion/FieldAttributePayloadTest.kt diff --git a/android-app/app/build.gradle.kts b/android-app/app/build.gradle.kts index d99b23b..93e0d54 100644 --- a/android-app/app/build.gradle.kts +++ b/android-app/app/build.gradle.kts @@ -12,8 +12,8 @@ android { applicationId = "com.korexlabs.dhinspeccion" minSdk = 26 targetSdk = 36 - versionCode = 32 - versionName = "0.19.4" + versionCode = 33 + versionName = "0.19.5" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true diff --git a/android-app/app/src/main/java/com/korexlabs/dhinspeccion/MainViewModel.kt b/android-app/app/src/main/java/com/korexlabs/dhinspeccion/MainViewModel.kt index fa8c9a2..3e8caf8 100644 --- a/android-app/app/src/main/java/com/korexlabs/dhinspeccion/MainViewModel.kt +++ b/android-app/app/src/main/java/com/korexlabs/dhinspeccion/MainViewModel.kt @@ -244,7 +244,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { return } if (type.familyRequired && familyId == null) { - error = "Elegí una clasificación técnica o la opción Otro / no catalogado." + error = "Elegí el Tipo de instalación o subinstalación correspondiente." return } launchBusy(mutation = true) { diff --git a/android-app/app/src/main/java/com/korexlabs/dhinspeccion/data/FieldAttributePayload.kt b/android-app/app/src/main/java/com/korexlabs/dhinspeccion/data/FieldAttributePayload.kt new file mode 100644 index 0000000..25316cb --- /dev/null +++ b/android-app/app/src/main/java/com/korexlabs/dhinspeccion/data/FieldAttributePayload.kt @@ -0,0 +1,19 @@ +package com.korexlabs.dhinspeccion.data + +/** Builds the API payload using definition IDs while the form keeps stable field codes. */ +internal fun fieldAttributePayload( + definitions: List, + valuesByCode: Map, +): Map = definitions.mapNotNull { definition -> + val raw = valuesByCode[definition.code]?.trim().orEmpty() + if (raw.isBlank()) return@mapNotNull null + definition.id to coerceFieldAttribute(definition, raw) +}.toMap() + +private fun coerceFieldAttribute(definition: FieldAttributeDefinition, raw: String): Any = + when (definition.dataType.uppercase()) { + "INTEGER", "INT" -> raw.toLongOrNull() ?: raw + "NUMBER", "DECIMAL", "FLOAT", "DOUBLE" -> raw.replace(',', '.').toDoubleOrNull() ?: raw + "BOOLEAN", "BOOL" -> raw.lowercase() in setOf("true", "1", "si", "sí", "yes") + else -> raw + } diff --git a/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/DhApp.kt b/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/DhApp.kt index 7cc4bd8..9155f0b 100644 --- a/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/DhApp.kt +++ b/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/DhApp.kt @@ -60,6 +60,7 @@ import com.korexlabs.dhinspeccion.MainViewModel import com.korexlabs.dhinspeccion.data.FieldAttributeDefinition import com.korexlabs.dhinspeccion.data.FieldInventoryItem import com.korexlabs.dhinspeccion.data.FieldType +import com.korexlabs.dhinspeccion.data.fieldAttributePayload import com.korexlabs.dhinspeccion.data.VisitDetail import com.korexlabs.dhinspeccion.data.VisitSummary import kotlinx.coroutines.launch @@ -521,18 +522,7 @@ private fun InventoryCard(item: FieldInventoryItem, canModify: Boolean, onSelect } private fun buildAttributes(type: FieldType, values: Map): Map = - type.attributes.mapNotNull { definition -> - val raw = values[definition.code]?.trim().orEmpty() - if (raw.isBlank()) return@mapNotNull null - definition.code to coerceAttribute(definition, raw) - }.toMap() - -private fun coerceAttribute(definition: FieldAttributeDefinition, raw: String): Any = when (definition.dataType.uppercase()) { - "INTEGER", "INT" -> raw.toLongOrNull() ?: raw - "NUMBER", "DECIMAL", "FLOAT", "DOUBLE" -> raw.replace(',', '.').toDoubleOrNull() ?: raw - "BOOLEAN", "BOOL" -> raw.lowercase() in setOf("true", "1", "si", "sí", "yes") - else -> raw -} + fieldAttributePayload(type.attributes, values) private fun hasPermission(context: Context, permission: String): Boolean = ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED diff --git a/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/DynamicVisitRoot.kt b/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/DynamicVisitRoot.kt index 1b6f188..d465db3 100644 --- a/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/DynamicVisitRoot.kt +++ b/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/DynamicVisitRoot.kt @@ -56,6 +56,7 @@ import com.korexlabs.dhinspeccion.MainViewModel import com.korexlabs.dhinspeccion.data.FieldAttributeDefinition import com.korexlabs.dhinspeccion.data.FieldInventoryItem import com.korexlabs.dhinspeccion.data.FieldType +import com.korexlabs.dhinspeccion.data.fieldAttributePayload import com.korexlabs.dhinspeccion.data.VisitDetail import kotlinx.coroutines.launch import kotlinx.coroutines.suspendCancellableCoroutine @@ -693,11 +694,11 @@ private fun DynamicFieldInventoryScreen(model: MainViewModel, onBack: () -> Unit } if (selectedType?.familyRequired == true) { - Text("Clasificación técnica *", fontWeight = FontWeight.Bold) + Text("Tipo de instalación *", fontWeight = FontWeight.Bold) OutlinedTextField( value = familySearch, onValueChange = { familySearch = it }, - label = { Text("Buscar clasificación") }, + label = { Text("Buscar tipo de instalación") }, supportingText = { Text("${filteredFamilies.size} opciones compatibles") }, modifier = Modifier.fillMaxWidth(), singleLine = true, @@ -906,18 +907,7 @@ private fun dynamicStatusLabel(status: String): String = when (status) { } private fun buildDynamicAttributes(type: FieldType, values: Map): Map = - type.attributes.mapNotNull { definition -> - val raw = values[definition.code]?.trim().orEmpty() - if (raw.isBlank()) return@mapNotNull null - definition.code to coerceDynamicAttribute(definition, raw) - }.toMap() - -private fun coerceDynamicAttribute(definition: FieldAttributeDefinition, raw: String): Any = when (definition.dataType.uppercase()) { - "INTEGER", "INT" -> raw.toLongOrNull() ?: raw - "NUMBER", "DECIMAL", "FLOAT", "DOUBLE" -> raw.replace(',', '.').toDoubleOrNull() ?: raw - "BOOLEAN", "BOOL" -> raw.lowercase() in setOf("true", "1", "si", "sí", "yes") - else -> raw -} + fieldAttributePayload(type.attributes, values) private fun dynamicHasPermission(context: Context, permission: String): Boolean = ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED diff --git a/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/F3VisitRoot.kt b/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/F3VisitRoot.kt index 7f137d2..c70dc29 100644 --- a/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/F3VisitRoot.kt +++ b/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/F3VisitRoot.kt @@ -57,6 +57,7 @@ import com.korexlabs.dhinspeccion.MainViewModel import com.korexlabs.dhinspeccion.data.FieldAttributeDefinition import com.korexlabs.dhinspeccion.data.FieldInventoryItem import com.korexlabs.dhinspeccion.data.FieldType +import com.korexlabs.dhinspeccion.data.fieldAttributePayload import com.korexlabs.dhinspeccion.data.VisitDetail import kotlinx.coroutines.launch import kotlinx.coroutines.suspendCancellableCoroutine @@ -720,18 +721,7 @@ private fun statusLabel(status: String): String = when (status) { } private fun buildF3Attributes(type: FieldType, values: Map): Map = - type.attributes.mapNotNull { definition -> - val raw = values[definition.code]?.trim().orEmpty() - if (raw.isBlank()) return@mapNotNull null - definition.code to coerceF3Attribute(definition, raw) - }.toMap() - -private fun coerceF3Attribute(definition: FieldAttributeDefinition, raw: String): Any = when (definition.dataType.uppercase()) { - "INTEGER", "INT" -> raw.toLongOrNull() ?: raw - "NUMBER", "DECIMAL", "FLOAT", "DOUBLE" -> raw.replace(',', '.').toDoubleOrNull() ?: raw - "BOOLEAN", "BOOL" -> raw.lowercase() in setOf("true", "1", "si", "sí", "yes") - else -> raw -} + fieldAttributePayload(type.attributes, values) private fun f3HasPermission(context: Context, permission: String): Boolean = ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED diff --git a/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/ModernVisitRoot.kt b/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/ModernVisitRoot.kt index f6b0727..d6cf377 100644 --- a/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/ModernVisitRoot.kt +++ b/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/ModernVisitRoot.kt @@ -82,6 +82,7 @@ import com.korexlabs.dhinspeccion.MainViewModel import com.korexlabs.dhinspeccion.data.FieldAttributeDefinition import com.korexlabs.dhinspeccion.data.FieldInventoryItem import com.korexlabs.dhinspeccion.data.FieldType +import com.korexlabs.dhinspeccion.data.fieldAttributePayload import com.korexlabs.dhinspeccion.data.VisitDetail import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -1099,18 +1100,7 @@ private fun modernNormalize(value: String): String = value.trim().lowercase() private fun modernStatusLabel(status: String): String = visitStatusLabelEs(status) private fun buildModernAttributes(type: FieldType, values: Map): Map = - type.attributes.mapNotNull { definition -> - val raw = values[definition.code]?.trim().orEmpty() - if (raw.isBlank()) return@mapNotNull null - definition.code to coerceModernAttribute(definition, raw) - }.toMap() - -private fun coerceModernAttribute(definition: FieldAttributeDefinition, raw: String): Any = when (definition.dataType.uppercase()) { - "INTEGER", "INT" -> raw.toLongOrNull() ?: raw - "NUMBER", "DECIMAL", "FLOAT", "DOUBLE" -> raw.replace(',', '.').toDoubleOrNull() ?: raw - "BOOLEAN", "BOOL" -> raw.lowercase() in setOf("true", "1", "si", "sí", "yes") - else -> raw -} + fieldAttributePayload(type.attributes, values) private fun modernHasPermission(context: Context, permission: String): Boolean = ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED diff --git a/android-app/app/src/test/java/com/korexlabs/dhinspeccion/DynamicFieldFlowContractTest.kt b/android-app/app/src/test/java/com/korexlabs/dhinspeccion/DynamicFieldFlowContractTest.kt index 5e96fd8..9d629f4 100644 --- a/android-app/app/src/test/java/com/korexlabs/dhinspeccion/DynamicFieldFlowContractTest.kt +++ b/android-app/app/src/test/java/com/korexlabs/dhinspeccion/DynamicFieldFlowContractTest.kt @@ -10,7 +10,7 @@ class DynamicFieldFlowContractTest { @Test fun fastSubinstallationFlowKeepsParentClassificationAndCaptureSteps() { assertTrue(source.contains("Elegí la Instalación padre")) - assertTrue(source.contains("Clasificación técnica *")) + assertTrue(source.contains("Tipo de instalación *")) assertTrue(source.contains("Guardar y tomar foto")) assertTrue(source.contains("model.loadFieldTypes(item.id)")) assertTrue(source.contains("parentId = item.id")) diff --git a/android-app/app/src/test/java/com/korexlabs/dhinspeccion/FieldAttributePayloadTest.kt b/android-app/app/src/test/java/com/korexlabs/dhinspeccion/FieldAttributePayloadTest.kt new file mode 100644 index 0000000..2665f7d --- /dev/null +++ b/android-app/app/src/test/java/com/korexlabs/dhinspeccion/FieldAttributePayloadTest.kt @@ -0,0 +1,29 @@ +package com.korexlabs.dhinspeccion + +import com.korexlabs.dhinspeccion.data.FieldAttributeDefinition +import com.korexlabs.dhinspeccion.data.fieldAttributePayload +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test + +class FieldAttributePayloadTest { + @Test + fun payloadUsesDefinitionIdsExpectedByApiAndKeepsTypedValues() { + val definitions = listOf( + FieldAttributeDefinition("id-marca", "campo_marca", "Marca", "TEXT"), + FieldAttributeDefinition("id-capacidad", "campo_capacidad", "Capacidad", "NUMBER"), + FieldAttributeDefinition("id-serie", "campo_numero_serie", "Número de serie", "TEXT"), + ) + + val payload = fieldAttributePayload( + definitions, + mapOf("campo_marca" to " algo ", "campo_capacidad" to "37,73", "campo_numero_serie" to " "), + ) + + assertEquals("algo", payload["id-marca"]) + assertEquals(37.73, payload["id-capacidad"]) + assertFalse(payload.containsKey("id-serie")) + assertTrue(payload.keys.none { it.startsWith("campo_") }) + } +} diff --git a/android-app/app/src/test/java/com/korexlabs/dhinspeccion/ReleaseMetadataTest.kt b/android-app/app/src/test/java/com/korexlabs/dhinspeccion/ReleaseMetadataTest.kt index 32ce11a..dbf446a 100644 --- a/android-app/app/src/test/java/com/korexlabs/dhinspeccion/ReleaseMetadataTest.kt +++ b/android-app/app/src/test/java/com/korexlabs/dhinspeccion/ReleaseMetadataTest.kt @@ -9,7 +9,7 @@ class ReleaseMetadataTest { fun debugBuildKeepsSeparateApplicationIdentity() { assertEquals("com.korexlabs.dhinspeccion.debug", BuildConfig.APPLICATION_ID) assertEquals(32, BuildConfig.VERSION_CODE) - assertEquals("0.19.4-debug", BuildConfig.VERSION_NAME) + assertEquals("0.19.5-debug", BuildConfig.VERSION_NAME) } @Test diff --git a/api-v3/test/unit/f5-android-test-cut.test.ts b/api-v3/test/unit/f5-android-test-cut.test.ts index 0df88fa..295b560 100644 --- a/api-v3/test/unit/f5-android-test-cut.test.ts +++ b/api-v3/test/unit/f5-android-test-cut.test.ts @@ -10,8 +10,8 @@ function mountedRepoFile(path: string): string { test('F6.3 Android test cut targets production API and has a distinct installable debug version', () => { const gradle = mountedRepoFile('android-app/app/build.gradle.kts'); - assert.match(gradle, /versionCode = 32/); - assert.match(gradle, /versionName = "0\.19\.4"/); + assert.match(gradle, /versionCode = 33/); + assert.match(gradle, /versionName = "0\.19\.5"/); assert.match(gradle, /https:\/\/dhv2\.korexlabs\.com\/api\/v3\//); assert.match(gradle, /applicationIdSuffix = "\.debug"/); });