diff --git a/src/dispatch/database/service.py b/src/dispatch/database/service.py index ba326ff4edea..7c4fd7b03a92 100644 --- a/src/dispatch/database/service.py +++ b/src/dispatch/database/service.py @@ -116,6 +116,8 @@ def get_named_models(self): return {"IndividualContact"} if model == "TagAll": return {"Tag"} + if model == "NotCaseType": + return {"CaseType"} else: return {self.filter_spec["model"]} return set() @@ -124,6 +126,8 @@ def format_for_sqlalchemy(self, query, default_model): filter_spec = self.filter_spec if filter_spec.get("model") in ["Participant", "Commander", "Assignee"]: filter_spec["model"] = "IndividualContact" + elif filter_spec.get("model") == "NotCaseType": + filter_spec["model"] = "CaseType" elif filter_spec.get("model") == "TagAll": filter_spec["model"] = "Tag" @@ -501,7 +505,7 @@ def common_parameters( ] -def has_tag_all(filter_spec: List[dict]): +def has_filter_model(model: str, filter_spec: List[dict]): """Checks if the filter spec has a TagAll filter.""" if isinstance(filter_spec, list): @@ -511,12 +515,35 @@ def has_tag_all(filter_spec: List[dict]): if key == "and": for condition in value: or_condition = condition.get("or", []) - if or_condition and or_condition[0].get("model") == "TagAll": + if or_condition and or_condition[0].get("model") == model: return True return False -def rebuild_filter_spec_without_tag_all(filter_spec: List[dict]): +def has_tag_all(filter_spec: List[dict]): + return has_filter_model("TagAll", filter_spec) + + +def has_not_case_type(filter_spec: List[dict]): + return has_filter_model("NotCaseType", filter_spec) + + +def rebuild_filter_spec_for_not_case_type(filter_spec: dict): + new_filter_spec = [] + for key, value in filter_spec.items(): + if key == "and": + for condition in value: + or_condition = condition.get("or", []) + if or_condition and or_condition[0].get("model") == "NotCaseType": + for cond in or_condition: + cond["op"] = "!=" + new_filter_spec.append({"and": [{"and": [cond]}]}) + else: + new_filter_spec.append(condition) + return {"and": new_filter_spec} + + +def rebuild_filter_spec_without_tag_all(filter_spec: dict): """Rebuilds the filter spec without the TagAll filter.""" new_filter_spec = [] tag_all_spec = [] @@ -565,6 +592,10 @@ def search_filter_sort_paginate( query = apply_filter_specific_joins(model_cls, filter_spec, query) # if the filter_spec has the TagAll filter, we need to split the query up # and intersect all of the results + if has_not_case_type(filter_spec): + new_filter_spec = rebuild_filter_spec_for_not_case_type(filter_spec) + if new_filter_spec: + query = apply_filters(query, new_filter_spec, model_cls) if has_tag_all(filter_spec): new_filter_spec, tag_all_spec = rebuild_filter_spec_without_tag_all(filter_spec) if new_filter_spec: diff --git a/src/dispatch/static/dispatch/src/case/TableFilterDialog.vue b/src/dispatch/static/dispatch/src/case/TableFilterDialog.vue index d7acb07e6c16..4dac0dc74590 100644 --- a/src/dispatch/static/dispatch/src/case/TableFilterDialog.vue +++ b/src/dispatch/static/dispatch/src/case/TableFilterDialog.vue @@ -21,7 +21,23 @@ - + + +
+ + {{ + exclude_mode ? "Exclude selected" : "Include only selected" + }} +
+
@@ -102,7 +118,12 @@ const props = defineProps({ const display = ref(false) const local_case_priority = ref([]) const local_case_severity = ref([]) -const local_case_type = ref([]) +const local_selected_case_types = ref([]) +const exclude_mode = ref(false) // Default to "include only selected" mode +const local_case_type = computed(() => (!exclude_mode.value ? local_selected_case_types.value : [])) +const local_not_case_type = computed(() => + exclude_mode.value ? local_selected_case_types.value : [] +) const local_closed_at = ref({}) const local_project = ref(props.projects) const local_reported_at = ref({}) @@ -118,7 +139,6 @@ const case_priority = computed( const case_severity = computed( () => store.state.case_management.table.options.filters.case_severity ) -const case_type = computed(() => store.state.case_management.table.options.filters.case_type) const project = computed(() => store.state.case_management.table.options.filters.project) const status = computed(() => store.state.case_management.table.options.filters.status) const tag = computed(() => store.state.case_management.table.options.filters.tag) @@ -128,7 +148,7 @@ const numFilters = computed(() => { return sum([ case_priority.value?.length || 0, case_severity.value?.length || 0, - case_type.value?.length || 0, + local_selected_case_types.value?.length || 0, project.value?.length || 0, status.value?.length || 0, tag.value?.length || 0, @@ -152,6 +172,7 @@ const applyFilters = () => { case_priority: local_case_priority.value, case_severity: local_case_severity.value, case_type: local_case_type.value, + not_case_type: local_not_case_type.value, closed_at: local_closed_at.value, project: local_project.value, reported_at: local_reported_at.value, diff --git a/src/dispatch/static/dispatch/src/case_cost/CaseCostCard.vue b/src/dispatch/static/dispatch/src/case_cost/CaseCostCard.vue index abff2cb07ed5..476738c24c74 100644 --- a/src/dispatch/static/dispatch/src/case_cost/CaseCostCard.vue +++ b/src/dispatch/static/dispatch/src/case_cost/CaseCostCard.vue @@ -72,7 +72,7 @@ export default { computed: { totalCost: function () { var totalCost = this.caseCosts.reduce(function (accumulator, item) { - if (item.case_cost_type.model_type == "New") { + if (item.case_cost_type?.model_type == "New") { return accumulator } return accumulator + item.amount