F3.2 Android: incorporar estado multi-Acta y cierre
This commit is contained in:
@@ -16,6 +16,11 @@ import com.korexlabs.dhinspeccion.data.FieldFindingOptionsResponse
|
||||
import com.korexlabs.dhinspeccion.data.FieldFindingsRepository
|
||||
import com.korexlabs.dhinspeccion.data.FieldInventoryItem
|
||||
import com.korexlabs.dhinspeccion.data.FieldType
|
||||
import com.korexlabs.dhinspeccion.data.MobileActClosure
|
||||
import com.korexlabs.dhinspeccion.data.MobileActDetail
|
||||
import com.korexlabs.dhinspeccion.data.MobileActSummary
|
||||
import com.korexlabs.dhinspeccion.data.MobileActsRepository
|
||||
import com.korexlabs.dhinspeccion.data.MobileResponsibleRequest
|
||||
import com.korexlabs.dhinspeccion.data.StoredSession
|
||||
import com.korexlabs.dhinspeccion.data.VisitDetail
|
||||
import com.korexlabs.dhinspeccion.data.VisitSummary
|
||||
@@ -26,6 +31,7 @@ import java.time.Instant
|
||||
class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
private val repository = DhRepository(application)
|
||||
private val findingsRepository = FieldFindingsRepository(application)
|
||||
private val actsRepository = MobileActsRepository(application)
|
||||
|
||||
var session: StoredSession? by mutableStateOf(repository.currentSession())
|
||||
private set
|
||||
@@ -48,6 +54,13 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
var selectedFieldAsset: FieldAssetDetail? by mutableStateOf(null)
|
||||
private set
|
||||
|
||||
var acts: List<MobileActSummary> by mutableStateOf(emptyList())
|
||||
private set
|
||||
var selectedAct: MobileActDetail? by mutableStateOf(null)
|
||||
private set
|
||||
var actClosure: MobileActClosure? by mutableStateOf(null)
|
||||
private set
|
||||
|
||||
var fieldFindingOptions: FieldFindingOptionsResponse? by mutableStateOf(null)
|
||||
private set
|
||||
var lastCreatedFinding: FieldFindingItem? by mutableStateOf(null)
|
||||
@@ -85,6 +98,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
inventory = emptyList()
|
||||
fieldTypes = emptyList()
|
||||
selectedFieldAsset = null
|
||||
clearActState()
|
||||
clearFindingState()
|
||||
}
|
||||
}
|
||||
@@ -101,6 +115,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
fieldTypes = emptyList()
|
||||
selectedFieldAsset = null
|
||||
clearFindingState()
|
||||
loadActsInternal(id, selectDraft = true)
|
||||
}
|
||||
|
||||
fun closeVisitView() {
|
||||
@@ -108,6 +123,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
inventory = emptyList()
|
||||
fieldTypes = emptyList()
|
||||
selectedFieldAsset = null
|
||||
clearActState()
|
||||
clearFindingState()
|
||||
loadVisits()
|
||||
}
|
||||
@@ -117,10 +133,51 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
launchBusy {
|
||||
visit = repository.startVisit(id)
|
||||
notice = "Inspección iniciada."
|
||||
loadActsInternal(id, selectDraft = true)
|
||||
loadVisitsInternal()
|
||||
}
|
||||
}
|
||||
|
||||
fun reloadActs() {
|
||||
val visitId = visit?.id ?: return
|
||||
launchBusy { loadActsInternal(visitId, selectDraft = selectedAct == null) }
|
||||
}
|
||||
|
||||
fun selectAct(actId: String) {
|
||||
launchBusy {
|
||||
selectedAct = actsRepository.get(actId)
|
||||
actClosure = actsRepository.closure(actId)
|
||||
clearFindingState()
|
||||
}
|
||||
}
|
||||
|
||||
fun createActForSelectedInventory() {
|
||||
val currentVisit = visit ?: return
|
||||
val asset = selectedFieldAsset?.asset
|
||||
if (currentVisit.status != "IN_PROGRESS") {
|
||||
error = "La inspección debe estar en curso para crear un Acta."
|
||||
return
|
||||
}
|
||||
if (asset == null) {
|
||||
error = "Seleccioná primero una Instalación o Subinstalación para iniciar el Acta."
|
||||
return
|
||||
}
|
||||
if (acts.any { it.status == "DRAFT" }) {
|
||||
error = "Ya existe un Acta en borrador. Cerrala o cancelala antes de crear la siguiente."
|
||||
return
|
||||
}
|
||||
launchBusy {
|
||||
val created = actsRepository.create(currentVisit.id, asset.id, currentVisit.code)
|
||||
selectedAct = created
|
||||
actClosure = actsRepository.closure(created.id)
|
||||
loadActsInternal(currentVisit.id, selectDraft = false)
|
||||
notice = "${created.code} creada. Los Hallazgos nuevos quedarán vinculados explícitamente a esta Acta."
|
||||
if (selectedFieldAsset?.capture?.readyForFinding == true) {
|
||||
loadFindingOptionsInternal(currentVisit.id, asset.id, created.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun searchInventory(search: String, parentId: String? = null) {
|
||||
val id = visit?.id ?: return
|
||||
launchBusy {
|
||||
@@ -141,8 +198,13 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
selectedFieldAsset = repository.selectFieldAsset(visitId, item.id)
|
||||
notice = "Inventario agregado a la inspección."
|
||||
inventory = repository.fieldInventory(visitId, null, null).data
|
||||
if (selectedFieldAsset?.capture?.readyForFinding == true) {
|
||||
loadFindingOptionsInternal(visitId, item.id)
|
||||
val draft = selectedDraftAct()
|
||||
if (selectedFieldAsset?.capture?.readyForFinding == true && draft != null) {
|
||||
selectedAct = actsRepository.ensureAsset(draft.id, item.id)
|
||||
loadActsInternal(visitId, selectDraft = false)
|
||||
loadFindingOptionsInternal(visitId, item.id, draft.id)
|
||||
} else if (selectedFieldAsset?.capture?.readyForFinding == true) {
|
||||
notice = "Inventario listo. Creá o seleccioná un Acta antes de registrar Hallazgos."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -207,8 +269,11 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
selectedFieldAsset = repository.selectFieldAsset(visitId, result.canonical.id)
|
||||
notice = "Fusión registrada. Se conserva ${result.canonical.code} y la historia de ${result.source.code} permanece trazable."
|
||||
inventory = repository.fieldInventory(visitId, null, null).data
|
||||
if (selectedFieldAsset?.capture?.readyForFinding == true) {
|
||||
loadFindingOptionsInternal(visitId, result.canonical.id)
|
||||
val draft = selectedDraftAct()
|
||||
if (selectedFieldAsset?.capture?.readyForFinding == true && draft != null) {
|
||||
selectedAct = actsRepository.ensureAsset(draft.id, result.canonical.id)
|
||||
loadActsInternal(visitId, selectDraft = false)
|
||||
loadFindingOptionsInternal(visitId, result.canonical.id, draft.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -237,8 +302,13 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
"Fotografía registrada."
|
||||
}
|
||||
inventory = repository.fieldInventory(visitId, null, null).data
|
||||
if (response.capture.readyForFinding) {
|
||||
loadFindingOptionsInternal(visitId, asset.id)
|
||||
val draft = selectedDraftAct()
|
||||
if (response.capture.readyForFinding && draft != null) {
|
||||
selectedAct = actsRepository.ensureAsset(draft.id, asset.id)
|
||||
loadActsInternal(visitId, selectDraft = false)
|
||||
loadFindingOptionsInternal(visitId, asset.id, draft.id)
|
||||
} else if (response.capture.readyForFinding) {
|
||||
notice = "Inventario listo. Creá o seleccioná un Acta antes de registrar Hallazgos."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -246,7 +316,16 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
fun openFindingForSelected() {
|
||||
val visitId = visit?.id ?: return
|
||||
val assetId = selectedFieldAsset?.asset?.id ?: return
|
||||
launchBusy { loadFindingOptionsInternal(visitId, assetId) }
|
||||
val draft = selectedDraftAct()
|
||||
if (draft == null) {
|
||||
error = "Creá o seleccioná el Acta en borrador antes de registrar Hallazgos."
|
||||
return
|
||||
}
|
||||
launchBusy {
|
||||
selectedAct = actsRepository.ensureAsset(draft.id, assetId)
|
||||
loadActsInternal(visitId, selectDraft = false)
|
||||
loadFindingOptionsInternal(visitId, assetId, draft.id)
|
||||
}
|
||||
}
|
||||
|
||||
fun createFieldFinding(
|
||||
@@ -259,6 +338,11 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
) {
|
||||
val visitId = visit?.id ?: return
|
||||
val assetId = selectedFieldAsset?.asset?.id ?: return
|
||||
val actId = selectedDraftAct()?.id
|
||||
if (actId == null) {
|
||||
error = "No hay un Acta en borrador seleccionada."
|
||||
return
|
||||
}
|
||||
if (description.isBlank()) {
|
||||
error = "Describí el Hallazgo antes de guardarlo."
|
||||
return
|
||||
@@ -272,10 +356,12 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
return
|
||||
}
|
||||
launchBusy {
|
||||
selectedAct = actsRepository.ensureAsset(actId, assetId)
|
||||
val response = findingsRepository.create(
|
||||
visitId,
|
||||
assetId,
|
||||
CreateFieldFindingRequest(
|
||||
actId = actId,
|
||||
catalogItemId = catalogItemId,
|
||||
customTitle = customTitle?.trim()?.takeIf { it.isNotBlank() },
|
||||
customLegalBasis = customLegalBasis?.trim()?.takeIf { it.isNotBlank() },
|
||||
@@ -285,8 +371,9 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
),
|
||||
)
|
||||
lastCreatedFinding = response.finding
|
||||
notice = "Hallazgo ${response.finding.code} registrado. Podés agregar evidencia fotográfica."
|
||||
loadFindingOptionsInternal(visitId, assetId, keepLastCreated = true)
|
||||
notice = "Hallazgo ${response.finding.code} registrado en ${response.act.code}. Podés agregar evidencia fotográfica."
|
||||
loadFindingOptionsInternal(visitId, assetId, actId, keepLastCreated = true)
|
||||
loadActsInternal(visitId, selectDraft = false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,6 +405,139 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
launchBusy { loadEvidenceInternal(findingId) }
|
||||
}
|
||||
|
||||
fun setCompanyResponsiblePresent(
|
||||
fullName: String,
|
||||
documentType: String,
|
||||
documentNumber: String,
|
||||
position: String,
|
||||
email: String?,
|
||||
phone: String?,
|
||||
) {
|
||||
val actId = selectedAct?.id ?: return
|
||||
if (fullName.isBlank() || documentNumber.isBlank() || position.isBlank()) {
|
||||
error = "Completá nombre, documento y cargo del responsable de la empresa."
|
||||
return
|
||||
}
|
||||
launchBusy {
|
||||
actClosure = actsRepository.setResponsible(
|
||||
actId,
|
||||
MobileResponsibleRequest(
|
||||
attendanceStatus = "PRESENT",
|
||||
fullName = fullName.trim(),
|
||||
documentType = documentType,
|
||||
documentNumber = documentNumber.trim(),
|
||||
position = position.trim(),
|
||||
email = email?.trim()?.takeIf { it.isNotBlank() },
|
||||
phone = phone?.trim()?.takeIf { it.isNotBlank() },
|
||||
),
|
||||
)
|
||||
notice = "Responsable de empresa registrado para el Acta."
|
||||
}
|
||||
}
|
||||
|
||||
fun setCompanyResponsibleAbsent(reason: String) {
|
||||
val actId = selectedAct?.id ?: return
|
||||
if (reason.trim().length < 10) {
|
||||
error = "Indicá un motivo de ausencia de al menos 10 caracteres."
|
||||
return
|
||||
}
|
||||
launchBusy {
|
||||
actClosure = actsRepository.setResponsible(
|
||||
actId,
|
||||
MobileResponsibleRequest(
|
||||
attendanceStatus = "ABSENT",
|
||||
absenceReason = reason.trim(),
|
||||
),
|
||||
)
|
||||
notice = "Ausencia del responsable registrada."
|
||||
}
|
||||
}
|
||||
|
||||
fun prepareSelectedAct() {
|
||||
val actId = selectedAct?.id ?: return
|
||||
launchBusy {
|
||||
actClosure = actsRepository.prepare(actId)
|
||||
refreshSelectedActInternal(actId)
|
||||
notice = "Acta preparada. Su contenido quedó congelado para las firmas."
|
||||
}
|
||||
}
|
||||
|
||||
fun reopenSelectedAct() {
|
||||
val actId = selectedAct?.id ?: return
|
||||
launchBusy {
|
||||
actClosure = actsRepository.reopen(actId)
|
||||
refreshSelectedActInternal(actId)
|
||||
notice = "Acta reabierta. Podés corregirla antes de volver a preparar."
|
||||
}
|
||||
}
|
||||
|
||||
fun signSelectedActAsInspector(
|
||||
png: File,
|
||||
latitude: Double?,
|
||||
longitude: Double?,
|
||||
accuracyM: Double?,
|
||||
) {
|
||||
val actId = selectedAct?.id ?: return
|
||||
launchBusy {
|
||||
actClosure = actsRepository.signInspector(actId, png, latitude, longitude, accuracyM)
|
||||
notice = "Firma del inspector incorporada al Acta."
|
||||
}
|
||||
}
|
||||
|
||||
fun signSelectedActAsCompany(
|
||||
png: File,
|
||||
latitude: Double?,
|
||||
longitude: Double?,
|
||||
accuracyM: Double?,
|
||||
manifestation: String,
|
||||
statement: String?,
|
||||
) {
|
||||
val actId = selectedAct?.id ?: return
|
||||
launchBusy {
|
||||
actClosure = actsRepository.signCompany(
|
||||
actId, png, latitude, longitude, accuracyM, manifestation, statement,
|
||||
)
|
||||
notice = if (manifestation == "DISSENT") {
|
||||
"Firma de empresa registrada con disidencia."
|
||||
} else {
|
||||
"Firma de empresa registrada."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun recordCompanyOutcome(status: String, reason: String) {
|
||||
val actId = selectedAct?.id ?: return
|
||||
if (reason.trim().length < 10) {
|
||||
error = "Indicá un motivo de al menos 10 caracteres."
|
||||
return
|
||||
}
|
||||
launchBusy {
|
||||
actClosure = actsRepository.companyOutcome(actId, status, reason)
|
||||
notice = if (status == "ABSENT") "Ausencia de empresa asentada." else "Negativa a firmar asentada."
|
||||
}
|
||||
}
|
||||
|
||||
fun closeSelectedAct() {
|
||||
val currentVisit = visit ?: return
|
||||
val actId = selectedAct?.id ?: return
|
||||
launchBusy {
|
||||
actClosure = actsRepository.closeAct(actId)
|
||||
refreshSelectedActInternal(actId)
|
||||
loadActsInternal(currentVisit.id, selectDraft = false)
|
||||
clearFindingState()
|
||||
notice = "${selectedAct?.code ?: "Acta"} cerrada e inmutable. Podés crear otra Acta o finalizar la inspección."
|
||||
}
|
||||
}
|
||||
|
||||
fun closeInspection() {
|
||||
val visitId = visit?.id ?: return
|
||||
launchBusy {
|
||||
visit = actsRepository.closeVisit(visitId)
|
||||
loadVisitsInternal()
|
||||
notice = "Inspección cerrada. Las Actas y documentos quedan disponibles para oficina."
|
||||
}
|
||||
}
|
||||
|
||||
fun clearFindingFlow() {
|
||||
fieldFindingOptions = null
|
||||
lastCreatedFinding = null
|
||||
@@ -330,12 +550,43 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
clearFindingState()
|
||||
}
|
||||
|
||||
private fun selectedDraftAct(): MobileActDetail? =
|
||||
selectedAct?.takeIf { it.status == "DRAFT" }
|
||||
?: acts.firstOrNull { it.status == "DRAFT" }?.let { summary ->
|
||||
selectedAct?.takeIf { it.id == summary.id && it.status == "DRAFT" }
|
||||
}
|
||||
|
||||
private suspend fun loadActsInternal(visitId: String, selectDraft: Boolean) {
|
||||
acts = actsRepository.list(visitId).data
|
||||
val currentId = selectedAct?.id
|
||||
val current = currentId?.let { id -> acts.firstOrNull { it.id == id } }
|
||||
val target = when {
|
||||
current != null -> current.id
|
||||
selectDraft -> acts.firstOrNull { it.status == "DRAFT" }?.id
|
||||
else -> null
|
||||
}
|
||||
if (target != null) {
|
||||
selectedAct = actsRepository.get(target)
|
||||
actClosure = actsRepository.closure(target)
|
||||
} else if (current == null) {
|
||||
selectedAct = null
|
||||
actClosure = null
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun refreshSelectedActInternal(actId: String) {
|
||||
selectedAct = actsRepository.get(actId)
|
||||
actClosure = actsRepository.closure(actId)
|
||||
visit?.id?.let { loadActsInternal(it, selectDraft = false) }
|
||||
}
|
||||
|
||||
private suspend fun loadFindingOptionsInternal(
|
||||
visitId: String,
|
||||
assetId: String,
|
||||
actId: String,
|
||||
keepLastCreated: Boolean = false,
|
||||
) {
|
||||
val options = findingsRepository.options(visitId, assetId)
|
||||
val options = findingsRepository.options(visitId, assetId, actId)
|
||||
fieldFindingOptions = options
|
||||
if (!keepLastCreated) lastCreatedFinding = null
|
||||
val loaded = linkedMapOf<String, List<FieldFindingEvidence>>()
|
||||
@@ -351,6 +602,12 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
)
|
||||
}
|
||||
|
||||
private fun clearActState() {
|
||||
acts = emptyList()
|
||||
selectedAct = null
|
||||
actClosure = null
|
||||
}
|
||||
|
||||
private fun clearFindingState() {
|
||||
fieldFindingOptions = null
|
||||
lastCreatedFinding = null
|
||||
|
||||
Reference in New Issue
Block a user