diff --git a/common/src/main/kotlin/com/alfresco/content/common/EntryListener.kt b/common/src/main/kotlin/com/alfresco/content/common/EntryListener.kt index 43b4c27a..70a8927e 100644 --- a/common/src/main/kotlin/com/alfresco/content/common/EntryListener.kt +++ b/common/src/main/kotlin/com/alfresco/content/common/EntryListener.kt @@ -1,5 +1,6 @@ package com.alfresco.content.common +import com.alfresco.content.data.Entry import com.alfresco.content.data.ParentEntry import com.alfresco.content.data.payloads.FieldsData @@ -19,5 +20,5 @@ interface EntryListener { fun onProcessStart(entries: List) {} fun onAttachFolder(entry: ParentEntry) {} - fun onAttachFiles(field: FieldsData) {} + fun onAttachFiles(field: FieldsData, deletedFiles: MutableMap) {} } diff --git a/data/src/main/kotlin/com/alfresco/content/data/AttachFolderSearchData.kt b/data/src/main/kotlin/com/alfresco/content/data/AttachFolderSearchData.kt index 026e638a..a55c487d 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/AttachFolderSearchData.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/AttachFolderSearchData.kt @@ -3,4 +3,4 @@ package com.alfresco.content.data import com.alfresco.content.data.payloads.FieldsData data class AttachFolderSearchData(val entry: Entry? = null) -data class AttachFilesData(val field: FieldsData? = null) +data class AttachFilesData(val field: FieldsData? = null, val deletedFiles: MutableMap) diff --git a/data/src/main/kotlin/com/alfresco/content/data/Entry.kt b/data/src/main/kotlin/com/alfresco/content/data/Entry.kt index 9d94126d..3ab4a37e 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/Entry.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/Entry.kt @@ -379,7 +379,7 @@ data class Entry( ).withOfflineStatus() } - fun with(contentEntry: ContentEntry): Entry { + fun with(contentEntry: ContentEntry, observerID: String): Entry { return Entry( id = contentEntry.id.toString(), name = contentEntry.name, @@ -388,6 +388,8 @@ data class Entry( isRelatedContent = contentEntry.isRelatedContent, isContentAvailable = contentEntry.isContentAvailable, mimeType = contentEntry.mimeType, + observerID = observerID, + uploadServer = UploadServerType.DATA_FROM_SERVER, ) } @@ -558,6 +560,7 @@ enum class UploadServerType { DEFAULT, UPLOAD_TO_TASK, UPLOAD_TO_PROCESS, + DATA_FROM_SERVER, NONE, ; diff --git a/data/src/main/kotlin/com/alfresco/content/data/OptionsModel.kt b/data/src/main/kotlin/com/alfresco/content/data/OptionsModel.kt index eb6cef65..6906907a 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/OptionsModel.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/OptionsModel.kt @@ -51,6 +51,7 @@ enum class DefaultOutcomesID { DEFAULT_START_WORKFLOW, DEFAULT_SAVE, DEFAULT_CLAIM, + DEFAULT_RELEASE, DEFAULT_COMPLETE, ; fun value() = name.lowercase() diff --git a/data/src/main/kotlin/com/alfresco/content/data/ProcessEntry.kt b/data/src/main/kotlin/com/alfresco/content/data/ProcessEntry.kt index fe6049f3..923fdd1e 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/ProcessEntry.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/ProcessEntry.kt @@ -90,6 +90,7 @@ data class ProcessEntry( */ fun with(data: TaskEntry): ProcessEntry { return ProcessEntry( + name = data.name, description = data.description ?: "", processInstanceId = data.processInstanceId, taskEntry = data, diff --git a/data/src/main/kotlin/com/alfresco/content/data/payloads/FieldsData.kt b/data/src/main/kotlin/com/alfresco/content/data/payloads/FieldsData.kt index b0cd29c7..9e815c4a 100644 --- a/data/src/main/kotlin/com/alfresco/content/data/payloads/FieldsData.kt +++ b/data/src/main/kotlin/com/alfresco/content/data/payloads/FieldsData.kt @@ -47,7 +47,7 @@ data class FieldsData( fun getContentList(): List { return if (((value as? List<*>)?.firstOrNull() is Map<*, *>)) { - (value as? List<*>)?.map { Gson().fromJson(JSONObject(it as Map).toString(), ContentEntry::class.java) }?.map { Entry.with(it) } ?: emptyList() + (value as? List<*>)?.map { Gson().fromJson(JSONObject(it as Map).toString(), ContentEntry::class.java) }?.map { Entry.with(it, id) } ?: emptyList() } else { (value as? List<*>)?.mapNotNull { it as? Entry } ?: emptyList() } diff --git a/process-app/src/main/kotlin/com/alfresco/content/process/ui/composeviews/FormScreen.kt b/process-app/src/main/kotlin/com/alfresco/content/process/ui/composeviews/FormScreen.kt index b3a6471a..e7276e29 100644 --- a/process-app/src/main/kotlin/com/alfresco/content/process/ui/composeviews/FormScreen.kt +++ b/process-app/src/main/kotlin/com/alfresco/content/process/ui/composeviews/FormScreen.kt @@ -28,20 +28,10 @@ fun FormScreen(navController: NavController, viewModel: FormViewModel, fragment: val state by viewModel.collectAsState() - val task = state.parent.taskEntry - val customOutcomes = when { state.formFields.isEmpty() -> emptyList() state.processOutcomes.isEmpty() -> defaultOutcomes(state) - task.assignee?.id == 0 -> { - listOf( - OptionsModel( - id = DefaultOutcomesID.DEFAULT_CLAIM.value(), - name = stringResource(id = R.string.action_menu_claim), - ), - ) - } - + state.parent.taskEntry.memberOfCandidateGroup == true -> pooledOutcomes(state, viewModel) else -> customOutcomes(state) } @@ -128,3 +118,34 @@ private fun customOutcomes(state: FormViewState): List { ) + state.processOutcomes } } + +@Composable +private fun pooledOutcomes(state: FormViewState, viewModel: FormViewModel): List { + val dataObj = state.parent.taskEntry + + when { + dataObj.assignee?.id == null || dataObj.assignee?.id == 0 -> { + return listOf( + OptionsModel( + id = DefaultOutcomesID.DEFAULT_CLAIM.value(), + name = stringResource(id = R.string.action_menu_claim), + ), + ) + } + + viewModel.isAssigneeAndLoggedInSame(dataObj.assignee) -> { + return listOf( + OptionsModel( + id = DefaultOutcomesID.DEFAULT_RELEASE.value(), + name = stringResource(id = R.string.action_menu_release), + ), + OptionsModel( + id = DefaultOutcomesID.DEFAULT_SAVE.value(), + name = stringResource(id = R.string.action_text_save), + ), + ) + state.processOutcomes + } + + else -> return emptyList() + } +} diff --git a/process-app/src/main/kotlin/com/alfresco/content/process/ui/fragments/FormViewModel.kt b/process-app/src/main/kotlin/com/alfresco/content/process/ui/fragments/FormViewModel.kt index cb451ef6..bf2f366b 100644 --- a/process-app/src/main/kotlin/com/alfresco/content/process/ui/fragments/FormViewModel.kt +++ b/process-app/src/main/kotlin/com/alfresco/content/process/ui/fragments/FormViewModel.kt @@ -53,7 +53,7 @@ class FormViewModel( OfflineRepository().removeCompletedUploads() if (state.parent.processInstanceId != null) { - getTaskForms(state.parent) + getTaskDetails() } else { singleProcessDefinition(state.parent.id) } @@ -66,7 +66,7 @@ class FormViewModel( viewModelScope.on { it.field?.let { field -> - entryListener?.onAttachFiles(field) + entryListener?.onAttachFiles(field, it.deletedFiles) } } } @@ -80,12 +80,15 @@ class FormViewModel( observeUploadsJob = repo.observeProcessUploads(parentId, UploadServerType.UPLOAD_TO_PROCESS) .execute { if (it is Success) { - val listFields = state.formFields.filter { fieldsData -> fieldsData.type == FieldType.UPLOAD.value() } - listFields.forEach { field -> - val listContents = it().filter { content -> content.observerID == field.id } - val isError = field.required && listContents.isEmpty() && listContents.all { content -> !content.isUpload } - updateFieldValue(field.id, listContents, Pair(isError, "")) + withState { newState -> + val listFields = newState.formFields.filter { fieldsData -> fieldsData.type == FieldType.UPLOAD.value() } + listFields.forEach { field -> + val listContents = field.getContentList().filter { filter -> filter.uploadServer == UploadServerType.DATA_FROM_SERVER } + it().filter { content -> content.observerID == field.id } + val isError = field.required && listContents.isEmpty() && listContents.all { content -> !content.isUpload } + updateFieldValue(field.id, listContents, Pair(isError, "")) + } } + this } else { this @@ -197,6 +200,33 @@ class FormViewModel( } } + private fun getTaskDetails() = withState { state -> + viewModelScope.launch { + // Fetch tasks detail data + repository::getTaskDetails.asFlow( + state.parent.taskEntry.id, + ).execute { + when (it) { + is Loading -> copy(request = Loading()) + is Fail -> copy(request = Fail(it.error)) + is Success -> { + val updateState = update(it()) + getTaskForms(updateState.parent) + updateState.copy(request = Success(it())) + } + + else -> { + this + } + } + } + } + } + + internal fun isAssigneeAndLoggedInSame(assignee: UserGroupDetails?) = getAPSUser().id == assignee?.id + + internal fun isStartedByAndLoggedInSame(initiatorId: String?) = getAPSUser().id.toString() == initiatorId + fun linkContentToProcess(state: FormViewState, entry: Entry, sourceName: String, field: FieldsData?) = viewModelScope.launch { repository::linkADWContentToProcess @@ -306,6 +336,7 @@ class FormViewModel( DefaultOutcomesID.DEFAULT_COMPLETE.value() -> completeTask() DefaultOutcomesID.DEFAULT_SAVE.value() -> saveForm() DefaultOutcomesID.DEFAULT_CLAIM.value() -> claimTask() + DefaultOutcomesID.DEFAULT_RELEASE.value() -> releaseTask() else -> actionOutcome(optionsModel.outcome) } } @@ -335,6 +366,31 @@ class FormViewModel( } } + /** + * execute API to release the task + */ + private fun releaseTask() = withState { state -> + requireNotNull(state.parent) + viewModelScope.launch { + repository::releaseTask.asFlow(state.parent.taskEntry.id).execute { + when (it) { + is Loading -> copy(requestClaimRelease = Loading()) + is Fail -> { + copy(requestClaimRelease = Fail(it.error)) + } + + is Success -> { + copy(requestClaimRelease = Success(it())) + } + + else -> { + this + } + } + } + } + } + private fun completeTask() = withState { state -> viewModelScope.launch { repository::actionCompleteOutcome.asFlow(state.parent.taskEntry.id, convertFieldsToValues(state.formFields)).execute { @@ -446,6 +502,7 @@ class FormViewModel( val convertedDate = (field.value as? String)?.getFormattedDate(DATE_FORMAT_4_1, DATE_FORMAT_5) values[field.id] = convertedDate } + FieldType.DATE.value() -> { val convertedDate = (field.value as? String)?.getFormattedDate(DATE_FORMAT_4, DATE_FORMAT_5) values[field.id] = convertedDate diff --git a/process-app/src/main/kotlin/com/alfresco/content/process/ui/fragments/FormViewState.kt b/process-app/src/main/kotlin/com/alfresco/content/process/ui/fragments/FormViewState.kt index 27fd4a3a..f05b493c 100644 --- a/process-app/src/main/kotlin/com/alfresco/content/process/ui/fragments/FormViewState.kt +++ b/process-app/src/main/kotlin/com/alfresco/content/process/ui/fragments/FormViewState.kt @@ -11,6 +11,7 @@ import com.alfresco.content.data.ResponseAccountInfo import com.alfresco.content.data.ResponseFormVariables import com.alfresco.content.data.ResponseListForm import com.alfresco.content.data.ResponseListProcessDefinition +import com.alfresco.content.data.TaskEntry import com.alfresco.content.data.payloads.FieldsData import com.alfresco.process.models.ProfileData import retrofit2.Response @@ -31,9 +32,21 @@ data class FormViewState( val requestAccountInfo: Async = Uninitialized, val requestProfile: Async = Uninitialized, val requestClaimRelease: Async> = Uninitialized, + val request: Async = Uninitialized, ) : MavericksState { constructor(target: ProcessEntry) : this(parent = target) + /** + * update the taskDetailObj params after getting the response from server. + */ + fun update(response: TaskEntry?): FormViewState { + if (response == null) return this + + val processEntry = ProcessEntry.with(response) + + return copy(parent = processEntry) + } + /** * update the single process definition entry */ diff --git a/process-app/src/main/kotlin/com/alfresco/content/process/ui/fragments/ProcessAttachFilesFragment.kt b/process-app/src/main/kotlin/com/alfresco/content/process/ui/fragments/ProcessAttachFilesFragment.kt index 9daffecd..bcc90ec3 100644 --- a/process-app/src/main/kotlin/com/alfresco/content/process/ui/fragments/ProcessAttachFilesFragment.kt +++ b/process-app/src/main/kotlin/com/alfresco/content/process/ui/fragments/ProcessAttachFilesFragment.kt @@ -159,7 +159,7 @@ class ProcessAttachFilesFragment : ProcessBaseFragment(), MavericksView, EntryLi override fun onDestroy() { withState(viewModel) { CoroutineScope(Dispatchers.Main).launch { - EventBus.default.send(AttachFilesData(it.parent.field)) + EventBus.default.send(AttachFilesData(it.parent.field, viewModel.serverdeletedFiles)) } } super.onDestroy() diff --git a/process-app/src/main/kotlin/com/alfresco/content/process/ui/fragments/ProcessAttachFilesViewModel.kt b/process-app/src/main/kotlin/com/alfresco/content/process/ui/fragments/ProcessAttachFilesViewModel.kt index 9ed0c226..3576dfa6 100644 --- a/process-app/src/main/kotlin/com/alfresco/content/process/ui/fragments/ProcessAttachFilesViewModel.kt +++ b/process-app/src/main/kotlin/com/alfresco/content/process/ui/fragments/ProcessAttachFilesViewModel.kt @@ -27,6 +27,7 @@ class ProcessAttachFilesViewModel( private var observeUploadsJob: Job? = null var parentId: String = "" var entryListener: EntryListener? = null + var serverdeletedFiles: MutableMap = mutableMapOf() init { @@ -44,6 +45,7 @@ class ProcessAttachFilesViewModel( } else -> { + setState { copy(listContents = field.getContentList(), baseEntries = field.getContentList()) } observeUploads(state) } } @@ -58,7 +60,11 @@ class ProcessAttachFilesViewModel( * delete content locally */ fun deleteAttachment(entry: Entry) = stateFlow.execute { - OfflineRepository().remove(entry) + if (entry.uploadServer != UploadServerType.DATA_FROM_SERVER) { + OfflineRepository().remove(entry) + } else { + serverdeletedFiles[entry.id] = entry + } deleteUploads(entry.id) } diff --git a/process-app/src/main/kotlin/com/alfresco/content/process/ui/fragments/ProcessFragment.kt b/process-app/src/main/kotlin/com/alfresco/content/process/ui/fragments/ProcessFragment.kt index 7f574704..bcc9d97a 100644 --- a/process-app/src/main/kotlin/com/alfresco/content/process/ui/fragments/ProcessFragment.kt +++ b/process-app/src/main/kotlin/com/alfresco/content/process/ui/fragments/ProcessFragment.kt @@ -25,6 +25,7 @@ import com.airbnb.mvrx.withState import com.alfresco.content.common.EntryListener import com.alfresco.content.data.Entry import com.alfresco.content.data.ParentEntry +import com.alfresco.content.data.UploadServerType import com.alfresco.content.data.payloads.FieldType import com.alfresco.content.data.payloads.FieldsData import com.alfresco.content.hideSoftInput @@ -207,9 +208,11 @@ class ProcessFragment : Fragment(), MavericksView, EntryListener { } } - override fun onAttachFiles(field: FieldsData) = withState(viewModel) { state -> + override fun onAttachFiles(field: FieldsData, deletedFiles: MutableMap) = withState(viewModel) { state -> if (isAdded && field.type == FieldType.UPLOAD.value()) { - val listContents = viewModel.getContents(state, field.id) + val serverUploads = field.getContentList().filter { it.uploadServer == UploadServerType.DATA_FROM_SERVER }.filterNot { item -> deletedFiles.any { it.value.id == item.id } } + + val listContents = serverUploads + viewModel.getContents(state, field.id) val isError = field.required && listContents.isEmpty() viewModel.updateFieldValue(field.id, listContents, Pair(isError, ""))