diff --git a/ProcessMaker/Filters/Filter.php b/ProcessMaker/Filters/Filter.php index 04e381a2da..c010558ec6 100644 --- a/ProcessMaker/Filters/Filter.php +++ b/ProcessMaker/Filters/Filter.php @@ -15,6 +15,8 @@ class Filter const TYPE_PARTICIPANTS_FULLNAME = 'ParticipantsFullName'; + const TYPE_ASSIGNEES_FULLNAME = 'AssigneesFullName'; + const TYPE_STATUS = 'Status'; const TYPE_FIELD = 'Field'; @@ -200,6 +202,9 @@ private function valueAliasMethod() case self::TYPE_PARTICIPANTS_FULLNAME: $method = 'valueAliasParticipantByFullName'; break; + case self::TYPE_ASSIGNEES_FULLNAME: + $method = 'valueAliasAssigneeByFullName'; + break; case self::TYPE_STATUS: $method = 'valueAliasStatus'; break; diff --git a/ProcessMaker/Filters/SaveSession.php b/ProcessMaker/Filters/SaveSession.php index 77fd973e9b..3a9f8979a4 100644 --- a/ProcessMaker/Filters/SaveSession.php +++ b/ProcessMaker/Filters/SaveSession.php @@ -39,7 +39,9 @@ public static function getConfigFilter(String $name, Object $user) { $key = self::getKey($user, $name); - return self::get($key, []); + $default = ['filters' => []]; + + return self::get($key, $default); } /** diff --git a/ProcessMaker/Http/Controllers/TaskController.php b/ProcessMaker/Http/Controllers/TaskController.php index ac20276af4..430da20b8a 100644 --- a/ProcessMaker/Http/Controllers/TaskController.php +++ b/ProcessMaker/Http/Controllers/TaskController.php @@ -131,8 +131,9 @@ public function edit(ProcessRequestToken $task, string $preview = '') 'lastname', 'avatar', 'timezone', - 'datetime_format' + 'datetime_format', ]); + return view('tasks.edit', [ 'task' => $task, 'dueLabels' => self::$dueLabels, diff --git a/ProcessMaker/Models/ProcessRequestToken.php b/ProcessMaker/Models/ProcessRequestToken.php index 10db8195c0..c9ceb6674a 100644 --- a/ProcessMaker/Models/ProcessRequestToken.php +++ b/ProcessMaker/Models/ProcessRequestToken.php @@ -34,12 +34,12 @@ * @property string $element_id * @property string $element_type * @property string $status - * @property \Carbon\Carbon $completed_at - * @property \Carbon\Carbon $due_at - * @property \Carbon\Carbon $initiated_at - * @property \Carbon\Carbon $riskchanges_at - * @property \Carbon\Carbon $updated_at - * @property \Carbon\Carbon $created_at + * @property Carbon $completed_at + * @property Carbon $due_at + * @property Carbon $initiated_at + * @property Carbon $riskchanges_at + * @property Carbon $updated_at + * @property Carbon $created_at * @property ProcessRequest $processRequest * * @OA\Schema( @@ -332,7 +332,7 @@ public function getScreen(): ?Screen // It uses a try-catch block to handle any exceptions that might occur, for example in test environments. try { $localName = $this->getBpmnDefinition()->localName; - } catch (\Throwable $t) { + } catch (Throwable $t) { $localName = null; } $isManualTask = $localName === 'manualTask'; @@ -457,7 +457,7 @@ public function getAdvanceStatusAttribute() /** * Check if the user has access to reassign this task * - * @param \ProcessMaker\Models\User $user + * @param User $user */ public function authorizeReassignment(User $user) { @@ -616,7 +616,7 @@ public function fieldAliasCompleted() public function fieldAliasUser_Id() { - return 'user_id'; + return 'process_request_tokens.user_id'; } /** @@ -665,9 +665,9 @@ public function valueAliasStatus($value, $expression, $callback = null, User $us } elseif (array_key_exists($value, $statusMap)) { $query->where('is_self_service', 0); if ($expression->operator == '=') { - $query->whereIn('status', $statusMap[$value]); + $query->whereIn('process_request_tokens.status', $statusMap[$value]); } elseif ($expression->operator == '!=') { - $query->whereNotIn('status', $statusMap[$value]); + $query->whereNotIn('process_request_tokens.status', $statusMap[$value]); } } else { $query->where('status', $expression->operator, $value) @@ -758,6 +758,20 @@ public function valueAliasProcess_Name(string $value, Expression $expression): c }; } + /** + * PMQL value alias for assignee field by fullname. + * @param string $value + * @return callable + */ + public function valueAliasAssigneeByFullName($value, $expression) + { + return function ($query) use ($expression, $value) { + $query->whereHas('user', function ($query) use ($expression, $value) { + $query->whereRaw("CONCAT(firstname, ' ', lastname) " . $expression->operator . ' ?', [$value]); + }); + }; + } + /** * PMQL wildcard for process request & data fields * @@ -909,8 +923,8 @@ public function persistUserData($user) /** * Log an error when executing the token * - * @param \Throwable $error - * @param \ProcessMaker\Nayra\Contracts\Bpmn\FlowElementInterface $bpmnElement + * @param Throwable $error + * @param FlowElementInterface $bpmnElement */ public function logError(Throwable $error, FlowElementInterface $bpmnElement) { diff --git a/resources/js/common/PMColumnFilterPopoverCommonMixin.js b/resources/js/common/PMColumnFilterPopoverCommonMixin.js index fa60f52b9b..52290a0cdc 100644 --- a/resources/js/common/PMColumnFilterPopoverCommonMixin.js +++ b/resources/js/common/PMColumnFilterPopoverCommonMixin.js @@ -1,3 +1,5 @@ +import { get, cloneDeep } from "lodash"; + const PMColumnFilterCommonMixin = { data() { return { @@ -9,6 +11,22 @@ const PMColumnFilterCommonMixin = { }; }, methods: { + storeFilterConfiguration() { + const { order, type } = this.filterConfiguration(); + let url = "users/store_filter_configuration/"; + if (this.$props.columns && this.savedSearch) { + url += "savedSearch|" + this.savedSearch; + } else { + url += type; + } + let config = { + filters: this.formattedFilter(), + order + }; + ProcessMaker.apiClient.put(url, config); + window.ProcessMaker.advanced_filter = config; + window.ProcessMaker.EventBus.$emit("advanced-filter-updated"); + }, getViewConfigFilter() { return [ { @@ -67,15 +85,31 @@ const PMColumnFilterCommonMixin = { } ]; }, - onApply(json, index) { - let oldValue, type, value; + addAliases(json, key, label) { + let type, value; for (let i in json) { - oldValue = json[i].subject.value; - type = this.getTypeColumnFilter(oldValue); - value = this.getAliasColumnForFilter(oldValue); + type = this.getTypeColumnFilter(key, json[i].subject.type); + value = this.getAliasColumnForFilter(key, json[i].subject.value); json[i].subject.type = type; json[i].subject.value = value; + json[i]._column_field = key; + json[i]._column_label = label; + + if (json[i].or && json[i].or.length > 0) { + this.addAliases(json[i].or, key, label); + } } + }, + getTypeColumnFilter(field, defaultType = 'Field') { + return this.tableHeaders.find(column => column.field === field)?.filter_subject?.type || defaultType; + }, + getAliasColumnForFilter(field, defaultValue) { + return this.tableHeaders.find(column => column.field === field)?.filter_subject?.value || defaultValue; + }, + getAliasColumnForOrderBy(value) { + return this.tableHeaders.find(column => column.field === value)?.order_column || value; + }, + onApply(json, index) { this.advancedFilterInit(); this.advancedFilter[index] = json; this.markStyleWhenColumnSetAFilter(); @@ -101,9 +135,20 @@ const PMColumnFilterCommonMixin = { object.$refs.pmColumnFilterForm.setValues(this.advancedFilter[index]); } }, + formattedFilter() { + const filterCopy = cloneDeep(this.advancedFilter); + Object.keys(filterCopy).forEach((key) => { + if (filterCopy[key].length === 0) { + delete filterCopy[key]; + } + const label = this.tableHeaders.find(column => column.field === key)?.label; + this.addAliases(filterCopy[key], key, label); + }); + return Object.values(filterCopy).flat(1); + }, getAdvancedFilter() { - let flat = this.json2Array(this.advancedFilter).flat(1); - return flat.length > 0 ? "&advanced_filter=" + encodeURIComponent(JSON.stringify(flat)) : ""; + let formattedFilter = this.formattedFilter(); + return formattedFilter.length > 0 ? "&advanced_filter=" + encodeURIComponent(JSON.stringify(formattedFilter)) : ""; }, getUrlUsers(filter) { let page = 1; @@ -122,8 +167,12 @@ const PMColumnFilterCommonMixin = { let format = "string"; if (column.format) { format = column.format; + if (format === "int") { + // We don't have a field for integers + format = "string"; + } } - if (column.field === "status" || column.field === "assignee") { + if (column.field === "status") { format = "stringSelect"; } return format; @@ -133,17 +182,14 @@ const PMColumnFilterCommonMixin = { if (column.field === "status") { formatRange = this.getStatus(); } - if (column.field === "assignee") { - formatRange = this.viewAssignee; - } return formatRange; }, getOperators(column) { let operators = []; - if (column.field === "case_title" || column.field === "name" || column.field === "process" || column.field === "task_name" || column.field === "participants") { + if (column.field === "case_title" || column.field === "name" || column.field === "process" || column.field === "task_name" || column.field === "participants" || column.field === "assignee") { operators = ["=", "in", "contains", "regex"]; } - if (column.field === "status" || column.field === "assignee") { + if (column.field === "status") { operators = ["=", "in"]; } if (column.field === "initiated_at" || column.field === "completed_at" || column.field === "due_at") { @@ -208,35 +254,25 @@ const PMColumnFilterCommonMixin = { } } }, - getFilterConfiguration(name) { - if ("filter_user" in window.Processmaker) { - this.setFilterPropsFromConfig(window.Processmaker.filter_user); - return; - } - let url = "users/get_filter_configuration/" + name; - ProcessMaker.apiClient.get(url).then(response => { - this.setFilterPropsFromConfig(response.data.data); + getFilterConfiguration() { + const filters = {}; + get(window, 'ProcessMaker.advanced_filter.filters', []).forEach((filter) => { + const key = filter._column_field; + if (!(key in filters)) { + filters[key] = []; + } + filters[key].push(filter); }); - }, - setFilterPropsFromConfig(config) { - if (typeof config !== "object") { - config = {}; - } - if ("filter" in config && typeof config.filter === "object") { - this.advancedFilter = config.filter; - } - if (config?.order?.by && config?.order?.direction) { - this.setOrderByProps(config.order.by, config.order.direction); + this.advancedFilter = filters; + + const order = get(window, 'ProcessMaker.advanced_filter.order'); + if (order?.by && order?.direction) { + this.setOrderByProps(order.by, order.direction); } + this.markStyleWhenColumnSetAFilter(); + window.ProcessMaker.EventBus.$emit("advanced-filter-updated"); }, - json2Array(json) { - let result = []; - for (let i in json) { - result.push(json[i]); - } - return result; - } } }; -export default PMColumnFilterCommonMixin; \ No newline at end of file +export default PMColumnFilterCommonMixin; diff --git a/resources/js/common/advancedFilterStatusMixin.js b/resources/js/common/advancedFilterStatusMixin.js new file mode 100644 index 0000000000..778a200489 --- /dev/null +++ b/resources/js/common/advancedFilterStatusMixin.js @@ -0,0 +1,48 @@ +import { get } from "lodash"; + +export default { + data() { + return { + advancedFilter: [], + }; + }, + mounted() { + this.setAdvancedFilter(); + window.ProcessMaker.EventBus.$on('advanced-filter-updated', this.setAdvancedFilter); + }, + methods: { + setAdvancedFilter() { + this.advancedFilter = get(window, 'ProcessMaker.advanced_filter.filters', []); + }, + formatForBadge(filters, result) { + for(const filter of filters) { + result.push([ + this.formatBadgeSubject(filter), + [{name: this.formatBadgeValue(filter), advanced_filter: true}] + ]); + + if (filter.or && filter.or.length > 0) { + this.formatForBadge(filter.or, result); + } + } + }, + formatBadgeSubject(filter) { + return get(filter, '_column_label', ''); + }, + formatBadgeValue(filter) { + let result = filter.operator; + result = result + " " + filter.value; + return result; + }, + }, + computed: { + formatAdvancedFilterForBadges() { + if (!this.advancedFilter || this.advancedFilter.length === 0) { + return []; + } + const result = []; + this.formatForBadge(this.advancedFilter, result); + return result; + }, + }, +} \ No newline at end of file diff --git a/resources/js/components/PMColumnFilterPopover/PMColumnFilterPopover.vue b/resources/js/components/PMColumnFilterPopover/PMColumnFilterPopover.vue index 6e0a4703a5..30577c932b 100644 --- a/resources/js/components/PMColumnFilterPopover/PMColumnFilterPopover.vue +++ b/resources/js/components/PMColumnFilterPopover/PMColumnFilterPopover.vue @@ -48,11 +48,9 @@ popoverShow: false }; }, - updated() { - this.$emit("onUpdate", this); - }, methods: { onShown() { + this.$emit("onUpdate", this); this.focusCancelButton(); this.closeOnBlur(); }, diff --git a/resources/js/components/shared/PmqlInput.vue b/resources/js/components/shared/PmqlInput.vue index c907ccf623..779d1a0aad 100755 --- a/resources/js/components/shared/PmqlInput.vue +++ b/resources/js/components/shared/PmqlInput.vue @@ -145,14 +145,14 @@
@@ -173,9 +174,12 @@ diff --git a/resources/js/tasks/components/TasksList.vue b/resources/js/tasks/components/TasksList.vue index c9a9590ddc..4e05c9e763 100644 --- a/resources/js/tasks/components/TasksList.vue +++ b/resources/js/tasks/components/TasksList.vue @@ -24,7 +24,7 @@