diff --git a/app/src/main/assets/questionnaire_order.json b/app/src/main/assets/questionnaire_order.json index 18ac359..1f1463d 100644 --- a/app/src/main/assets/questionnaire_order.json +++ b/app/src/main/assets/questionnaire_order.json @@ -1,24 +1,41 @@ [ { "file": "questionnaire_1_demographic_information.json", - "showPoints": false + "showPoints": false, + "condition": { + "alwaysAvailable": true + } }, { "file": "questionnaire_2_rhs.json", - "showPoints": true + "showPoints": true, + "condition": { + "alwaysAvailable": true + } }, { "file": "questionnaire_3_integration_index.json", - "showPoints": true + "showPoints": true, + "condition": { + "alwaysAvailable": true + } }, { "file": "questionnaire_4_consultation_results.json", - "showPoints": false + "showPoints": false, + "condition": { + "requiresCompleted": [ + "questionnaire_1_demographic_information", + "questionnaire_2_rhs", + "questionnaire_3_integration_index" + ] + } }, { "file": "questionnaire_5_final_interview.json", "showPoints": false, "condition": { + "requiresCompleted": ["questionnaire_4_consultation_results"], "questionnaire": "questionnaire_4_consultation_results", "questionId": "consultation_decision", "operator": "==", @@ -27,6 +44,20 @@ }, { "file": "questionnaire_6_follow_up_survey.json", - "showPoints": false + "showPoints": false, + "condition": { + "anyOf": [ + { + "requiresCompleted": ["questionnaire_5_final_interview"] + }, + { + "requiresCompleted": ["questionnaire_4_consultation_results"], + "questionnaire": "questionnaire_4_consultation_results", + "questionId": "consultation_decision", + "operator": "!=", + "value": "yellow" + } + ] + } } ] diff --git a/app/src/main/java/com/dano/test1/HandlerClientCoachCode.kt b/app/src/main/java/com/dano/test1/HandlerClientCoachCode.kt index b179cbf..ba363d6 100644 --- a/app/src/main/java/com/dano/test1/HandlerClientCoachCode.kt +++ b/app/src/main/java/com/dano/test1/HandlerClientCoachCode.kt @@ -69,6 +69,11 @@ class HandlerClientCoachCode( val clientCode = clientCodeField.text.toString() val coachCode = coachCodeField.text.toString() + // Prüfen, ob die Datenbank-Dateien vor dem Klick existieren + val dbFile = layout.context.getDatabasePath("questionnaire_database") + val dbJournalFile = layout.context.getDatabasePath("questionnaire_database-journal") + val dbExisted = dbFile.exists() || dbJournalFile.exists() + // Check if client code already exists asynchronously CoroutineScope(Dispatchers.IO).launch { val existingClient = MyApp.database.clientDao().getClientByCode(clientCode) @@ -81,6 +86,13 @@ class HandlerClientCoachCode( } else { // Either no existing client or re-using previous code saveAnswers(clientCode, coachCode) + + // Datenbank-Dateien löschen, wenn sie vorher NICHT existierten + if (!dbExisted) { + dbFile.delete() + dbJournalFile.delete() + } + goToNextQuestion() } } diff --git a/app/src/main/java/com/dano/test1/HandlerOpeningScreen.kt b/app/src/main/java/com/dano/test1/HandlerOpeningScreen.kt index e592d4f..fb897df 100644 --- a/app/src/main/java/com/dano/test1/HandlerOpeningScreen.kt +++ b/app/src/main/java/com/dano/test1/HandlerOpeningScreen.kt @@ -8,10 +8,11 @@ import android.view.View import android.widget.* import kotlinx.coroutines.* import org.json.JSONArray +import org.json.JSONObject import android.util.Log +import com.dano.test1.data.CompletedQuestionnaire import java.io.File - var INTEGRATION_INDEX_POINTS: Int? = null class HandlerOpeningScreen(private val activity: MainActivity) { @@ -52,8 +53,7 @@ class HandlerOpeningScreen(private val activity: MainActivity) { val pathExists = File(dbPath).exists() if (pathExists) { updateMainButtonsState(true) - } - else{ + } else { updateMainButtonsState(false) } @@ -90,15 +90,7 @@ class HandlerOpeningScreen(private val activity: MainActivity) { val obj = jsonArray.getJSONObject(i) val file = obj.getString("file") val conditionObj = obj.optJSONObject("condition") - val condition = if (conditionObj != null) { - QuestionItem.Condition( - questionnaire = conditionObj.getString("questionnaire"), - questionId = conditionObj.getString("questionId"), - operator = conditionObj.getString("operator"), - value = conditionObj.getString("value") - ) - } else null - + val condition = parseCondition(conditionObj) val showPoints = obj.optBoolean("showPoints", false) QuestionItem.QuestionnaireEntry(file, condition, showPoints) @@ -109,6 +101,63 @@ class HandlerOpeningScreen(private val activity: MainActivity) { } } + // Parser: erzeugt ein QuestionItem.Condition? (sehr robust gegenüber verschiedenen JSON-Formaten) + private fun parseCondition(conditionObj: JSONObject?): QuestionItem.Condition? { + if (conditionObj == null) return null + + // anyOf + if (conditionObj.has("anyOf")) { + val arr = conditionObj.optJSONArray("anyOf") ?: JSONArray() + val conditions = mutableListOf() + for (i in 0 until arr.length()) { + val sub = arr.optJSONObject(i) + parseCondition(sub)?.let { conditions.add(it) } + } + return QuestionItem.Condition.AnyOf(conditions) + } + + // alwaysAvailable + if (conditionObj.has("alwaysAvailable")) { + val flag = conditionObj.optBoolean("alwaysAvailable", false) + if (flag) return QuestionItem.Condition.AlwaysAvailable + } + + // requiresCompleted (array) + val requiresList = mutableListOf() + if (conditionObj.has("requiresCompleted")) { + val reqArr = conditionObj.optJSONArray("requiresCompleted") + if (reqArr != null) { + for (i in 0 until reqArr.length()) { + requiresList.add(reqArr.optString(i)) + } + } else { + // sometimes it's a single string + conditionObj.optString("requiresCompleted")?.let { if (it.isNotBlank()) requiresList.add(it) } + } + } + + // question-check fields + val questionnaire = conditionObj.optString("questionnaire", null) + val questionId = conditionObj.optString("questionId", null) + val operator = conditionObj.optString("operator", null) + val value = conditionObj.optString("value", null) + + val hasQuestionCheck = !questionnaire.isNullOrBlank() && !questionId.isNullOrBlank() && !operator.isNullOrBlank() && value != null + + return when { + requiresList.isNotEmpty() && hasQuestionCheck -> { + QuestionItem.Condition.Combined(requiresList, QuestionItem.Condition.QuestionCondition(questionnaire!!, questionId!!, operator!!, value!!)) + } + hasQuestionCheck -> { + QuestionItem.Condition.QuestionCondition(questionnaire!!, questionId!!, operator!!, value!!) + } + requiresList.isNotEmpty() -> { + QuestionItem.Condition.RequiresCompleted(requiresList) + } + else -> null + } + } + private fun createQuestionnaireButtons() { buttonContainer.removeAllViews() dynamicButtons.clear() @@ -129,12 +178,23 @@ class HandlerOpeningScreen(private val activity: MainActivity) { } updateButtonTexts() - setButtonsEnabled(listOf(dynamicButtons.firstOrNull()).filterNotNull()) + + // Initial: enable those with AlwaysAvailable (falls vorhanden) + val alwaysButtons = questionnaireEntries.mapIndexedNotNull { idx, entry -> + val btn = dynamicButtons.getOrNull(idx) + if (entry.condition is QuestionItem.Condition.AlwaysAvailable) btn else null + } + setButtonsEnabled(alwaysButtons) dynamicButtons.forEach { button -> button.setOnClickListener { + // require a client code to start actual questionnaire (sichere Kontrolle) + val clientCode = editText.text.toString().trim() + + GlobalValues.LAST_CLIENT_CODE = clientCode startQuestionnaireForButton(button) - setButtonsEnabled(dynamicButtons.filter { it != button }) + // disable other buttons while one questionnaire is open + setButtonsEnabled(dynamicButtons.filter { it == button }) } } } @@ -190,86 +250,143 @@ class HandlerOpeningScreen(private val activity: MainActivity) { withContext(Dispatchers.Main) { val message = LanguageManager.getText(languageID, "no_profile") Toast.makeText(activity, message, Toast.LENGTH_LONG).show() - setButtonsEnabled(listOf(dynamicButtons.firstOrNull()).filterNotNull()) + // enable only alwaysAvailable ones if no client found + val alwaysButtons = questionnaireEntries.mapIndexedNotNull { idx, entry -> + val btn = dynamicButtons.getOrNull(idx) + if (entry.condition is QuestionItem.Condition.AlwaysAvailable) btn else null + } + setButtonsEnabled(alwaysButtons) } return@launch } withContext(Dispatchers.Main) { updateMainButtonsState(true) // Datenbank vorhanden -> Buttons aktivieren - handleNormalLoad(clientCode) + } + + handleNormalLoad(clientCode) + } + } + + // Evaluierung der Bedingung: suspend, weil DB-Abfragen stattfinden. + private suspend fun evaluateCondition( + condition: QuestionItem.Condition?, + clientCode: String, + completedEntries: List // Anpassung an deinem DAO-Objekt-Name + ): Boolean { + if (condition == null) return false + + when (condition) { + is QuestionItem.Condition.AlwaysAvailable -> return true + is QuestionItem.Condition.RequiresCompleted -> { + // prüfen, ob alle required items in completedEntries vorhanden und isDone == true sind + val normalizedCompleted = completedEntries.map { normalizeQuestionnaireId(it.questionnaireId) } + return condition.required.all { req -> + val nReq = normalizeQuestionnaireId(req) + normalizedCompleted.any { it.contains(nReq) || nReq.contains(it) } + } + } + is QuestionItem.Condition.QuestionCondition -> { + // need to fetch the answer for that questionnaire/questionId + val answers = MyApp.database.answerDao().getAnswersForClientAndQuestionnaire(clientCode, condition.questionnaire) + val relevant = answers.find { it.questionId.endsWith(condition.questionId, ignoreCase = true) } + val answerValue = relevant?.answerValue ?: "" + return when (condition.operator) { + "==" -> answerValue == condition.value + "!=" -> answerValue != condition.value + else -> false + } + } + is QuestionItem.Condition.Combined -> { + // Combined: requiresCompleted (if present) AND questionCheck must match + val reqOk = if (condition.requiresCompleted.isNullOrEmpty()) true + else { + val normalizedCompleted = completedEntries.map { normalizeQuestionnaireId(it.questionnaireId) } + condition.requiresCompleted.all { req -> + val nReq = normalizeQuestionnaireId(req) + normalizedCompleted.any { it.contains(nReq) || nReq.contains(it) } + } + } + if (!reqOk) return false + // dann Frage-Check auswerten + val q = condition.questionCheck + if (q != null) { + val answers = MyApp.database.answerDao().getAnswersForClientAndQuestionnaire(clientCode, q.questionnaire) + val relevant = answers.find { it.questionId.endsWith(q.questionId, ignoreCase = true) } + val answerValue = relevant?.answerValue ?: "" + return when (q.operator) { + "==" -> answerValue == q.value + "!=" -> answerValue != q.value + else -> false + } + } + return reqOk + } + is QuestionItem.Condition.AnyOf -> { + // true, wenn irgendeine der Sub-Bedingungen erfüllt ist + for (sub in condition.conditions) { + val subRes = evaluateCondition(sub, clientCode, completedEntries) + if (subRes) return true + } + return false } } } + private fun normalizeQuestionnaireId(name: String): String { + return name.lowercase().removeSuffix(".json") + } + private suspend fun handleNormalLoad(clientCode: String) { - val completedIds = withContext(Dispatchers.IO) { - MyApp.database.completedQuestionnaireDao().getCompletedQuestionnairesForClient(clientCode) - } - - if (completedIds.isEmpty()) { - setButtonsEnabled(listOf(dynamicButtons.firstOrNull()).filterNotNull()) - val message = LanguageManager.getText(languageID, "no_profile") - Toast.makeText(activity, message, Toast.LENGTH_LONG).show() - return - } - - val completedIndexes = completedIds.mapNotNull { id -> - questionnaireEntries.indexOfFirst { it.file.contains(id, ignoreCase = true) }.takeIf { it >= 0 } - }.sorted() - val completedEntries = withContext(Dispatchers.IO) { MyApp.database.completedQuestionnaireDao().getAllForClient(clientCode) } + // fülle buttonPoints & INTEGRATION_INDEX_POINTS buttonPoints.clear() for (entry in completedEntries) { if (entry.isDone) { buttonPoints[entry.questionnaireId] = entry.sumPoints ?: 0 - if (entry.questionnaireId.contains("questionnaire_3_integration_index", ignoreCase = true)) { INTEGRATION_INDEX_POINTS = entry.sumPoints } } } - updateButtonTexts() - - var nextIndex = (completedIndexes.lastOrNull() ?: -1) + 1 - - while (nextIndex < questionnaireEntries.size) { - val entry = questionnaireEntries[nextIndex] - val condition = entry.condition - - if (condition != null) { - val answers = MyApp.database.answerDao().getAnswersForClientAndQuestionnaire(clientCode, condition.questionnaire) - - val relevantAnswer = answers.find { - it.questionId.endsWith(condition.questionId) - } - - val answerValue = relevantAnswer?.answerValue ?: "" - val conditionMet = when (condition.operator) { - "!=" -> answerValue != condition.value - "==" -> answerValue == condition.value - else -> true - } - - if (conditionMet) break - else nextIndex++ - } else { - break - } + withContext(Dispatchers.Main) { + updateButtonTexts() } - if (nextIndex >= questionnaireEntries.size) { - setButtonsEnabled(emptyList()) - val message = LanguageManager.getText(languageID, "questionnaires_finished") - Toast.makeText(activity, message, Toast.LENGTH_LONG).show() - } else { - val nextFileName = questionnaireEntries[nextIndex].file - val nextButton = questionnaireFiles.entries.firstOrNull { it.value == nextFileName }?.key - setButtonsEnabled(listOfNotNull(nextButton)) + // für jeden Fragebogen prüfen, ob er aktiv sein darf + val enabledButtons = mutableListOf