F6.9 capture act narrative before seal in offline Android flow
Android CI / RC / Android · lint, tests, debug APK, release compile (push) Successful in 3m11s
DH V2 CI / API · typecheck, tests, build (push) Failing after 25s
DH V2 CI / WEB · typecheck, build (push) Successful in 18s
DH V2 CI / Docker / scripts contract (push) Skipped
Production dependency audit / API · production dependencies (push) Successful in 9s
Production dependency audit / WEB · production dependencies (push) Successful in 8s
Android CI / RC / Android · lint, tests, debug APK, release compile (push) Successful in 3m11s
DH V2 CI / API · typecheck, tests, build (push) Failing after 25s
DH V2 CI / WEB · typecheck, build (push) Successful in 18s
DH V2 CI / Docker / scripts contract (push) Skipped
Production dependency audit / API · production dependencies (push) Successful in 9s
Production dependency audit / WEB · production dependencies (push) Successful in 8s
This commit is contained in:
+11
-1
@@ -1,7 +1,17 @@
|
|||||||
# DH Inspección Android · release final de campo 0.19.10
|
# DH Inspección Android · release de campo 0.19.11
|
||||||
|
|
||||||
## Candidata vigente
|
## Candidata vigente
|
||||||
|
|
||||||
|
- `versionName`: **0.19.11**; `versionCode`: **39**.
|
||||||
|
- API: `https://dhv2.korexlabs.com/api/v3/`; compatible con API 0.29.0-9 / WEB 0.23.0-6.
|
||||||
|
- Antes de cerrar el contenido, el inspector escribe una descripción real de lo actuado.
|
||||||
|
- La descripción se sincroniza antes del bloqueo, también cuando se trabajó sin conexión.
|
||||||
|
- Un acta anterior, ya sellada, conserva el texto originalmente registrado.
|
||||||
|
|
||||||
|
## Referencia de 0.19.10
|
||||||
|
|
||||||
|
### Candidata anterior
|
||||||
|
|
||||||
- Fase funcional: **Flujo final de campo · Inspección → Acta → Hallazgos → Firma**.
|
- Fase funcional: **Flujo final de campo · Inspección → Acta → Hallazgos → Firma**.
|
||||||
- `versionName`: **0.19.10**.
|
- `versionName`: **0.19.10**.
|
||||||
- `versionCode`: **38**.
|
- `versionCode`: **38**.
|
||||||
|
|||||||
@@ -8,13 +8,14 @@ plugins {
|
|||||||
android {
|
android {
|
||||||
namespace = "com.korexlabs.dhinspeccion"
|
namespace = "com.korexlabs.dhinspeccion"
|
||||||
compileSdk = 36
|
compileSdk = 36
|
||||||
|
buildToolsVersion = "36.0.0"
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "com.korexlabs.dhinspeccion"
|
applicationId = "com.korexlabs.dhinspeccion"
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 38
|
versionCode = 39
|
||||||
versionName = "0.19.10"
|
versionName = "0.19.11"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
|
|||||||
@@ -260,7 +260,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
status = "DRAFT",
|
status = "DRAFT",
|
||||||
occurredAt = occurredAt,
|
occurredAt = occurredAt,
|
||||||
title = "Acta de inspección ${currentVisit.code}",
|
title = "Acta de inspección ${currentVisit.code}",
|
||||||
summary = "Acta de inspección en curso. Los Hallazgos y observaciones se incorporan de forma trazable durante la inspección.",
|
summary = "Se inició la inspección ${currentVisit.code}.",
|
||||||
)
|
)
|
||||||
selectedAct = local
|
selectedAct = local
|
||||||
acts = acts + MobileActSummary(
|
acts = acts + MobileActSummary(
|
||||||
@@ -733,16 +733,22 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun prepareSelectedAct(urgency: String) {
|
fun prepareSelectedAct(urgency: String, narrative: String) {
|
||||||
val actId = selectedAct?.id ?: return
|
val actId = selectedAct?.id ?: return
|
||||||
if (urgency !in setOf("URGENT", "NON_URGENT")) {
|
if (urgency !in setOf("URGENT", "NON_URGENT")) {
|
||||||
error = "Definí la urgencia antes de cerrar el Acta."
|
error = "Definí la urgencia antes de cerrar el Acta."
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
val summary = narrative.trim()
|
||||||
|
if (summary.length !in 10..20000) {
|
||||||
|
error = "Describí lo actuado antes de cerrar el Acta (entre 10 y 20000 caracteres)."
|
||||||
|
return
|
||||||
|
}
|
||||||
launchBusy(mutation = true) {
|
launchBusy(mutation = true) {
|
||||||
|
offlineQueue.enqueueActSummary(actId, summary)
|
||||||
offlineQueue.enqueueActLock(actId, urgency)
|
offlineQueue.enqueueActLock(actId, urgency)
|
||||||
selectedAct = selectedAct?.copy(status = "LOCKED", urgency = urgency)
|
selectedAct = selectedAct?.copy(status = "LOCKED", urgency = urgency, summary = summary)
|
||||||
acts = acts.map { if (it.id == actId) it.copy(status = "LOCKED", urgency = urgency) else it }
|
acts = acts.map { if (it.id == actId) it.copy(status = "LOCKED", urgency = urgency, summary = summary) else it }
|
||||||
actClosure = actClosure?.copy(act = actClosure!!.act.copy(status = "LOCKED", urgency = urgency))
|
actClosure = actClosure?.copy(act = actClosure!!.act.copy(status = "LOCKED", urgency = urgency))
|
||||||
val result = offlineQueue.syncAll()
|
val result = offlineQueue.syncAll()
|
||||||
refreshOfflineStateInternal()
|
refreshOfflineStateInternal()
|
||||||
|
|||||||
@@ -106,7 +106,8 @@ data class PrepareMobileActRequest(
|
|||||||
)
|
)
|
||||||
|
|
||||||
data class UpdateMobileActRequest(
|
data class UpdateMobileActRequest(
|
||||||
val assetIds: List<String>,
|
val assetIds: List<String>? = null,
|
||||||
|
val summary: String? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
data class MobileResponsibleRequest(
|
data class MobileResponsibleRequest(
|
||||||
@@ -367,7 +368,7 @@ class MobileActsRepository(context: Context) {
|
|||||||
clientGeneratedId = clientGeneratedId,
|
clientGeneratedId = clientGeneratedId,
|
||||||
occurredAt = occurredAt,
|
occurredAt = occurredAt,
|
||||||
title = "Acta de inspección $visitCode",
|
title = "Acta de inspección $visitCode",
|
||||||
summary = "Acta de inspección en curso. Los Hallazgos y observaciones se incorporan de forma trazable durante la inspección.",
|
summary = "Se inició la inspección $visitCode.",
|
||||||
assetIds = emptyList(),
|
assetIds = emptyList(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -376,6 +377,10 @@ class MobileActsRepository(context: Context) {
|
|||||||
return created
|
return created
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun updateSummary(actId: String, summary: String): MobileActDetail = authorized { session ->
|
||||||
|
api.updateAct("Bearer ${session.accessToken}", actId, UpdateMobileActRequest(summary = summary))
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun ensureAsset(actId: String, assetId: String): MobileActDetail {
|
suspend fun ensureAsset(actId: String, assetId: String): MobileActDetail {
|
||||||
val detail = get(actId)
|
val detail = get(actId)
|
||||||
if (detail.assets.any { it.id == assetId }) return detail
|
if (detail.assets.any { it.id == assetId }) return detail
|
||||||
|
|||||||
@@ -256,6 +256,12 @@ class OfflineMutationQueue(private val context: Context) {
|
|||||||
.toString(),
|
.toString(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
suspend fun enqueueActSummary(actId: String, summary: String): String = enqueueJson(
|
||||||
|
type = "ACT_SUMMARY",
|
||||||
|
resourceId = actId,
|
||||||
|
payload = JSONObject().put("summary", summary).toString(),
|
||||||
|
)
|
||||||
|
|
||||||
suspend fun enqueueActAsset(actId: String, assetId: String): String = enqueueJson(
|
suspend fun enqueueActAsset(actId: String, assetId: String): String = enqueueJson(
|
||||||
type = "ACT_ASSET_ENSURE",
|
type = "ACT_ASSET_ENSURE",
|
||||||
resourceId = actId,
|
resourceId = actId,
|
||||||
@@ -450,6 +456,10 @@ class OfflineMutationQueue(private val context: Context) {
|
|||||||
clientGeneratedId = operation.resourceId,
|
clientGeneratedId = operation.resourceId,
|
||||||
occurredAt = payload.getString("occurredAt"),
|
occurredAt = payload.getString("occurredAt"),
|
||||||
)
|
)
|
||||||
|
"ACT_SUMMARY" -> MobileActsRepository(context).updateSummary(
|
||||||
|
operation.resourceId,
|
||||||
|
payload.getString("summary"),
|
||||||
|
)
|
||||||
"ACT_ASSET_ENSURE" -> MobileActsRepository(context).ensureAsset(
|
"ACT_ASSET_ENSURE" -> MobileActsRepository(context).ensureAsset(
|
||||||
operation.resourceId,
|
operation.resourceId,
|
||||||
payload.getString("assetId"),
|
payload.getString("assetId"),
|
||||||
|
|||||||
@@ -62,6 +62,9 @@ fun MobileActsScreen(
|
|||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
var closingUrgency by rememberSaveable(selected?.id) { mutableStateOf("") }
|
var closingUrgency by rememberSaveable(selected?.id) { mutableStateOf("") }
|
||||||
|
var actNarrative by rememberSaveable(selected?.id) {
|
||||||
|
mutableStateOf(selected?.summary.takeUnless { it == null || it.startsWith("Se inició la inspección ") || it.startsWith("Acta de inspección en curso.") }.orEmpty())
|
||||||
|
}
|
||||||
var attendance by rememberSaveable(selected?.id) {
|
var attendance by rememberSaveable(selected?.id) {
|
||||||
mutableStateOf(representativeSeed?.attendanceStatus ?: "PRESENT")
|
mutableStateOf(representativeSeed?.attendanceStatus ?: "PRESENT")
|
||||||
}
|
}
|
||||||
@@ -233,6 +236,13 @@ fun MobileActsScreen(
|
|||||||
HorizontalDivider()
|
HorizontalDivider()
|
||||||
Text("Finalizar contenido", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold)
|
Text("Finalizar contenido", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold)
|
||||||
Text("Al cerrar el contenido, el Acta queda inmutable y pasa a Para firmar. La firma del inspector se toma de Mi perfil del Dashboard; en la tablet firma el acompañante de la empresa.")
|
Text("Al cerrar el contenido, el Acta queda inmutable y pasa a Para firmar. La firma del inspector se toma de Mi perfil del Dashboard; en la tablet firma el acompañante de la empresa.")
|
||||||
|
OutlinedTextField(
|
||||||
|
actNarrative,
|
||||||
|
{ actNarrative = it },
|
||||||
|
label = { Text("Descripción de lo actuado *") },
|
||||||
|
minLines = 4,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
)
|
||||||
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
AssistChip(
|
AssistChip(
|
||||||
onClick = { closingUrgency = "NON_URGENT" },
|
onClick = { closingUrgency = "NON_URGENT" },
|
||||||
@@ -244,8 +254,8 @@ fun MobileActsScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
Button(
|
Button(
|
||||||
onClick = { model.prepareSelectedAct(closingUrgency) },
|
onClick = { model.prepareSelectedAct(closingUrgency, actNarrative) },
|
||||||
enabled = !model.busy && closure?.responsible != null && closingUrgency.isNotBlank(),
|
enabled = !model.busy && closure?.responsible != null && closingUrgency.isNotBlank() && actNarrative.trim().length in 10..20000,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
) { Text("Cerrar contenido y pasar a firma") }
|
) { Text("Cerrar contenido y pasar a firma") }
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-2
@@ -76,6 +76,9 @@ fun ModernMobileActsScreen(
|
|||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
var closingUrgency by rememberSaveable(selected?.id) { mutableStateOf("") }
|
var closingUrgency by rememberSaveable(selected?.id) { mutableStateOf("") }
|
||||||
|
var actNarrative by rememberSaveable(selected?.id) {
|
||||||
|
mutableStateOf(selected?.summary.takeUnless { it == null || it.startsWith("Se inició la inspección ") || it.startsWith("Acta de inspección en curso.") }.orEmpty())
|
||||||
|
}
|
||||||
var fullName by rememberSaveable(selected?.id) { mutableStateOf(representativeSeed?.fullName.orEmpty()) }
|
var fullName by rememberSaveable(selected?.id) { mutableStateOf(representativeSeed?.fullName.orEmpty()) }
|
||||||
var documentType by rememberSaveable(selected?.id) { mutableStateOf("DNI") }
|
var documentType by rememberSaveable(selected?.id) { mutableStateOf("DNI") }
|
||||||
var documentNumber by rememberSaveable(selected?.id) { mutableStateOf(representativeSeed?.documentNumber.orEmpty()) }
|
var documentNumber by rememberSaveable(selected?.id) { mutableStateOf(representativeSeed?.documentNumber.orEmpty()) }
|
||||||
@@ -307,6 +310,14 @@ fun ModernMobileActsScreen(
|
|||||||
"Al cerrar el contenido, el Acta queda inmutable y pasa a Para firmar. La firma del inspector se toma de Mi perfil del Dashboard; en la tablet sólo firma el acompañante de la empresa. La urgencia pertenece al Acta completa. La urgencia no se asigna a cada Hallazgo.",
|
"Al cerrar el contenido, el Acta queda inmutable y pasa a Para firmar. La firma del inspector se toma de Mi perfil del Dashboard; en la tablet sólo firma el acompañante de la empresa. La urgencia pertenece al Acta completa. La urgencia no se asigna a cada Hallazgo.",
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
)
|
)
|
||||||
|
OutlinedTextField(
|
||||||
|
actNarrative,
|
||||||
|
{ actNarrative = it },
|
||||||
|
label = { Text("Descripción de lo actuado *") },
|
||||||
|
supportingText = { Text("Relatá la inspección y sus observaciones. Se incorpora al acta antes de sellarla.") },
|
||||||
|
minLines = 4,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
)
|
||||||
Text("Urgencia del Acta", fontWeight = FontWeight.SemiBold)
|
Text("Urgencia del Acta", fontWeight = FontWeight.SemiBold)
|
||||||
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
FilterChip(
|
FilterChip(
|
||||||
@@ -330,8 +341,8 @@ fun ModernMobileActsScreen(
|
|||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
)
|
)
|
||||||
Button(
|
Button(
|
||||||
onClick = { model.prepareSelectedAct(closingUrgency) },
|
onClick = { model.prepareSelectedAct(closingUrgency, actNarrative) },
|
||||||
enabled = !model.busy && closure?.responsible != null && closingUrgency.isNotBlank(),
|
enabled = !model.busy && closure?.responsible != null && closingUrgency.isNotBlank() && actNarrative.trim().length in 10..20000,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
) { Text("Cerrar contenido y pasar a firma") }
|
) { Text("Cerrar contenido y pasar a firma") }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ class ReleaseMetadataTest {
|
|||||||
@Test
|
@Test
|
||||||
fun debugBuildKeepsSeparateApplicationIdentity() {
|
fun debugBuildKeepsSeparateApplicationIdentity() {
|
||||||
assertEquals("com.korexlabs.dhinspeccion.debug", BuildConfig.APPLICATION_ID)
|
assertEquals("com.korexlabs.dhinspeccion.debug", BuildConfig.APPLICATION_ID)
|
||||||
assertEquals(38, BuildConfig.VERSION_CODE)
|
assertEquals(39, BuildConfig.VERSION_CODE)
|
||||||
assertEquals("0.19.10-debug", BuildConfig.VERSION_NAME)
|
assertEquals("0.19.11-debug", BuildConfig.VERSION_NAME)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user