fix(field): repair installation creation and streamline mobile form
Android CI / RC / Android · lint, tests, debug APK, release compile (push) Successful in 3m4s
DH V2 CI / API · typecheck, tests, build (push) Successful in 32s
DH V2 CI / WEB · typecheck, build (push) Successful in 18s
Production dependency audit / WEB · production dependencies (push) Successful in 9s
DH V2 CI / Docker / scripts contract (push) Successful in 1m0s
Production dependency audit / API · production dependencies (push) Successful in 9s

This commit is contained in:
DH V2
2026-09-14 19:25:20 -03:00
parent 20ccba5292
commit 23450bb6d0
10 changed files with 53 additions and 10 deletions
+2 -2
View File
@@ -12,8 +12,8 @@ android {
applicationId = "com.korexlabs.dhinspeccion"
minSdk = 26
targetSdk = 36
versionCode = 33
versionName = "0.19.5"
versionCode = 34
versionName = "0.19.6"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
@@ -63,6 +63,7 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.focus.FocusDirection
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
@@ -778,6 +779,11 @@ private fun ModernFieldInventoryScreen(model: MainViewModel, onBack: () -> Unit)
enabled = !model.busy,
modifier = Modifier.fillMaxWidth().menuAnchor(MenuAnchorType.PrimaryEditable),
singleLine = true,
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
keyboardActions = KeyboardActions(onNext = {
familyExpanded = false
focusManager.moveFocus(FocusDirection.Down)
}),
)
ExposedDropdownMenu(
expanded = familyExpanded && !model.busy,
@@ -818,9 +824,11 @@ private fun ModernFieldInventoryScreen(model: MainViewModel, onBack: () -> Unit)
value = name,
onValueChange = { name = it },
label = { Text("Nombre técnico *") },
supportingText = { Text("Usá la denominación técnica del elemento. El código DH se genera automáticamente.") },
placeholder = { Text("Denominación técnica") },
modifier = Modifier.fillMaxWidth(),
singleLine = true,
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
keyboardActions = KeyboardActions(onNext = { focusManager.moveFocus(FocusDirection.Down) }),
)
OutlinedTextField(
value = commonName,
@@ -828,17 +836,24 @@ private fun ModernFieldInventoryScreen(model: MainViewModel, onBack: () -> Unit)
label = { Text("Nombre habitual (opcional)") },
modifier = Modifier.fillMaxWidth(),
singleLine = true,
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
keyboardActions = KeyboardActions(onNext = { focusManager.moveFocus(FocusDirection.Down) }),
)
OutlinedTextField(
value = description,
onValueChange = { description = it },
label = { Text("Descripción (opcional)") },
supportingText = { Text("Dato equivalente al alta desde el panel web.") },
modifier = Modifier.fillMaxWidth(),
minLines = 2,
singleLine = true,
keyboardOptions = KeyboardOptions(imeAction = if (editableAttributes.isEmpty()) ImeAction.Done else ImeAction.Next),
keyboardActions = KeyboardActions(
onNext = { focusManager.moveFocus(FocusDirection.Down) },
onDone = { keyboard?.hide(); focusManager.clearFocus() },
),
)
editableAttributes.forEach { definition ->
editableAttributes.forEachIndexed { index, definition ->
val isLastAttribute = index == editableAttributes.lastIndex
OutlinedTextField(
value = attributeValues[definition.code].orEmpty(),
onValueChange = { attributeValues[definition.code] = it },
@@ -849,8 +864,14 @@ private fun ModernFieldInventoryScreen(model: MainViewModel, onBack: () -> Unit)
},
keyboardOptions = KeyboardOptions(
keyboardType = if (definition.dataType.uppercase() in setOf("NUMBER", "DECIMAL", "INTEGER", "FLOAT")) KeyboardType.Decimal else KeyboardType.Text,
imeAction = if (isLastAttribute) ImeAction.Done else ImeAction.Next,
),
keyboardActions = KeyboardActions(
onNext = { focusManager.moveFocus(FocusDirection.Down) },
onDone = { keyboard?.hide(); focusManager.clearFocus() },
),
modifier = Modifier.fillMaxWidth(),
singleLine = true,
)
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

@@ -8,8 +8,8 @@ class ReleaseMetadataTest {
@Test
fun debugBuildKeepsSeparateApplicationIdentity() {
assertEquals("com.korexlabs.dhinspeccion.debug", BuildConfig.APPLICATION_ID)
assertEquals(33, BuildConfig.VERSION_CODE)
assertEquals("0.19.5-debug", BuildConfig.VERSION_NAME)
assertEquals(34, BuildConfig.VERSION_CODE)
assertEquals("0.19.6-debug", BuildConfig.VERSION_NAME)
}
@Test
@@ -760,6 +760,7 @@ export class AssetsService {
parentId: dto.parentId,
operationalAreaId: dto.operationalAreaId,
operatorCompanyId: dto.operatorCompanyId,
inventoryFamilyId: dto.inventoryFamilyId ?? null,
code: dto.code,
name: dto.name,
commonName: dto.commonName ?? null,
@@ -36,6 +36,10 @@ export class CreateFieldDiscoveryDto {
@IsUUID('4')
operatorCompanyId!: string;
@IsOptional()
@IsUUID('4')
inventoryFamilyId?: string | null;
@IsOptional()
@Transform(({ value }) => typeof value === 'string' && value.trim() ? value.trim() : null)
@IsString()
@@ -295,6 +295,7 @@ export class FieldInventoryService {
operationalAreaId: context.areaId,
// Compatibility-only creation snapshot. Membership never depends on it.
operatorCompanyId: context.companyId,
inventoryFamilyId: dto.familyId ?? null,
description: dto.description ?? null,
discoveryNotes: dto.discoveryNotes ?? null,
attributes: dto.attributes,
+17 -2
View File
@@ -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 = 33/);
assert.match(gradle, /versionName = "0\.19\.5"/);
assert.match(gradle, /versionCode = 34/);
assert.match(gradle, /versionName = "0\.19\.6"/);
assert.match(gradle, /https:\/\/dhv2\.korexlabs\.com\/api\/v3\//);
assert.match(gradle, /applicationIdSuffix = "\.debug"/);
});
@@ -26,3 +26,18 @@ test('F5/F6.3 field inventory exposes Other families as reviewable choices to An
assert.match(service, /AS "isOther"/);
assert.match(service, /isOtherFamily: family\.isOther/);
});
test('field inventory persists the validated Tipo de instalación in the initial asset insert', () => {
const fieldService = readFileSync(
resolve(process.cwd(), 'src/inspection-visits/field-inventory.service.ts'),
'utf8',
);
const assetsService = readFileSync(
resolve(process.cwd(), 'src/asset-master/assets.service.ts'),
'utf8',
);
assert.match(fieldService, /inventoryFamilyId: dto\.familyId \?\? null/);
assert.match(assetsService, /inventoryFamilyId: dto\.inventoryFamilyId \?\? null/);
});
+1
View File
@@ -4,6 +4,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="referrer" content="strict-origin-when-cross-origin" />
<link rel="icon" type="image/webp" href="/favicon.webp" />
<title>DH Inspección V2</title>
</head>
<body>
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB