Fixes #3709, #Fixes 1262 - Core Workflow implementation
This commit is contained in:
parent
60a3758d38
commit
05a471f90d
145 changed files with 6338 additions and 1910 deletions
|
@ -10,16 +10,13 @@ class App.ControllerForm extends App.Controller
|
|||
@[key] = value
|
||||
|
||||
if !@handlers
|
||||
@handlers = []
|
||||
@handlers = [ App.FormHandlerCoreWorkflow.run ]
|
||||
|
||||
if @handlersConfig
|
||||
for key, value of @handlersConfig
|
||||
if value && value.run
|
||||
@handlers.push value.run
|
||||
|
||||
@handlers.push @showHideToggle
|
||||
@handlers.push @requiredMandantoryToggle
|
||||
|
||||
if !@model
|
||||
@model = {}
|
||||
if !@attributes
|
||||
|
@ -312,51 +309,20 @@ class App.ControllerForm extends App.Controller
|
|||
else
|
||||
throw "Invalid UiElement.#{attribute.tag}"
|
||||
|
||||
if attribute.only_shown_if_selectable
|
||||
count = Object.keys(attribute.options).length
|
||||
if !attribute.null && (attribute.nulloption && count is 2) || (!attribute.nulloption && count is 1)
|
||||
attribute.transparent = true
|
||||
attributesNew = clone(attribute)
|
||||
attributesNew.type = 'hidden'
|
||||
attributesNew.value = ''
|
||||
for item in attribute.options
|
||||
if item.value && item.value isnt ''
|
||||
attributesNew.value = item.value
|
||||
item = $( App.view('generic/input')(attribute: attributesNew) )
|
||||
|
||||
if @handlers
|
||||
item.bind('change', (e) =>
|
||||
params = App.ControllerForm.params($(e.target))
|
||||
item_bind = item
|
||||
item_event = 'change'
|
||||
if item.find('.richtext-content').length > 0
|
||||
item_bind = item.find('.richtext-content')
|
||||
item_event = 'blur'
|
||||
|
||||
item_bind.bind(item_event, (e) =>
|
||||
@lastChangedAttribute = attribute.name
|
||||
params = App.ControllerForm.params(@form)
|
||||
for handler in @handlers
|
||||
handler(params, attribute, @attributes, idPrefix, form, @)
|
||||
)
|
||||
|
||||
# bind dependency
|
||||
if @dependency
|
||||
for action in @dependency
|
||||
|
||||
# bind on element if name is matching
|
||||
if action.bind && action.bind.name is attribute.name
|
||||
ui = @
|
||||
do (action, attribute) ->
|
||||
item.bind('change', ->
|
||||
value = $(@).val()
|
||||
if !value
|
||||
value = $(@).find('select, input').val()
|
||||
|
||||
# lookup relation if needed
|
||||
if action.bind.relation
|
||||
data = App[action.bind.relation].find(value)
|
||||
value = data.name
|
||||
|
||||
# check if value is used in condition
|
||||
if _.contains(action.bind.value, value)
|
||||
if action.change.action is 'hide'
|
||||
ui.hide(action.change.name)
|
||||
else
|
||||
ui.show(action.change.name)
|
||||
)
|
||||
|
||||
if !attribute.display || attribute.transparent
|
||||
|
||||
# hide/show item
|
||||
|
@ -387,82 +353,96 @@ class App.ControllerForm extends App.Controller
|
|||
|
||||
return fullItem
|
||||
|
||||
@findFieldByName: (key, el) ->
|
||||
return el.find('[name="' + key + '"]')
|
||||
|
||||
@findFieldByData: (key, el) ->
|
||||
return el.find('[data-name="' + key + '"]')
|
||||
|
||||
@findFieldByGroup: (key, el) ->
|
||||
return el.find('.form-group[data-attribute-name="' + key + '"]')
|
||||
|
||||
@fieldIsShown: (field) ->
|
||||
return !field.closest('.form-group').hasClass('is-hidden')
|
||||
|
||||
@fieldIsMandatory: (field) ->
|
||||
return field.closest('.form-group').hasClass('is-required')
|
||||
|
||||
@fieldIsRemoved: (field) ->
|
||||
return field.closest('.form-group').hasClass('is-removed')
|
||||
|
||||
attributeIsMandatory: (name) ->
|
||||
field_by_name = @constructor.findFieldByName(name, @form)
|
||||
if field_by_name.length > 0
|
||||
return @constructor.fieldIsMandatory(field_by_name)
|
||||
|
||||
field_by_data = @constructor.findFieldByData(name, @form)
|
||||
if field_by_data.length > 0
|
||||
return @constructor.fieldIsMandatory(field_by_data)
|
||||
|
||||
return false
|
||||
|
||||
show: (name, el = @form) ->
|
||||
if !_.isArray(name)
|
||||
name = [name]
|
||||
for key in name
|
||||
el.find('[name="' + key + '"]').closest('.form-group').removeClass('hide')
|
||||
el.find('[name="' + key + '"]').removeClass('is-hidden')
|
||||
el.find('[data-name="' + key + '"]').closest('.form-group').removeClass('hide')
|
||||
el.find('[data-name="' + key + '"]').removeClass('is-hidden')
|
||||
field_by_group = @constructor.findFieldByGroup(key, el)
|
||||
field_by_group.removeClass('hide')
|
||||
field_by_group.removeClass('is-hidden')
|
||||
field_by_group.removeClass('is-removed')
|
||||
|
||||
# hide old validation states
|
||||
if el
|
||||
el.find('.has-error').removeClass('has-error')
|
||||
el.find('.help-inline').html('')
|
||||
|
||||
hide: (name, el = @form) ->
|
||||
hide: (name, el = @form, remove = false) ->
|
||||
if !_.isArray(name)
|
||||
name = [name]
|
||||
for key in name
|
||||
el.find('[name="' + key + '"]').closest('.form-group').addClass('hide')
|
||||
el.find('[name="' + key + '"]').addClass('is-hidden')
|
||||
el.find('[data-name="' + key + '"]').closest('.form-group').addClass('hide')
|
||||
el.find('[data-name="' + key + '"]').addClass('is-hidden')
|
||||
field_by_group = @constructor.findFieldByGroup(key, el)
|
||||
field_by_group.addClass('hide')
|
||||
field_by_group.addClass('is-hidden')
|
||||
if remove
|
||||
field_by_group.addClass('is-removed')
|
||||
|
||||
mandantory: (name, el = @form) ->
|
||||
if !_.isArray(name)
|
||||
name = [name]
|
||||
for key in name
|
||||
el.find('[name="' + key + '"]').attr('required', true)
|
||||
el.find('[name="' + key + '"]').parents('.form-group').find('label span').html('*')
|
||||
field_by_name = @constructor.findFieldByName(key, el)
|
||||
field_by_data = @constructor.findFieldByData(key, el)
|
||||
|
||||
if !@constructor.fieldIsMandatory(field_by_name)
|
||||
field_by_name.attr('required', true)
|
||||
field_by_name.parents('.form-group').find('label span').html('*')
|
||||
field_by_name.closest('.form-group').addClass('is-required')
|
||||
if !@constructor.fieldIsMandatory(field_by_data)
|
||||
field_by_data.attr('required', true)
|
||||
field_by_data.parents('.form-group').find('label span').html('*')
|
||||
field_by_data.closest('.form-group').addClass('is-required')
|
||||
|
||||
optional: (name, el = @form) ->
|
||||
if !_.isArray(name)
|
||||
name = [name]
|
||||
for key in name
|
||||
el.find('[name="' + key + '"]').attr('required', false)
|
||||
el.find('[name="' + key + '"]').parents('.form-group').find('label span').html('')
|
||||
field_by_name = @constructor.findFieldByName(key, el)
|
||||
field_by_data = @constructor.findFieldByData(key, el)
|
||||
|
||||
showHideToggle: (params, changedAttribute, attributes, _classname, form, ui) ->
|
||||
for attribute in attributes
|
||||
if attribute.shown_if
|
||||
hit = false
|
||||
for refAttribute, refValue of attribute.shown_if
|
||||
if params[refAttribute]
|
||||
if _.isArray(refValue)
|
||||
for item in refValue
|
||||
if params[refAttribute].toString() is item.toString()
|
||||
hit = true
|
||||
else if params[refAttribute].toString() is refValue.toString()
|
||||
hit = true
|
||||
if hit
|
||||
ui.show(attribute.name, form)
|
||||
else
|
||||
ui.hide(attribute.name, form)
|
||||
|
||||
requiredMandantoryToggle: (params, changedAttribute, attributes, _classname, form, ui) ->
|
||||
for attribute in attributes
|
||||
if attribute.required_if
|
||||
hit = false
|
||||
for refAttribute, refValue of attribute.required_if
|
||||
if params[refAttribute]
|
||||
if _.isArray(refValue)
|
||||
for item in refValue
|
||||
if params[refAttribute].toString() is item.toString()
|
||||
hit = true
|
||||
else if params[refAttribute].toString() is refValue.toString()
|
||||
hit = true
|
||||
if hit
|
||||
ui.mandantory(attribute.name, form)
|
||||
else
|
||||
ui.optional(attribute.name, form)
|
||||
if @constructor.fieldIsMandatory(field_by_name)
|
||||
field_by_name.attr('required', false)
|
||||
field_by_name.parents('.form-group').find('label span').html('')
|
||||
field_by_name.closest('.form-group').removeClass('is-required')
|
||||
if @constructor.fieldIsMandatory(field_by_data)
|
||||
field_by_data.attr('required', false)
|
||||
field_by_data.parents('.form-group').find('label span').html('')
|
||||
field_by_data.closest('.form-group').removeClass('is-required')
|
||||
|
||||
validate: (params) ->
|
||||
App.Model.validate(
|
||||
model: @model
|
||||
params: params
|
||||
screen: @screen
|
||||
controllerForm: @
|
||||
)
|
||||
|
||||
# get all params of the form
|
||||
|
@ -488,9 +468,10 @@ class App.ControllerForm extends App.Controller
|
|||
|
||||
# array to names
|
||||
for item in array
|
||||
field = @findFieldByName(item.name, lookupForm)
|
||||
|
||||
# check if item is-hidden and should not be used
|
||||
if lookupForm.find('[name="' + item.name + '"]').hasClass('is-hidden') || lookupForm.find('div[data-name="' + item.name + '"]').hasClass('is-hidden')
|
||||
# check if item is-removed and should not be used
|
||||
if @fieldIsRemoved(field)
|
||||
delete param[item.name]
|
||||
continue
|
||||
|
||||
|
@ -562,7 +543,7 @@ class App.ControllerForm extends App.Controller
|
|||
# get {date}
|
||||
if key.substr(0,6) is '{date}'
|
||||
newKey = key.substr(6, key.length)
|
||||
if lookupForm.find("[data-name=\"#{newKey}\"]").hasClass('is-hidden')
|
||||
if lookupForm.find("[data-name=\"#{newKey}\"]").hasClass('is-removed')
|
||||
param[newKey] = null
|
||||
else if param[key]
|
||||
try
|
||||
|
@ -584,7 +565,7 @@ class App.ControllerForm extends App.Controller
|
|||
# get {datetime}
|
||||
else if key.substr(0,10) is '{datetime}'
|
||||
newKey = key.substr(10, key.length)
|
||||
if lookupForm.find("[data-name=\"#{newKey}\"]").hasClass('is-hidden')
|
||||
if lookupForm.find("[data-name=\"#{newKey}\"]").hasClass('is-removed')
|
||||
param[newKey] = null
|
||||
else if param[key]
|
||||
try
|
||||
|
@ -606,6 +587,9 @@ class App.ControllerForm extends App.Controller
|
|||
if parts[0] && parts[1] isnt undefined
|
||||
if parts[1] isnt undefined && !inputSelectObject[ parts[0] ]
|
||||
inputSelectObject[ parts[0] ] = {}
|
||||
if parts[1] is ''
|
||||
delete param[ key ]
|
||||
continue
|
||||
if parts[2] isnt undefined && !inputSelectObject[ parts[0] ][ parts[1] ]
|
||||
inputSelectObject[ parts[0] ][ parts[1] ] = {}
|
||||
if parts[3] isnt undefined && !inputSelectObject[ parts[0] ][ parts[1] ][ parts[2] ]
|
||||
|
@ -631,7 +615,7 @@ class App.ControllerForm extends App.Controller
|
|||
# get {business_hours}
|
||||
if key.substr(0,16) is '{business_hours}'
|
||||
newKey = key.substr(16, key.length)
|
||||
if lookupForm.find("[data-name=\"#{newKey}\"]").hasClass('is-hidden')
|
||||
if lookupForm.find("[data-name=\"#{newKey}\"]").hasClass('is-removed')
|
||||
param[newKey] = null
|
||||
else if param[key]
|
||||
newParams = {}
|
||||
|
@ -737,6 +721,9 @@ class App.ControllerForm extends App.Controller
|
|||
form.prop('disabled', false)
|
||||
|
||||
@validate: (data) ->
|
||||
if data.errors && Object.keys(data.errors).length == 1 && data.errors._core_workflow isnt undefined
|
||||
App.FormHandlerCoreWorkflow.delaySubmit(data.errors._core_workflow.controllerForm, data.errors._core_workflow.target || data.form)
|
||||
return
|
||||
|
||||
lookupForm = @findForm(data.form)
|
||||
|
||||
|
|
|
@ -27,7 +27,9 @@ class App.ControllerGenericEdit extends App.ControllerModal
|
|||
return false
|
||||
|
||||
# validate
|
||||
errors = @item.validate()
|
||||
errors = @item.validate(
|
||||
controllerForm: @controller
|
||||
)
|
||||
if errors
|
||||
@log 'error', errors
|
||||
@formValidate( form: e.target, errors: errors )
|
||||
|
|
|
@ -175,6 +175,7 @@ class App.ControllerGenericIndex extends App.Controller
|
|||
small: @small
|
||||
large: @large
|
||||
veryLarge: @veryLarge
|
||||
handlers: @handlers
|
||||
)
|
||||
|
||||
new: (e) ->
|
||||
|
@ -186,6 +187,7 @@ class App.ControllerGenericIndex extends App.Controller
|
|||
small: @small
|
||||
large: @large
|
||||
veryLarge: @veryLarge
|
||||
handlers: @handlers
|
||||
)
|
||||
|
||||
payload: (e) ->
|
||||
|
|
|
@ -10,7 +10,7 @@ class App.ControllerGenericNew extends App.ControllerModal
|
|||
@controller = new App.ControllerForm(
|
||||
model: App[ @genericObject ]
|
||||
params: @item
|
||||
screen: @screen || 'edit'
|
||||
screen: @screen || 'create'
|
||||
autofocus: true
|
||||
handlers: @handlers
|
||||
)
|
||||
|
@ -28,7 +28,9 @@ class App.ControllerGenericNew extends App.ControllerModal
|
|||
return false
|
||||
|
||||
# validate
|
||||
errors = object.validate()
|
||||
errors = object.validate(
|
||||
controllerForm: @controller
|
||||
)
|
||||
if errors
|
||||
@log 'error', errors
|
||||
@formValidate( form: e.target, errors: errors )
|
||||
|
|
|
@ -0,0 +1,575 @@
|
|||
# coffeelint: disable=camel_case_classes
|
||||
class App.UiElement.ApplicationSelector
|
||||
@defaults: (attribute = {}, params = {}) ->
|
||||
defaults = ['ticket.state_id']
|
||||
|
||||
groups =
|
||||
ticket:
|
||||
name: 'Ticket'
|
||||
model: 'Ticket'
|
||||
article:
|
||||
name: 'Article'
|
||||
model: 'TicketArticle'
|
||||
customer:
|
||||
name: 'Customer'
|
||||
model: 'User'
|
||||
organization:
|
||||
name: 'Organization'
|
||||
model: 'Organization'
|
||||
|
||||
if attribute.executionTime
|
||||
groups.execution_time =
|
||||
name: 'Execution Time'
|
||||
|
||||
operators_type =
|
||||
'^datetime$': ['before (absolute)', 'after (absolute)', 'before (relative)', 'after (relative)', 'within next (relative)', 'within last (relative)', 'till (relative)', 'from (relative)']
|
||||
'^timestamp$': ['before (absolute)', 'after (absolute)', 'before (relative)', 'after (relative)', 'within next (relative)', 'within last (relative)', 'till (relative)', 'from (relative)']
|
||||
'^date$': ['before (absolute)', 'after (absolute)', 'before (relative)', 'after (relative)', 'within next (relative)', 'within last (relative)']
|
||||
'boolean$': ['is', 'is not']
|
||||
'integer$': ['is', 'is not']
|
||||
'^radio$': ['is', 'is not']
|
||||
'^select$': ['is', 'is not']
|
||||
'^tree_select$': ['is', 'is not']
|
||||
'^input$': ['contains', 'contains not']
|
||||
'^richtext$': ['contains', 'contains not']
|
||||
'^textarea$': ['contains', 'contains not']
|
||||
'^tag$': ['contains all', 'contains one', 'contains all not', 'contains one not']
|
||||
|
||||
if attribute.hasChanged
|
||||
operators_type =
|
||||
'^datetime$': ['before (absolute)', 'after (absolute)', 'before (relative)', 'after (relative)', 'within next (relative)', 'within last (relative)', 'till (relative)', 'from (relative)', 'has changed']
|
||||
'^timestamp$': ['before (absolute)', 'after (absolute)', 'before (relative)', 'after (relative)', 'within next (relative)', 'within last (relative)', 'till (relative)', 'from (relative)', 'has changed']
|
||||
'^date$': ['before (absolute)', 'after (absolute)', 'before (relative)', 'after (relative)', 'within next (relative)', 'within last (relative)', 'till (relative)', 'from (relative)', 'has changed']
|
||||
'boolean$': ['is', 'is not', 'has changed']
|
||||
'integer$': ['is', 'is not', 'has changed']
|
||||
'^radio$': ['is', 'is not', 'has changed']
|
||||
'^select$': ['is', 'is not', 'has changed']
|
||||
'^tree_select$': ['is', 'is not', 'has changed']
|
||||
'^input$': ['contains', 'contains not', 'has changed']
|
||||
'^richtext$': ['contains', 'contains not', 'has changed']
|
||||
'^textarea$': ['contains', 'contains not', 'has changed']
|
||||
'^tag$': ['contains all', 'contains one', 'contains all not', 'contains one not']
|
||||
|
||||
operators_name =
|
||||
'_id$': ['is', 'is not']
|
||||
'_ids$': ['is', 'is not']
|
||||
|
||||
if attribute.hasChanged
|
||||
operators_name =
|
||||
'_id$': ['is', 'is not', 'has changed']
|
||||
'_ids$': ['is', 'is not', 'has changed']
|
||||
|
||||
# merge config
|
||||
elements = {}
|
||||
|
||||
if attribute.article is false
|
||||
delete groups.article
|
||||
|
||||
if attribute.action
|
||||
elements['ticket.action'] =
|
||||
name: 'action'
|
||||
display: 'Action'
|
||||
tag: 'select'
|
||||
null: false
|
||||
translate: true
|
||||
options:
|
||||
create: 'created'
|
||||
update: 'updated'
|
||||
'update.merged_into': 'merged into'
|
||||
'update.received_merge': 'received merge'
|
||||
operator: ['is', 'is not']
|
||||
|
||||
for groupKey, groupMeta of groups
|
||||
if groupKey is 'execution_time'
|
||||
if attribute.executionTime
|
||||
elements['execution_time.calendar_id'] =
|
||||
name: 'calendar_id'
|
||||
display: 'Calendar'
|
||||
tag: 'select'
|
||||
relation: 'Calendar'
|
||||
null: false
|
||||
translate: false
|
||||
operator: ['is in working time', 'is not in working time']
|
||||
|
||||
else
|
||||
for row in App[groupMeta.model].configure_attributes
|
||||
# ignore passwords and relations
|
||||
if row.type isnt 'password' && row.name.substr(row.name.length-4,4) isnt '_ids' && row.searchable isnt false
|
||||
config = _.clone(row)
|
||||
if config.type is 'email' || config.type is 'tel'
|
||||
config.type = 'text'
|
||||
for operatorRegEx, operator of operators_type
|
||||
myRegExp = new RegExp(operatorRegEx, 'i')
|
||||
if config.tag && config.tag.match(myRegExp)
|
||||
config.operator = operator
|
||||
elements["#{groupKey}.#{config.name}"] = config
|
||||
for operatorRegEx, operator of operators_name
|
||||
myRegExp = new RegExp(operatorRegEx, 'i')
|
||||
if config.name && config.name.match(myRegExp)
|
||||
config.operator = operator
|
||||
elements["#{groupKey}.#{config.name}"] = config
|
||||
|
||||
if config.tag == 'select'
|
||||
config.multiple = true
|
||||
|
||||
if attribute.out_of_office
|
||||
elements['ticket.out_of_office_replacement_id'] =
|
||||
name: 'out_of_office_replacement_id'
|
||||
display: 'Out of office replacement'
|
||||
tag: 'autocompletion_ajax'
|
||||
relation: 'User'
|
||||
null: false
|
||||
translate: true
|
||||
operator: ['is', 'is not']
|
||||
|
||||
# Remove 'has changed' operator from attributes which don't support the operator.
|
||||
['ticket.created_at', 'ticket.updated_at'].forEach (element_name) ->
|
||||
elements[element_name]['operator'] = elements[element_name]['operator'].filter (item) -> item != 'has changed'
|
||||
|
||||
elements['ticket.mention_user_ids'] =
|
||||
name: 'mention_user_ids'
|
||||
display: 'Subscribe'
|
||||
tag: 'autocompletion_ajax'
|
||||
relation: 'User'
|
||||
null: false
|
||||
translate: true
|
||||
operator: ['is', 'is not']
|
||||
|
||||
[defaults, groups, elements]
|
||||
|
||||
@rowContainer: (groups, elements, attribute) ->
|
||||
row = $( App.view('generic/application_selector_row')(
|
||||
attribute: attribute
|
||||
pre_condition: @HasPreCondition()
|
||||
) )
|
||||
selector = @buildAttributeSelector(groups, elements)
|
||||
row.find('.js-attributeSelector').prepend(selector)
|
||||
row
|
||||
|
||||
@emptyBody: (attribute) ->
|
||||
return $( App.view('generic/application_selector_empty')(
|
||||
attribute: attribute
|
||||
) )
|
||||
|
||||
@render: (attribute, params = {}) ->
|
||||
|
||||
[defaults, groups, elements] = @defaults(attribute, params)
|
||||
|
||||
item = $( App.view('generic/application_selector')(attribute: attribute) )
|
||||
|
||||
# add filter
|
||||
item.delegate('.js-add', 'click', (e) =>
|
||||
element = $(e.target).closest('.js-filterElement')
|
||||
|
||||
# add first available attribute
|
||||
field = undefined
|
||||
for groupAndAttribute, _config of elements
|
||||
if @hasDuplicateSelector()
|
||||
field = groupAndAttribute
|
||||
break
|
||||
else if !item.find(".js-attributeSelector [value=\"#{groupAndAttribute}\"]:selected").get(0)
|
||||
field = groupAndAttribute
|
||||
break
|
||||
return if !field
|
||||
row = @rowContainer(groups, elements, attribute)
|
||||
|
||||
emptyRow = item.find('div.horizontal-filter-body')
|
||||
if emptyRow.find('input.empty:hidden').length > 0 && @hasEmptySelectorAtStart()
|
||||
emptyRow.parent().replaceWith(row)
|
||||
else
|
||||
element.after(row)
|
||||
row.find('.js-attributeSelector select').trigger('change')
|
||||
|
||||
@rebuildAttributeSelectors(item, row, field, elements, {}, attribute)
|
||||
|
||||
if attribute.preview isnt false
|
||||
@preview(item)
|
||||
)
|
||||
|
||||
# remove filter
|
||||
item.delegate('.js-remove', 'click', (e) =>
|
||||
return if $(e.currentTarget).hasClass('is-disabled')
|
||||
|
||||
if @hasEmptySelectorAtStart()
|
||||
if item.find('.js-remove').length > 1
|
||||
$(e.target).closest('.js-filterElement').remove()
|
||||
else
|
||||
$(e.target).closest('.js-filterElement').find('div.horizontal-filter-body').html(@emptyBody(attribute))
|
||||
else
|
||||
$(e.target).closest('.js-filterElement').remove()
|
||||
|
||||
@updateAttributeSelectors(item)
|
||||
if attribute.preview isnt false
|
||||
@preview(item)
|
||||
)
|
||||
|
||||
paramValue = {}
|
||||
for groupAndAttribute, meta of params[attribute.name]
|
||||
continue if !elements[groupAndAttribute]
|
||||
paramValue[groupAndAttribute] = meta
|
||||
|
||||
# build initial params
|
||||
if !_.isEmpty(paramValue)
|
||||
@renderParamValue(item, attribute, params, paramValue)
|
||||
else
|
||||
if @hasEmptySelectorAtStart()
|
||||
row = @rowContainer(groups, elements, attribute)
|
||||
row.find('.horizontal-filter-body').html(@emptyBody(attribute))
|
||||
item.filter('.js-filter').append(row)
|
||||
else
|
||||
for groupAndAttribute in defaults
|
||||
|
||||
# build and append
|
||||
row = @rowContainer(groups, elements, attribute)
|
||||
@rebuildAttributeSelectors(item, row, groupAndAttribute, elements, {}, attribute)
|
||||
item.filter('.js-filter').append(row)
|
||||
|
||||
# change attribute selector
|
||||
item.delegate('.js-attributeSelector select', 'change', (e) =>
|
||||
elementRow = $(e.target).closest('.js-filterElement')
|
||||
groupAndAttribute = elementRow.find('.js-attributeSelector option:selected').attr('value')
|
||||
return if !groupAndAttribute
|
||||
@rebuildAttributeSelectors(item, elementRow, groupAndAttribute, elements, {}, attribute)
|
||||
@updateAttributeSelectors(item)
|
||||
)
|
||||
|
||||
# change operator selector
|
||||
item.delegate('.js-operator select', 'change', (e) =>
|
||||
elementRow = $(e.target).closest('.js-filterElement')
|
||||
groupAndAttribute = elementRow.find('.js-attributeSelector option:selected').attr('value')
|
||||
return if !groupAndAttribute
|
||||
@buildOperator(item, elementRow, groupAndAttribute, elements, {}, attribute)
|
||||
)
|
||||
|
||||
# bind for preview
|
||||
if attribute.preview isnt false
|
||||
search = =>
|
||||
@preview(item)
|
||||
|
||||
triggerSearch = ->
|
||||
item.find('.js-previewCounterContainer').addClass('hide')
|
||||
item.find('.js-previewLoader').removeClass('hide')
|
||||
App.Delay.set(
|
||||
search,
|
||||
600,
|
||||
'preview',
|
||||
)
|
||||
|
||||
item.on('change', 'select', (e) ->
|
||||
triggerSearch()
|
||||
)
|
||||
item.on('change keyup', 'input', (e) ->
|
||||
triggerSearch()
|
||||
)
|
||||
|
||||
@disableRemoveForOneAttribute(item)
|
||||
item
|
||||
|
||||
@renderParamValue: (item, attribute, params, paramValue) ->
|
||||
[defaults, groups, elements] = @defaults(attribute, params)
|
||||
|
||||
for groupAndAttribute, meta of paramValue
|
||||
|
||||
# build and append
|
||||
row = @rowContainer(groups, elements, attribute)
|
||||
@rebuildAttributeSelectors(item, row, groupAndAttribute, elements, meta, attribute)
|
||||
item.filter('.js-filter').append(row)
|
||||
|
||||
@preview: (item) ->
|
||||
params = App.ControllerForm.params(item)
|
||||
App.Ajax.request(
|
||||
id: 'application_selector'
|
||||
type: 'POST'
|
||||
url: "#{App.Config.get('api_path')}/tickets/selector"
|
||||
data: JSON.stringify(params)
|
||||
processData: true,
|
||||
success: (data, status, xhr) =>
|
||||
App.Collection.loadAssets(data.assets)
|
||||
item.find('.js-previewCounterContainer').removeClass('hide')
|
||||
item.find('.js-previewLoader').addClass('hide')
|
||||
@ticketTable(data.ticket_ids, data.ticket_count, item)
|
||||
)
|
||||
|
||||
@ticketTable: (ticket_ids, ticket_count, item) ->
|
||||
item.find('.js-previewCounter').html(ticket_count)
|
||||
new App.TicketList(
|
||||
tableId: 'ticket-selector'
|
||||
el: item.find('.js-previewTable')
|
||||
ticket_ids: ticket_ids
|
||||
)
|
||||
|
||||
@buildAttributeSelector: (groups, elements) ->
|
||||
selection = $('<select class="form-control"></select>')
|
||||
for groupKey, groupMeta of groups
|
||||
groupKeyClass = groupKey.replace('.', '-')
|
||||
displayName = App.i18n.translateInline(groupMeta.name)
|
||||
selection.closest('select').append("<optgroup label=\"#{displayName}\" class=\"js-#{groupKeyClass}\"></optgroup>")
|
||||
optgroup = selection.find("optgroup.js-#{groupKeyClass}")
|
||||
for elementKey, elementGroup of elements
|
||||
spacer = elementKey.split(/\./).slice(0, -1).join('.')
|
||||
if spacer is groupKey
|
||||
attributeConfig = elements[elementKey]
|
||||
if attributeConfig.operator
|
||||
displayName = App.i18n.translateInline(attributeConfig.display)
|
||||
optgroup.append("<option value=\"#{elementKey}\">#{displayName}</option>")
|
||||
selection
|
||||
|
||||
# disable - if we only have one attribute
|
||||
@disableRemoveForOneAttribute: (elementFull) ->
|
||||
if @hasEmptySelectorAtStart()
|
||||
if elementFull.find('div.horizontal-filter-body input.empty:hidden').length > 0 && elementFull.find('.js-remove').length < 2
|
||||
elementFull.find('.js-remove').addClass('is-disabled')
|
||||
else
|
||||
elementFull.find('.js-remove').removeClass('is-disabled')
|
||||
else
|
||||
if elementFull.find('.js-attributeSelector select').length > 1
|
||||
elementFull.find('.js-remove').removeClass('is-disabled')
|
||||
else
|
||||
elementFull.find('.js-remove').addClass('is-disabled')
|
||||
|
||||
@updateAttributeSelectors: (elementFull) ->
|
||||
if !@hasDuplicateSelector()
|
||||
|
||||
# enable all
|
||||
elementFull.find('.js-attributeSelector select option').removeAttr('disabled')
|
||||
|
||||
# disable all used attributes
|
||||
elementFull.find('.js-attributeSelector select').each(->
|
||||
keyLocal = $(@).val()
|
||||
elementFull.find('.js-attributeSelector select option[value="' + keyLocal + '"]').attr('disabled', true)
|
||||
)
|
||||
|
||||
# disable - if we only have one attribute
|
||||
@disableRemoveForOneAttribute(elementFull)
|
||||
|
||||
@rebuildAttributeSelectors: (elementFull, elementRow, groupAndAttribute, elements, meta, attribute) ->
|
||||
|
||||
# set attribute
|
||||
if groupAndAttribute
|
||||
elementRow.find('.js-attributeSelector select').val(groupAndAttribute)
|
||||
|
||||
@buildOperator(elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
|
||||
|
||||
@buildOperator: (elementFull, elementRow, groupAndAttribute, elements, meta, attribute) ->
|
||||
currentOperator = elementRow.find('.js-operator option:selected').attr('value')
|
||||
|
||||
name = "#{attribute.name}::#{groupAndAttribute}::operator"
|
||||
|
||||
if !meta.operator && currentOperator
|
||||
meta.operator = currentOperator
|
||||
|
||||
selection = $("<select class=\"form-control\" name=\"#{name}\"></select>")
|
||||
|
||||
attributeConfig = elements[groupAndAttribute]
|
||||
if attributeConfig.operator
|
||||
|
||||
# check if operator exists
|
||||
operatorExists = false
|
||||
for operator in attributeConfig.operator
|
||||
if meta.operator is operator
|
||||
operatorExists = true
|
||||
break
|
||||
|
||||
if !operatorExists
|
||||
for operator in attributeConfig.operator
|
||||
meta.operator = operator
|
||||
break
|
||||
|
||||
for operator in attributeConfig.operator
|
||||
operatorName = App.i18n.translateInline(operator.replace(/_/g, ' '))
|
||||
selected = ''
|
||||
if !groupAndAttribute.match(/^ticket/) && operator is 'has changed'
|
||||
# do nothing, only show "has changed" in ticket attributes
|
||||
else
|
||||
if meta.operator is operator
|
||||
selected = 'selected="selected"'
|
||||
selection.append("<option value=\"#{operator}\" #{selected}>#{operatorName}</option>")
|
||||
selection
|
||||
|
||||
elementRow.find('.js-operator select').replaceWith(selection)
|
||||
|
||||
if @HasPreCondition()
|
||||
@buildPreCondition(elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
|
||||
else
|
||||
@buildValue(elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
|
||||
|
||||
@buildPreCondition: (elementFull, elementRow, groupAndAttribute, elements, meta, attributeConfig) ->
|
||||
currentOperator = elementRow.find('.js-operator option:selected').attr('value')
|
||||
currentPreCondition = elementRow.find('.js-preCondition option:selected').attr('value')
|
||||
|
||||
if !meta.pre_condition
|
||||
meta.pre_condition = currentPreCondition
|
||||
|
||||
toggleValue = =>
|
||||
preCondition = elementRow.find('.js-preCondition option:selected').attr('value')
|
||||
if preCondition isnt 'specific'
|
||||
elementRow.find('.js-value select').html('')
|
||||
elementRow.find('.js-value').addClass('hide')
|
||||
else
|
||||
elementRow.find('.js-value').removeClass('hide')
|
||||
@buildValue(elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
|
||||
|
||||
# force to use auto completion on user lookup
|
||||
attribute = _.clone(attributeConfig)
|
||||
|
||||
name = "#{attribute.name}::#{groupAndAttribute}::value"
|
||||
attributeSelected = elements[groupAndAttribute]
|
||||
|
||||
preCondition = false
|
||||
if attributeSelected.relation is 'User'
|
||||
preCondition = 'user'
|
||||
attribute.tag = 'user_autocompletion'
|
||||
if attributeSelected.relation is 'Organization'
|
||||
preCondition = 'org'
|
||||
attribute.tag = 'autocompletion_ajax'
|
||||
if !preCondition
|
||||
elementRow.find('.js-preCondition select').html('')
|
||||
elementRow.find('.js-preCondition').closest('.controls').addClass('hide')
|
||||
toggleValue()
|
||||
@buildValue(elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
|
||||
return
|
||||
|
||||
elementRow.find('.js-preCondition').removeClass('hide')
|
||||
name = "#{attribute.name}::#{groupAndAttribute}::pre_condition"
|
||||
|
||||
selection = $("<select class=\"form-control\" name=\"#{name}\" ></select>")
|
||||
options = {}
|
||||
if preCondition is 'user'
|
||||
options =
|
||||
'current_user.id': App.i18n.translateInline('current user')
|
||||
'specific': App.i18n.translateInline('specific user')
|
||||
'not_set': App.i18n.translateInline('not set (not defined)')
|
||||
else if preCondition is 'org'
|
||||
options =
|
||||
'current_user.organization_id': App.i18n.translateInline('current user organization')
|
||||
'specific': App.i18n.translateInline('specific organization')
|
||||
'not_set': App.i18n.translateInline('not set (not defined)')
|
||||
|
||||
for key, value of options
|
||||
selected = ''
|
||||
if key is meta.pre_condition
|
||||
selected = 'selected="selected"'
|
||||
selection.append("<option value=\"#{key}\" #{selected}>#{App.i18n.translateInline(value)}</option>")
|
||||
elementRow.find('.js-preCondition').closest('.controls').removeClass('hide')
|
||||
elementRow.find('.js-preCondition select').replaceWith(selection)
|
||||
|
||||
elementRow.find('.js-preCondition select').bind('change', (e) ->
|
||||
toggleValue()
|
||||
)
|
||||
|
||||
@buildValue(elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
|
||||
toggleValue()
|
||||
|
||||
@buildValueConfigValue: (elementFull, elementRow, groupAndAttribute, elements, meta, attribute) ->
|
||||
return _.clone(attribute.value[groupAndAttribute]['value'])
|
||||
|
||||
@buildValueName: (elementFull, elementRow, groupAndAttribute, elements, meta, attribute) ->
|
||||
return "#{attribute.name}::#{groupAndAttribute}::value"
|
||||
|
||||
@buildValue: (elementFull, elementRow, groupAndAttribute, elements, meta, attribute) ->
|
||||
name = @buildValueName(elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
|
||||
|
||||
# build new item
|
||||
attributeConfig = elements[groupAndAttribute]
|
||||
config = _.clone(attributeConfig)
|
||||
|
||||
if config.relation is 'User'
|
||||
config.tag = 'user_autocompletion'
|
||||
if config.relation is 'Organization'
|
||||
config.tag = 'autocompletion_ajax'
|
||||
|
||||
# render ui element
|
||||
item = ''
|
||||
if config && App.UiElement[config.tag]
|
||||
config['name'] = name
|
||||
if attribute.value && attribute.value[groupAndAttribute]
|
||||
config['value'] = @buildValueConfigValue(elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
|
||||
if 'multiple' of config
|
||||
config.multiple = true
|
||||
config.nulloption = false
|
||||
if config.relation is 'User'
|
||||
config.multiple = false
|
||||
config.nulloption = false
|
||||
config.guess = false
|
||||
config.disableCreateObject = true
|
||||
if config.relation is 'Organization'
|
||||
config.multiple = false
|
||||
config.nulloption = false
|
||||
config.guess = false
|
||||
if config.tag is 'checkbox'
|
||||
config.tag = 'select'
|
||||
tagSearch = "#{config.tag}_search"
|
||||
if App.UiElement[tagSearch]
|
||||
item = App.UiElement[tagSearch].render(config, {})
|
||||
else
|
||||
item = App.UiElement[config.tag].render(config, {})
|
||||
if meta.operator is 'before (relative)' || meta.operator is 'within next (relative)' || meta.operator is 'within last (relative)' || meta.operator is 'after (relative)' || meta.operator is 'from (relative)' || meta.operator is 'till (relative)'
|
||||
config['name'] = "#{attribute.name}::#{groupAndAttribute}"
|
||||
if attribute.value && attribute.value[groupAndAttribute]
|
||||
config['value'] = _.clone(attribute.value[groupAndAttribute])
|
||||
item = App.UiElement['time_range'].render(config, {})
|
||||
|
||||
elementRow.find('.js-value').removeClass('hide').html(item)
|
||||
if meta.operator is 'has changed'
|
||||
elementRow.find('.js-value').addClass('hide')
|
||||
elementRow.find('.js-preCondition').closest('.controls').addClass('hide')
|
||||
else
|
||||
elementRow.find('.js-value').removeClass('hide')
|
||||
|
||||
@humanText: (condition) ->
|
||||
none = App.i18n.translateContent('No filter.')
|
||||
return [none] if _.isEmpty(condition)
|
||||
[defaults, groups, elements] = @defaults()
|
||||
rules = []
|
||||
for attribute, meta of condition
|
||||
|
||||
objectAttribute = attribute.split(/\./)
|
||||
|
||||
# get stored params
|
||||
if meta && objectAttribute[1]
|
||||
operator = meta.operator
|
||||
value = meta.value
|
||||
model = toCamelCase(objectAttribute[0])
|
||||
config = elements[attribute]
|
||||
|
||||
valueHuman = []
|
||||
if _.isArray(value)
|
||||
for data in value
|
||||
r = @humanTextLookup(config, data)
|
||||
valueHuman.push r
|
||||
else
|
||||
valueHuman.push @humanTextLookup(config, value)
|
||||
|
||||
if valueHuman.join
|
||||
valueHuman = valueHuman.join(', ')
|
||||
rules.push "#{App.i18n.translateContent('Where')} <b>#{App.i18n.translateContent(model)} -> #{App.i18n.translateContent(config.display)}</b> #{App.i18n.translateContent(operator)} <b>#{valueHuman}</b>."
|
||||
|
||||
return [none] if _.isEmpty(rules)
|
||||
rules
|
||||
|
||||
@humanTextLookup: (config, value) ->
|
||||
return value if !App[config.relation]
|
||||
return value if !App[config.relation].exists(value)
|
||||
data = App[config.relation].fullLocal(value)
|
||||
return value if !data
|
||||
if data.displayName
|
||||
return App.i18n.translateContent(data.displayName())
|
||||
valueHuman.push App.i18n.translateContent(data.name)
|
||||
|
||||
@HasPreCondition: ->
|
||||
return true
|
||||
|
||||
@hasEmptySelectorAtStart: ->
|
||||
return false
|
||||
|
||||
@hasDuplicateSelector: ->
|
||||
return false
|
||||
|
||||
@coreWorkflowCustomModulesActive: ->
|
||||
enabled = false
|
||||
for workflow in App.CoreWorkflow.all()
|
||||
continue if !workflow.changeable
|
||||
continue if !workflow.condition_saved['custom.module'] && !workflow.condition_selected['custom.module'] && !workflow.perform['custom.module']
|
||||
enabled = true
|
||||
break
|
||||
return enabled
|
|
@ -186,10 +186,20 @@ class App.UiElement.ApplicationUiElement
|
|||
return if !attribute.filter
|
||||
return if _.isEmpty(attribute.options)
|
||||
|
||||
return if typeof attribute.filter isnt 'function'
|
||||
if typeof attribute.filter is 'function'
|
||||
App.Log.debug 'ControllerForm', '_filterOption:filter-function'
|
||||
|
||||
attribute.options = attribute.filter(attribute.options, attribute)
|
||||
else if !attribute.relation && attribute.filter && _.isArray(attribute.filter)
|
||||
@filterOptionArray(attribute)
|
||||
|
||||
@filterOptionArray: (attribute) ->
|
||||
result = []
|
||||
for option in attribute.options
|
||||
for value in attribute.filter
|
||||
if value.toString() == option.value.toString()
|
||||
result.push(option)
|
||||
|
||||
attribute.options = result
|
||||
|
||||
# set selected attributes
|
||||
@selectedOptions: (attribute) ->
|
||||
|
|
|
@ -0,0 +1,188 @@
|
|||
# coffeelint: disable=camel_case_classes
|
||||
class App.UiElement.core_workflow_condition extends App.UiElement.ApplicationSelector
|
||||
@defaults: (attribute = {}, params = {}) ->
|
||||
defaults = []
|
||||
|
||||
groups =
|
||||
ticket:
|
||||
name: 'Ticket'
|
||||
model: 'Ticket'
|
||||
model_show: ['Ticket']
|
||||
group:
|
||||
name: 'Group'
|
||||
model: 'Group'
|
||||
model_show: ['Group']
|
||||
user:
|
||||
name: 'User'
|
||||
model: 'User'
|
||||
model_show: ['User']
|
||||
customer:
|
||||
name: 'Customer'
|
||||
model: 'User'
|
||||
model_show: ['Ticket']
|
||||
organization:
|
||||
name: 'Organization'
|
||||
model: 'Organization'
|
||||
model_show: ['Organization']
|
||||
'customer.organization':
|
||||
name: 'Organization'
|
||||
model: 'Organization'
|
||||
model_show: ['Ticket']
|
||||
session:
|
||||
name: 'Session'
|
||||
model: 'User'
|
||||
model_show: ['Ticket']
|
||||
|
||||
showCustomModules = @coreWorkflowCustomModulesActive()
|
||||
if showCustomModules
|
||||
groups['custom'] =
|
||||
name: 'Custom'
|
||||
model_show: ['Ticket', 'User', 'Organization', 'Sla']
|
||||
|
||||
currentObject = params.object
|
||||
if attribute.workflow_object isnt undefined
|
||||
currentObject = attribute.workflow_object
|
||||
|
||||
if !_.isEmpty(currentObject)
|
||||
for key, data of groups
|
||||
continue if _.contains(data.model_show, currentObject)
|
||||
delete groups[key]
|
||||
|
||||
operatorsType =
|
||||
'active$': ['is']
|
||||
'boolean$': ['is', 'is not', 'is set', 'not set']
|
||||
'integer$': ['is', 'is not', 'is set', 'not set']
|
||||
'^select$': ['is', 'is not', 'is set', 'not set']
|
||||
'^tree_select$': ['is', 'is not', 'is set', 'not set']
|
||||
'^(input|textarea|richtext)$': ['is', 'is not', 'is set', 'not set', 'regex match', 'regex mismatch']
|
||||
|
||||
operatorsName =
|
||||
'_id$': ['is', 'is not', 'is set', 'not set']
|
||||
'_ids$': ['is', 'is not', 'is set', 'not set']
|
||||
|
||||
# merge config
|
||||
elements = {}
|
||||
|
||||
for groupKey, groupMeta of groups
|
||||
if groupKey is 'custom'
|
||||
continue if !showCustomModules
|
||||
|
||||
options = {}
|
||||
for module in App.CoreWorkflowCustomModule.all()
|
||||
options[module.name] = module.name
|
||||
|
||||
elements['custom.module'] = {
|
||||
name: 'module',
|
||||
display: 'Module',
|
||||
tag: 'select',
|
||||
multiple: true,
|
||||
options: options,
|
||||
null: false,
|
||||
operator: ['match one module', 'match all modules', 'match no modules']
|
||||
}
|
||||
continue
|
||||
if groupKey is 'session'
|
||||
elements['session.role_ids'] = {
|
||||
name: 'role_ids',
|
||||
display: 'Role',
|
||||
tag: 'select',
|
||||
relation: 'Role',
|
||||
null: false,
|
||||
operator: ['is', 'is not'],
|
||||
multiple: true
|
||||
}
|
||||
elements['session.group_ids_read'] = {
|
||||
name: 'group_ids_read',
|
||||
display: 'Group (read)',
|
||||
tag: 'select',
|
||||
relation: 'Group',
|
||||
null: false,
|
||||
operator: ['is', 'is not'],
|
||||
multiple: true
|
||||
}
|
||||
elements['session.group_ids_create'] = {
|
||||
name: 'group_ids_create',
|
||||
display: 'Group (create)',
|
||||
tag: 'select',
|
||||
relation: 'Group',
|
||||
null: false,
|
||||
operator: ['is', 'is not'],
|
||||
multiple: true
|
||||
}
|
||||
elements['session.group_ids_change'] = {
|
||||
name: 'group_ids_change',
|
||||
display: 'Group (change)',
|
||||
tag: 'select',
|
||||
relation: 'Group',
|
||||
null: false,
|
||||
operator: ['is', 'is not'],
|
||||
multiple: true
|
||||
}
|
||||
elements['session.group_ids_overview'] = {
|
||||
name: 'group_ids_overview',
|
||||
display: 'Group (overview)',
|
||||
tag: 'select',
|
||||
relation: 'Group',
|
||||
null: false,
|
||||
operator: ['is', 'is not'],
|
||||
multiple: true
|
||||
}
|
||||
elements['session.group_ids_full'] = {
|
||||
name: 'group_ids_full',
|
||||
display: 'Group (full)',
|
||||
tag: 'select',
|
||||
relation: 'Group',
|
||||
null: false,
|
||||
operator: ['is', 'is not'],
|
||||
multiple: true
|
||||
}
|
||||
elements['session.permission_ids'] = {
|
||||
name: 'permission_ids',
|
||||
display: 'Permissions',
|
||||
tag: 'select',
|
||||
relation: 'Permission',
|
||||
null: false,
|
||||
operator: ['is', 'is not'],
|
||||
multiple: true
|
||||
}
|
||||
|
||||
for row in App[groupMeta.model].configure_attributes
|
||||
continue if !_.contains(['input', 'textarea', 'richtext', 'select', 'integer', 'boolean', 'active', 'tree_select', 'autocompletion_ajax'], row.tag)
|
||||
continue if groupKey is 'ticket' && _.contains(['number', 'title'], row.name)
|
||||
|
||||
# ignore passwords and relations
|
||||
if row.type isnt 'password' && row.name.substr(row.name.length-4,4) isnt '_ids' && row.searchable isnt false
|
||||
config = _.clone(row)
|
||||
if config.tag is 'select'
|
||||
config.multiple = true
|
||||
config.default = undefined
|
||||
if config.type is 'email' || config.type is 'tel'
|
||||
config.type = 'text'
|
||||
for operatorRegEx, operator of operatorsType
|
||||
myRegExp = new RegExp(operatorRegEx, 'i')
|
||||
if config.tag && config.tag.match(myRegExp)
|
||||
config.operator = operator
|
||||
elements["#{groupKey}.#{config.name}"] = config
|
||||
for operatorRegEx, operator of operatorsName
|
||||
myRegExp = new RegExp(operatorRegEx, 'i')
|
||||
if config.name && config.name.match(myRegExp)
|
||||
config.operator = operator
|
||||
elements["#{groupKey}.#{config.name}"] = config
|
||||
|
||||
[defaults, groups, elements]
|
||||
|
||||
@buildValue: (elementFull, elementRow, groupAndAttribute, elements, meta, attribute) ->
|
||||
currentOperator = elementRow.find('.js-operator option:selected').attr('value')
|
||||
name = @buildValueName(elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
|
||||
|
||||
if _.contains(['is set', 'not set'], currentOperator)
|
||||
elementRow.find('.js-value').addClass('hide').html('<input type="hidden" name="' + name + '" value="true" />')
|
||||
return
|
||||
|
||||
super(elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
|
||||
|
||||
@HasPreCondition: ->
|
||||
return false
|
||||
|
||||
@hasEmptySelectorAtStart: ->
|
||||
return true
|
|
@ -0,0 +1,135 @@
|
|||
# coffeelint: disable=camel_case_classes
|
||||
class App.UiElement.core_workflow_perform extends App.UiElement.ApplicationSelector
|
||||
@defaults: (attribute = {}, params = {}) ->
|
||||
defaults = []
|
||||
|
||||
groups =
|
||||
ticket:
|
||||
name: 'Ticket'
|
||||
model: 'Ticket'
|
||||
model_show: ['Ticket']
|
||||
group:
|
||||
name: 'Group'
|
||||
model: 'Group'
|
||||
model_show: ['Group']
|
||||
customer:
|
||||
name: 'Customer'
|
||||
model: 'User'
|
||||
model_show: ['User']
|
||||
organization:
|
||||
name: 'Organization'
|
||||
model: 'Organization'
|
||||
model_show: ['Organization']
|
||||
|
||||
showCustomModules = @coreWorkflowCustomModulesActive()
|
||||
if showCustomModules
|
||||
groups['custom'] =
|
||||
name: 'Custom'
|
||||
model_show: ['Ticket', 'User', 'Organization', 'Sla']
|
||||
|
||||
currentObject = params.object
|
||||
if attribute.workflow_object isnt undefined
|
||||
currentObject = attribute.workflow_object
|
||||
|
||||
if !_.isEmpty(currentObject)
|
||||
for key, data of groups
|
||||
continue if _.contains(data.model_show, currentObject)
|
||||
delete groups[key]
|
||||
|
||||
operatorsType =
|
||||
'boolean$': ['show', 'hide', 'remove', 'set_mandatory', 'set_optional', 'add_option', 'remove_option', 'set_fixed_to']
|
||||
'integer$': ['show', 'hide', 'remove', 'set_mandatory', 'set_optional']
|
||||
'^select$': ['show', 'hide', 'remove', 'set_mandatory', 'set_optional', 'add_option', 'remove_option', 'set_fixed_to', 'select', 'auto_select']
|
||||
'^tree_select$': ['show', 'hide', 'remove', 'set_mandatory', 'set_optional', 'select', 'auto_select']
|
||||
'^input$': ['show', 'hide', 'remove', 'set_mandatory', 'set_optional', 'fill_in', 'fill_in_empty']
|
||||
|
||||
operatorsName =
|
||||
'_id$': ['show', 'hide', 'set_mandatory', 'set_optional', 'add_option', 'remove_option', 'set_fixed_to', 'select', 'auto_select']
|
||||
'_ids$': ['show', 'hide', 'set_mandatory', 'set_optional']
|
||||
'organization_id$': ['show', 'hide', 'set_mandatory', 'set_optional', 'add_option', 'remove_option']
|
||||
'owner_id$': ['show', 'hide', 'set_mandatory', 'set_optional', 'add_option', 'remove_option', 'select', 'auto_select']
|
||||
|
||||
# merge config
|
||||
elements = {}
|
||||
|
||||
for groupKey, groupMeta of groups
|
||||
if groupKey is 'custom'
|
||||
continue if !showCustomModules
|
||||
|
||||
options = {}
|
||||
for module in App.CoreWorkflowCustomModule.all()
|
||||
options[module.name] = module.name
|
||||
elements['custom.module'] = { name: 'module', display: 'Module', tag: 'select', multiple: true, options: options, null: false, operator: ['execute'] }
|
||||
continue
|
||||
|
||||
for row in App[groupMeta.model].configure_attributes
|
||||
continue if !_.contains(['input', 'select', 'integer', 'boolean', 'tree_select'], row.tag)
|
||||
continue if groupKey is 'ticket' && _.contains(['number', 'organization_id', 'title'], row.name)
|
||||
|
||||
# ignore passwords and relations
|
||||
if row.type isnt 'password' && row.name.substr(row.name.length-4,4) isnt '_ids' && row.searchable isnt false
|
||||
config = _.clone(row)
|
||||
if config.tag is 'boolean'
|
||||
config.tag = 'select'
|
||||
if config.tag is 'select'
|
||||
config.multiple = true
|
||||
config.default = undefined
|
||||
if config.type is 'email' || config.type is 'tel'
|
||||
config.type = 'text'
|
||||
for operatorRegEx, operator of operatorsType
|
||||
myRegExp = new RegExp(operatorRegEx, 'i')
|
||||
if config.tag && config.tag.match(myRegExp)
|
||||
config.operator = operator
|
||||
elements["#{groupKey}.#{config.name}"] = config
|
||||
for operatorRegEx, operator of operatorsName
|
||||
myRegExp = new RegExp(operatorRegEx, 'i')
|
||||
if config.name && config.name.match(myRegExp)
|
||||
config.operator = operator
|
||||
elements["#{groupKey}.#{config.name}"] = config
|
||||
|
||||
[defaults, groups, elements]
|
||||
|
||||
@renderParamValue: (item, attribute, params, paramValue) ->
|
||||
[defaults, groups, elements] = @defaults(attribute, params)
|
||||
|
||||
for groupAndAttribute, meta of paramValue
|
||||
|
||||
if !_.isArray(meta.operator)
|
||||
meta.operator = [meta.operator]
|
||||
|
||||
for operator in meta.operator
|
||||
operatorMeta = {}
|
||||
operatorMeta['operator'] = operator
|
||||
operatorMeta[operator] = meta[operator]
|
||||
|
||||
# build and append
|
||||
row = @rowContainer(groups, elements, attribute)
|
||||
@rebuildAttributeSelectors(item, row, groupAndAttribute, elements, operatorMeta, attribute)
|
||||
item.filter('.js-filter').append(row)
|
||||
|
||||
@buildValueConfigValue: (elementFull, elementRow, groupAndAttribute, elements, meta, attribute) ->
|
||||
currentOperator = elementRow.find('.js-operator option:selected').attr('value')
|
||||
return _.clone(attribute.value[groupAndAttribute][currentOperator])
|
||||
|
||||
@buildValueName: (elementFull, elementRow, groupAndAttribute, elements, meta, attribute) ->
|
||||
currentOperator = elementRow.find('.js-operator option:selected').attr('value')
|
||||
return "#{attribute.name}::#{groupAndAttribute}::#{currentOperator}"
|
||||
|
||||
@buildValue: (elementFull, elementRow, groupAndAttribute, elements, meta, attribute) ->
|
||||
currentOperator = elementRow.find('.js-operator option:selected').attr('value')
|
||||
name = @buildValueName(elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
|
||||
|
||||
if !_.contains(['add_option', 'remove_option', 'set_fixed_to', 'select', 'execute', 'fill_in', 'fill_in_empty'], currentOperator)
|
||||
elementRow.find('.js-value').addClass('hide').html('<input type="hidden" name="' + name + '" value="true" />')
|
||||
return
|
||||
|
||||
super(elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
|
||||
|
||||
@HasPreCondition: ->
|
||||
return false
|
||||
|
||||
@hasEmptySelectorAtStart: ->
|
||||
return true
|
||||
|
||||
@hasDuplicateSelector: ->
|
||||
return true
|
|
@ -1,505 +1,2 @@
|
|||
# coffeelint: disable=camel_case_classes
|
||||
class App.UiElement.ticket_selector
|
||||
@defaults: (attribute = {}) ->
|
||||
defaults = ['ticket.state_id']
|
||||
|
||||
groups =
|
||||
ticket:
|
||||
name: 'Ticket'
|
||||
model: 'Ticket'
|
||||
article:
|
||||
name: 'Article'
|
||||
model: 'TicketArticle'
|
||||
customer:
|
||||
name: 'Customer'
|
||||
model: 'User'
|
||||
organization:
|
||||
name: 'Organization'
|
||||
model: 'Organization'
|
||||
|
||||
if attribute.executionTime
|
||||
groups.execution_time =
|
||||
name: 'Execution Time'
|
||||
|
||||
operators_type =
|
||||
'^datetime$': ['before (absolute)', 'after (absolute)', 'before (relative)', 'after (relative)', 'within next (relative)', 'within last (relative)', 'till (relative)', 'from (relative)']
|
||||
'^timestamp$': ['before (absolute)', 'after (absolute)', 'before (relative)', 'after (relative)', 'within next (relative)', 'within last (relative)', 'till (relative)', 'from (relative)']
|
||||
'^date$': ['before (absolute)', 'after (absolute)', 'before (relative)', 'after (relative)', 'within next (relative)', 'within last (relative)']
|
||||
'boolean$': ['is', 'is not']
|
||||
'integer$': ['is', 'is not']
|
||||
'^radio$': ['is', 'is not']
|
||||
'^select$': ['is', 'is not']
|
||||
'^tree_select$': ['is', 'is not']
|
||||
'^input$': ['contains', 'contains not']
|
||||
'^richtext$': ['contains', 'contains not']
|
||||
'^textarea$': ['contains', 'contains not']
|
||||
'^tag$': ['contains all', 'contains one', 'contains all not', 'contains one not']
|
||||
|
||||
if attribute.hasChanged
|
||||
operators_type =
|
||||
'^datetime$': ['before (absolute)', 'after (absolute)', 'before (relative)', 'after (relative)', 'within next (relative)', 'within last (relative)', 'till (relative)', 'from (relative)', 'has changed']
|
||||
'^timestamp$': ['before (absolute)', 'after (absolute)', 'before (relative)', 'after (relative)', 'within next (relative)', 'within last (relative)', 'till (relative)', 'from (relative)', 'has changed']
|
||||
'^date$': ['before (absolute)', 'after (absolute)', 'before (relative)', 'after (relative)', 'within next (relative)', 'within last (relative)', 'till (relative)', 'from (relative)', 'has changed']
|
||||
'boolean$': ['is', 'is not', 'has changed']
|
||||
'integer$': ['is', 'is not', 'has changed']
|
||||
'^radio$': ['is', 'is not', 'has changed']
|
||||
'^select$': ['is', 'is not', 'has changed']
|
||||
'^tree_select$': ['is', 'is not', 'has changed']
|
||||
'^input$': ['contains', 'contains not', 'has changed']
|
||||
'^richtext$': ['contains', 'contains not', 'has changed']
|
||||
'^textarea$': ['contains', 'contains not', 'has changed']
|
||||
'^tag$': ['contains all', 'contains one', 'contains all not', 'contains one not']
|
||||
|
||||
operators_name =
|
||||
'_id$': ['is', 'is not']
|
||||
'_ids$': ['is', 'is not']
|
||||
|
||||
if attribute.hasChanged
|
||||
operators_name =
|
||||
'_id$': ['is', 'is not', 'has changed']
|
||||
'_ids$': ['is', 'is not', 'has changed']
|
||||
|
||||
# merge config
|
||||
elements = {}
|
||||
|
||||
if attribute.article is false
|
||||
delete groups.article
|
||||
|
||||
if attribute.action
|
||||
elements['ticket.action'] =
|
||||
name: 'action'
|
||||
display: 'Action'
|
||||
tag: 'select'
|
||||
null: false
|
||||
translate: true
|
||||
options:
|
||||
create: 'created'
|
||||
update: 'updated'
|
||||
'update.merged_into': 'merged into'
|
||||
'update.received_merge': 'received merge'
|
||||
operator: ['is', 'is not']
|
||||
|
||||
for groupKey, groupMeta of groups
|
||||
if groupKey is 'execution_time'
|
||||
if attribute.executionTime
|
||||
elements['execution_time.calendar_id'] =
|
||||
name: 'calendar_id'
|
||||
display: 'Calendar'
|
||||
tag: 'select'
|
||||
relation: 'Calendar'
|
||||
null: false
|
||||
translate: false
|
||||
operator: ['is in working time', 'is not in working time']
|
||||
|
||||
else
|
||||
for row in App[groupMeta.model].configure_attributes
|
||||
# ignore passwords and relations
|
||||
if row.type isnt 'password' && row.name.substr(row.name.length-4,4) isnt '_ids' && row.searchable isnt false
|
||||
config = _.clone(row)
|
||||
if config.type is 'email' || config.type is 'tel'
|
||||
config.type = 'text'
|
||||
for operatorRegEx, operator of operators_type
|
||||
myRegExp = new RegExp(operatorRegEx, 'i')
|
||||
if config.tag && config.tag.match(myRegExp)
|
||||
config.operator = operator
|
||||
elements["#{groupKey}.#{config.name}"] = config
|
||||
for operatorRegEx, operator of operators_name
|
||||
myRegExp = new RegExp(operatorRegEx, 'i')
|
||||
if config.name && config.name.match(myRegExp)
|
||||
config.operator = operator
|
||||
elements["#{groupKey}.#{config.name}"] = config
|
||||
|
||||
if config.tag == 'select'
|
||||
config.multiple = true
|
||||
|
||||
if attribute.out_of_office
|
||||
elements['ticket.out_of_office_replacement_id'] =
|
||||
name: 'out_of_office_replacement_id'
|
||||
display: 'Out of office replacement'
|
||||
tag: 'autocompletion_ajax'
|
||||
relation: 'User'
|
||||
null: false
|
||||
translate: true
|
||||
operator: ['is', 'is not']
|
||||
|
||||
# Remove 'has changed' operator from attributes which don't support the operator.
|
||||
['ticket.created_at', 'ticket.updated_at'].forEach (element_name) ->
|
||||
elements[element_name]['operator'] = elements[element_name]['operator'].filter (item) -> item != 'has changed'
|
||||
|
||||
elements['ticket.mention_user_ids'] =
|
||||
name: 'mention_user_ids'
|
||||
display: 'Subscribe'
|
||||
tag: 'autocompletion_ajax'
|
||||
relation: 'User'
|
||||
null: false
|
||||
translate: true
|
||||
operator: ['is', 'is not']
|
||||
|
||||
[defaults, groups, elements]
|
||||
|
||||
@rowContainer: (groups, elements, attribute) ->
|
||||
row = $( App.view('generic/ticket_selector_row')(attribute: attribute) )
|
||||
selector = @buildAttributeSelector(groups, elements)
|
||||
row.find('.js-attributeSelector').prepend(selector)
|
||||
row
|
||||
|
||||
@render: (attribute, params = {}) ->
|
||||
|
||||
[defaults, groups, elements] = @defaults(attribute)
|
||||
|
||||
item = $( App.view('generic/ticket_selector')(attribute: attribute) )
|
||||
|
||||
# add filter
|
||||
item.delegate('.js-add', 'click', (e) =>
|
||||
element = $(e.target).closest('.js-filterElement')
|
||||
|
||||
# add first available attribute
|
||||
field = undefined
|
||||
for groupAndAttribute, _config of elements
|
||||
if !item.find(".js-attributeSelector [value=\"#{groupAndAttribute}\"]:selected").get(0)
|
||||
field = groupAndAttribute
|
||||
break
|
||||
return if !field
|
||||
row = @rowContainer(groups, elements, attribute)
|
||||
element.after(row)
|
||||
row.find('.js-attributeSelector select').trigger('change')
|
||||
@rebuildAttributeSelectors(item, row, field, elements, {}, attribute)
|
||||
|
||||
if attribute.preview isnt false
|
||||
@preview(item)
|
||||
)
|
||||
|
||||
# remove filter
|
||||
item.delegate('.js-remove', 'click', (e) =>
|
||||
return if $(e.currentTarget).hasClass('is-disabled')
|
||||
$(e.target).closest('.js-filterElement').remove()
|
||||
@updateAttributeSelectors(item)
|
||||
if attribute.preview isnt false
|
||||
@preview(item)
|
||||
)
|
||||
|
||||
# build initial params
|
||||
if !_.isEmpty(params[attribute.name])
|
||||
selectorExists = false
|
||||
for groupAndAttribute, meta of params[attribute.name]
|
||||
selectorExists = true
|
||||
|
||||
# build and append
|
||||
row = @rowContainer(groups, elements, attribute)
|
||||
@rebuildAttributeSelectors(item, row, groupAndAttribute, elements, meta, attribute)
|
||||
item.filter('.js-filter').append(row)
|
||||
|
||||
else
|
||||
for groupAndAttribute in defaults
|
||||
|
||||
# build and append
|
||||
row = @rowContainer(groups, elements, attribute)
|
||||
@rebuildAttributeSelectors(item, row, groupAndAttribute, elements, {}, attribute)
|
||||
item.filter('.js-filter').append(row)
|
||||
|
||||
# change attribute selector
|
||||
item.delegate('.js-attributeSelector select', 'change', (e) =>
|
||||
elementRow = $(e.target).closest('.js-filterElement')
|
||||
groupAndAttribute = elementRow.find('.js-attributeSelector option:selected').attr('value')
|
||||
return if !groupAndAttribute
|
||||
@rebuildAttributeSelectors(item, elementRow, groupAndAttribute, elements, {}, attribute)
|
||||
@updateAttributeSelectors(item)
|
||||
)
|
||||
|
||||
# change operator selector
|
||||
item.delegate('.js-operator select', 'change', (e) =>
|
||||
elementRow = $(e.target).closest('.js-filterElement')
|
||||
groupAndAttribute = elementRow.find('.js-attributeSelector option:selected').attr('value')
|
||||
return if !groupAndAttribute
|
||||
@buildOperator(item, elementRow, groupAndAttribute, elements, {}, attribute)
|
||||
)
|
||||
|
||||
# bind for preview
|
||||
if attribute.preview isnt false
|
||||
search = =>
|
||||
@preview(item)
|
||||
|
||||
triggerSearch = ->
|
||||
item.find('.js-previewCounterContainer').addClass('hide')
|
||||
item.find('.js-previewLoader').removeClass('hide')
|
||||
App.Delay.set(
|
||||
search,
|
||||
600,
|
||||
'preview',
|
||||
)
|
||||
|
||||
item.on('change', 'select', (e) ->
|
||||
triggerSearch()
|
||||
)
|
||||
item.on('change keyup', 'input', (e) ->
|
||||
triggerSearch()
|
||||
)
|
||||
|
||||
@disableRemoveForOneAttribute(item)
|
||||
item
|
||||
|
||||
@preview: (item) ->
|
||||
params = App.ControllerForm.params(item)
|
||||
App.Ajax.request(
|
||||
id: 'ticket_selector'
|
||||
type: 'POST'
|
||||
url: "#{App.Config.get('api_path')}/tickets/selector"
|
||||
data: JSON.stringify(params)
|
||||
processData: true,
|
||||
success: (data, status, xhr) =>
|
||||
App.Collection.loadAssets(data.assets)
|
||||
item.find('.js-previewCounterContainer').removeClass('hide')
|
||||
item.find('.js-previewLoader').addClass('hide')
|
||||
@ticketTable(data.ticket_ids, data.ticket_count, item)
|
||||
)
|
||||
|
||||
@ticketTable: (ticket_ids, ticket_count, item) ->
|
||||
item.find('.js-previewCounter').html(ticket_count)
|
||||
new App.TicketList(
|
||||
tableId: 'ticket-selector'
|
||||
el: item.find('.js-previewTable')
|
||||
ticket_ids: ticket_ids
|
||||
)
|
||||
|
||||
@buildAttributeSelector: (groups, elements) ->
|
||||
selection = $('<select class="form-control"></select>')
|
||||
for groupKey, groupMeta of groups
|
||||
displayName = App.i18n.translateInline(groupMeta.name)
|
||||
selection.closest('select').append("<optgroup label=\"#{displayName}\" class=\"js-#{groupKey}\"></optgroup>")
|
||||
optgroup = selection.find("optgroup.js-#{groupKey}")
|
||||
for elementKey, elementGroup of elements
|
||||
spacer = elementKey.split(/\./)
|
||||
if spacer[0] is groupKey
|
||||
attributeConfig = elements[elementKey]
|
||||
if attributeConfig.operator
|
||||
displayName = App.i18n.translateInline(attributeConfig.display)
|
||||
optgroup.append("<option value=\"#{elementKey}\">#{displayName}</option>")
|
||||
selection
|
||||
|
||||
# disable - if we only have one attribute
|
||||
@disableRemoveForOneAttribute: (elementFull) ->
|
||||
if elementFull.find('.js-attributeSelector select').length > 1
|
||||
elementFull.find('.js-remove').removeClass('is-disabled')
|
||||
else
|
||||
elementFull.find('.js-remove').addClass('is-disabled')
|
||||
|
||||
@updateAttributeSelectors: (elementFull) ->
|
||||
|
||||
# enable all
|
||||
elementFull.find('.js-attributeSelector select option').removeAttr('disabled')
|
||||
|
||||
# disable all used attributes
|
||||
elementFull.find('.js-attributeSelector select').each(->
|
||||
keyLocal = $(@).val()
|
||||
elementFull.find('.js-attributeSelector select option[value="' + keyLocal + '"]').attr('disabled', true)
|
||||
)
|
||||
|
||||
# disable - if we only have one attribute
|
||||
@disableRemoveForOneAttribute(elementFull)
|
||||
|
||||
|
||||
@rebuildAttributeSelectors: (elementFull, elementRow, groupAndAttribute, elements, meta, attribute) ->
|
||||
|
||||
# set attribute
|
||||
if groupAndAttribute
|
||||
elementRow.find('.js-attributeSelector select').val(groupAndAttribute)
|
||||
|
||||
@buildOperator(elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
|
||||
|
||||
@buildOperator: (elementFull, elementRow, groupAndAttribute, elements, meta, attribute) ->
|
||||
currentOperator = elementRow.find('.js-operator option:selected').attr('value')
|
||||
|
||||
name = "#{attribute.name}::#{groupAndAttribute}::operator"
|
||||
|
||||
if !meta.operator && currentOperator
|
||||
meta.operator = currentOperator
|
||||
|
||||
selection = $("<select class=\"form-control\" name=\"#{name}\"></select>")
|
||||
|
||||
attributeConfig = elements[groupAndAttribute]
|
||||
if attributeConfig.operator
|
||||
|
||||
# check if operator exists
|
||||
operatorExists = false
|
||||
for operator in attributeConfig.operator
|
||||
if meta.operator is operator
|
||||
operatorExists = true
|
||||
break
|
||||
|
||||
if !operatorExists
|
||||
for operator in attributeConfig.operator
|
||||
meta.operator = operator
|
||||
break
|
||||
|
||||
for operator in attributeConfig.operator
|
||||
operatorName = App.i18n.translateInline(operator)
|
||||
selected = ''
|
||||
if !groupAndAttribute.match(/^ticket/) && operator is 'has changed'
|
||||
# do nothing, only show "has changed" in ticket attributes
|
||||
else
|
||||
if meta.operator is operator
|
||||
selected = 'selected="selected"'
|
||||
selection.append("<option value=\"#{operator}\" #{selected}>#{operatorName}</option>")
|
||||
selection
|
||||
|
||||
elementRow.find('.js-operator select').replaceWith(selection)
|
||||
|
||||
@buildPreCondition(elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
|
||||
|
||||
@buildPreCondition: (elementFull, elementRow, groupAndAttribute, elements, meta, attributeConfig) ->
|
||||
currentOperator = elementRow.find('.js-operator option:selected').attr('value')
|
||||
currentPreCondition = elementRow.find('.js-preCondition option:selected').attr('value')
|
||||
|
||||
if !meta.pre_condition
|
||||
meta.pre_condition = currentPreCondition
|
||||
|
||||
toggleValue = =>
|
||||
preCondition = elementRow.find('.js-preCondition option:selected').attr('value')
|
||||
if preCondition isnt 'specific'
|
||||
elementRow.find('.js-value select').html('')
|
||||
elementRow.find('.js-value').addClass('hide')
|
||||
else
|
||||
elementRow.find('.js-value').removeClass('hide')
|
||||
@buildValue(elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
|
||||
|
||||
# force to use auto completion on user lookup
|
||||
attribute = _.clone(attributeConfig)
|
||||
|
||||
name = "#{attribute.name}::#{groupAndAttribute}::value"
|
||||
attributeSelected = elements[groupAndAttribute]
|
||||
|
||||
preCondition = false
|
||||
if attributeSelected.relation is 'User'
|
||||
preCondition = 'user'
|
||||
attribute.tag = 'user_autocompletion'
|
||||
if attributeSelected.relation is 'Organization'
|
||||
preCondition = 'org'
|
||||
attribute.tag = 'autocompletion_ajax'
|
||||
if !preCondition
|
||||
elementRow.find('.js-preCondition select').html('')
|
||||
elementRow.find('.js-preCondition').closest('.controls').addClass('hide')
|
||||
toggleValue()
|
||||
@buildValue(elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
|
||||
return
|
||||
|
||||
elementRow.find('.js-preCondition').closest('.controls').removeClass('hide')
|
||||
name = "#{attribute.name}::#{groupAndAttribute}::pre_condition"
|
||||
|
||||
selection = $("<select class=\"form-control\" name=\"#{name}\" ></select>")
|
||||
options = {}
|
||||
if preCondition is 'user'
|
||||
options =
|
||||
'current_user.id': App.i18n.translateInline('current user')
|
||||
'specific': App.i18n.translateInline('specific user')
|
||||
'not_set': App.i18n.translateInline('not set (not defined)')
|
||||
else if preCondition is 'org'
|
||||
options =
|
||||
'current_user.organization_id': App.i18n.translateInline('current user organization')
|
||||
'specific': App.i18n.translateInline('specific organization')
|
||||
'not_set': App.i18n.translateInline('not set (not defined)')
|
||||
|
||||
for key, value of options
|
||||
selected = ''
|
||||
if key is meta.pre_condition
|
||||
selected = 'selected="selected"'
|
||||
selection.append("<option value=\"#{key}\" #{selected}>#{App.i18n.translateInline(value)}</option>")
|
||||
elementRow.find('.js-preCondition').closest('.controls').removeClass('hide')
|
||||
elementRow.find('.js-preCondition select').replaceWith(selection)
|
||||
|
||||
elementRow.find('.js-preCondition select').bind('change', (e) ->
|
||||
toggleValue()
|
||||
)
|
||||
|
||||
@buildValue(elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
|
||||
toggleValue()
|
||||
|
||||
@buildValue: (elementFull, elementRow, groupAndAttribute, elements, meta, attribute) ->
|
||||
name = "#{attribute.name}::#{groupAndAttribute}::value"
|
||||
|
||||
# build new item
|
||||
attributeConfig = elements[groupAndAttribute]
|
||||
config = _.clone(attributeConfig)
|
||||
|
||||
if config.relation is 'User'
|
||||
config.tag = 'user_autocompletion'
|
||||
if config.relation is 'Organization'
|
||||
config.tag = 'autocompletion_ajax'
|
||||
|
||||
# render ui element
|
||||
item = ''
|
||||
if config && App.UiElement[config.tag]
|
||||
config['name'] = name
|
||||
if attribute.value && attribute.value[groupAndAttribute]
|
||||
config['value'] = _.clone(attribute.value[groupAndAttribute]['value'])
|
||||
if 'multiple' of config
|
||||
config.multiple = true
|
||||
config.nulloption = false
|
||||
if config.relation is 'User'
|
||||
config.multiple = false
|
||||
config.nulloption = false
|
||||
config.guess = false
|
||||
if config.relation is 'Organization'
|
||||
config.multiple = false
|
||||
config.nulloption = false
|
||||
config.guess = false
|
||||
if config.tag is 'checkbox'
|
||||
config.tag = 'select'
|
||||
if config.tag is 'datetime'
|
||||
config.validationContainer = 'self'
|
||||
tagSearch = "#{config.tag}_search"
|
||||
if App.UiElement[tagSearch]
|
||||
item = App.UiElement[tagSearch].render(config, {})
|
||||
else
|
||||
item = App.UiElement[config.tag].render(config, {})
|
||||
if meta.operator is 'before (relative)' || meta.operator is 'within next (relative)' || meta.operator is 'within last (relative)' || meta.operator is 'after (relative)' || meta.operator is 'from (relative)' || meta.operator is 'till (relative)'
|
||||
config['name'] = "#{attribute.name}::#{groupAndAttribute}"
|
||||
if attribute.value && attribute.value[groupAndAttribute]
|
||||
config['value'] = _.clone(attribute.value[groupAndAttribute])
|
||||
item = App.UiElement['time_range'].render(config, {})
|
||||
|
||||
elementRow.find('.js-value').removeClass('hide').html(item)
|
||||
if meta.operator is 'has changed'
|
||||
elementRow.find('.js-value').addClass('hide')
|
||||
elementRow.find('.js-preCondition').closest('.controls').addClass('hide')
|
||||
else
|
||||
elementRow.find('.js-value').removeClass('hide')
|
||||
|
||||
@humanText: (condition) ->
|
||||
none = App.i18n.translateContent('No filter.')
|
||||
return [none] if _.isEmpty(condition)
|
||||
[defaults, groups, elements] = @defaults()
|
||||
rules = []
|
||||
for attribute, meta of condition
|
||||
|
||||
objectAttribute = attribute.split(/\./)
|
||||
|
||||
# get stored params
|
||||
if meta && objectAttribute[1]
|
||||
operator = meta.operator
|
||||
value = meta.value
|
||||
model = toCamelCase(objectAttribute[0])
|
||||
config = elements[attribute]
|
||||
|
||||
valueHuman = []
|
||||
if _.isArray(value)
|
||||
for data in value
|
||||
r = @humanTextLookup(config, data)
|
||||
valueHuman.push r
|
||||
else
|
||||
valueHuman.push @humanTextLookup(config, value)
|
||||
|
||||
if valueHuman.join
|
||||
valueHuman = valueHuman.join(', ')
|
||||
rules.push "#{App.i18n.translateContent('Where')} <b>#{App.i18n.translateContent(model)} -> #{App.i18n.translateContent(config.display)}</b> #{App.i18n.translateContent(operator)} <b>#{valueHuman}</b>."
|
||||
|
||||
return [none] if _.isEmpty(rules)
|
||||
rules
|
||||
|
||||
@humanTextLookup: (config, value) ->
|
||||
return value if !App[config.relation]
|
||||
return value if !App[config.relation].exists(value)
|
||||
data = App[config.relation].fullLocal(value)
|
||||
return value if !data
|
||||
if data.displayName
|
||||
return App.i18n.translateContent(data.displayName())
|
||||
valueHuman.push App.i18n.translateContent(data.name)
|
||||
class App.UiElement.ticket_selector extends App.UiElement.ApplicationSelector
|
||||
|
|
|
@ -8,6 +8,35 @@ class App.UiElement.tree_select extends App.UiElement.ApplicationUiElement
|
|||
if child.children
|
||||
@optionsSelect(child.children, value)
|
||||
|
||||
@filterTreeOptions: (values, valueDepth, options, nullExists) ->
|
||||
newOptions = []
|
||||
nullFound = false
|
||||
for option, index in options
|
||||
enabled = false
|
||||
for value in values
|
||||
valueArray = value.split('::')
|
||||
optionArray = option['value'].split('::')
|
||||
continue if valueArray[valueDepth] isnt optionArray[valueDepth]
|
||||
enabled = true
|
||||
break
|
||||
|
||||
if nullExists && !option.value && !nullFound
|
||||
nullFound = true
|
||||
enabled = true
|
||||
|
||||
if !enabled
|
||||
continue
|
||||
|
||||
if option['children'] && option['children'].length
|
||||
option['children'] = @filterTreeOptions(values, valueDepth + 1, option['children'], nullExists)
|
||||
|
||||
newOptions.push(option)
|
||||
|
||||
return newOptions
|
||||
|
||||
@filterOptionArray: (attribute) ->
|
||||
attribute.options = @filterTreeOptions(attribute.filter, 0, attribute.options, attribute.null)
|
||||
|
||||
@render: (attribute, params) ->
|
||||
|
||||
# set multiple option
|
||||
|
|
|
@ -52,7 +52,8 @@ class App.TicketCreate extends App.Controller
|
|||
App.Collection.loadAssets(data.assets)
|
||||
@formMeta = data.form_meta
|
||||
@buildScreen(params)
|
||||
@bindId = App.TicketCreateCollection.one(load)
|
||||
@bindId = App.TicketCreateCollection.bind(load, false)
|
||||
App.TicketCreateCollection.fetch()
|
||||
|
||||
# rerender view, e. g. on langauge change
|
||||
@controllerBind('ui:rerender', =>
|
||||
|
@ -123,6 +124,7 @@ class App.TicketCreate extends App.Controller
|
|||
@$('[name="formSenderType"]').val(type)
|
||||
|
||||
# force changing signature
|
||||
# skip on initialization because it will trigger core workflow
|
||||
@$('[name="group_id"]').trigger('change')
|
||||
|
||||
# add observer to change options
|
||||
|
@ -319,39 +321,11 @@ class App.TicketCreate extends App.Controller
|
|||
|
||||
handlers = @Config.get('TicketCreateFormHandler')
|
||||
|
||||
new App.ControllerForm(
|
||||
el: @$('.ticket-form-top')
|
||||
form_id: @formId
|
||||
model: App.Ticket
|
||||
screen: 'create_top'
|
||||
events:
|
||||
'change [name=customer_id]': @localUserInfo
|
||||
handlersConfig: handlers
|
||||
filter: @formMeta.filter
|
||||
formMeta: @formMeta
|
||||
autofocus: true
|
||||
params: params
|
||||
taskKey: @taskKey
|
||||
)
|
||||
|
||||
new App.ControllerForm(
|
||||
el: @$('.article-form-top')
|
||||
form_id: @formId
|
||||
model: App.TicketArticle
|
||||
screen: 'create_top'
|
||||
events:
|
||||
'fileUploadStart .richtext': => @submitDisable()
|
||||
'fileUploadStop .richtext': => @submitEnable()
|
||||
params: params
|
||||
taskKey: @taskKey
|
||||
)
|
||||
new App.ControllerForm(
|
||||
@controllerFormCreateMiddle = new App.ControllerForm(
|
||||
el: @$('.ticket-form-middle')
|
||||
form_id: @formId
|
||||
model: App.Ticket
|
||||
screen: 'create_middle'
|
||||
events:
|
||||
'change [name=customer_id]': @localUserInfo
|
||||
handlersConfig: handlers
|
||||
filter: @formMeta.filter
|
||||
formMeta: @formMeta
|
||||
|
@ -360,14 +334,52 @@ class App.TicketCreate extends App.Controller
|
|||
taskKey: @taskKey
|
||||
rejectNonExistentValues: true
|
||||
)
|
||||
new App.ControllerForm(
|
||||
|
||||
# tunnel events to make sure core workflow does know
|
||||
# about every change of all attributes (like subject)
|
||||
tunnelController = @controllerFormCreateMiddle
|
||||
class TicketCreateFormHandlerControllerFormCreateMiddle
|
||||
@run: (params, attribute, attributes, classname, form, ui) ->
|
||||
return if !ui.lastChangedAttribute
|
||||
tunnelController.lastChangedAttribute = ui.lastChangedAttribute
|
||||
params = App.ControllerForm.params(tunnelController.form)
|
||||
App.FormHandlerCoreWorkflow.run(params, tunnelController.attributes[0], tunnelController.attributes, tunnelController.idPrefix, tunnelController.form, tunnelController)
|
||||
|
||||
handlersTunnel = _.clone(handlers)
|
||||
handlersTunnel['000-TicketCreateFormHandlerControllerFormCreateMiddle'] = TicketCreateFormHandlerControllerFormCreateMiddle
|
||||
|
||||
@controllerFormCreateTop = new App.ControllerForm(
|
||||
el: @$('.ticket-form-top')
|
||||
form_id: @formId
|
||||
model: App.Ticket
|
||||
screen: 'create_top'
|
||||
events:
|
||||
'change [name=customer_id]': @localUserInfo
|
||||
handlersConfig: handlersTunnel
|
||||
filter: @formMeta.filter
|
||||
formMeta: @formMeta
|
||||
autofocus: true
|
||||
params: params
|
||||
taskKey: @taskKey
|
||||
)
|
||||
@controllerFormCreateTopArticle = new App.ControllerForm(
|
||||
el: @$('.article-form-top')
|
||||
form_id: @formId
|
||||
model: App.TicketArticle
|
||||
screen: 'create_top'
|
||||
events:
|
||||
'fileUploadStart .richtext': => @submitDisable()
|
||||
'fileUploadStop .richtext': => @submitEnable()
|
||||
handlersConfig: handlersTunnel
|
||||
params: params
|
||||
taskKey: @taskKey
|
||||
)
|
||||
@controllerFormCreateBottom = new App.ControllerForm(
|
||||
el: @$('.ticket-form-bottom')
|
||||
form_id: @formId
|
||||
model: App.Ticket
|
||||
screen: 'create_bottom'
|
||||
events:
|
||||
'change [name=customer_id]': @localUserInfo
|
||||
handlersConfig: handlers
|
||||
handlersConfig: handlersTunnel
|
||||
filter: @formMeta.filter
|
||||
formMeta: @formMeta
|
||||
params: params
|
||||
|
@ -513,19 +525,23 @@ class App.TicketCreate extends App.Controller
|
|||
ticket.load(params)
|
||||
|
||||
ticketErrorsTop = ticket.validate(
|
||||
screen: 'create_top'
|
||||
controllerForm: @controllerFormCreateTop
|
||||
target: e.target
|
||||
)
|
||||
ticketErrorsMiddle = ticket.validate(
|
||||
screen: 'create_middle'
|
||||
controllerForm: @controllerFormCreateMiddle
|
||||
target: e.target
|
||||
)
|
||||
ticketErrorsBottom = ticket.validate(
|
||||
screen: 'create_bottom'
|
||||
controllerForm: @controllerFormCreateBottom
|
||||
target: e.target
|
||||
)
|
||||
|
||||
article = new App.TicketArticle
|
||||
article.load(params['article'])
|
||||
articleErrors = article.validate(
|
||||
screen: 'create_top'
|
||||
controllerForm: @controllerFormCreateTopArticle
|
||||
target: e.target
|
||||
)
|
||||
|
||||
# collect whole validation result
|
||||
|
|
57
app/assets/javascripts/app/controllers/core_workflow.coffee
Normal file
57
app/assets/javascripts/app/controllers/core_workflow.coffee
Normal file
|
@ -0,0 +1,57 @@
|
|||
class CoreWorkflow extends App.ControllerSubContent
|
||||
requiredPermission: 'admin.core_workflow'
|
||||
header: 'Core Workflow'
|
||||
constructor: ->
|
||||
super
|
||||
|
||||
@setAttributes()
|
||||
|
||||
@genericController = new App.ControllerGenericIndex(
|
||||
el: @el
|
||||
id: @id
|
||||
genericObject: 'CoreWorkflow'
|
||||
defaultSortBy: 'name'
|
||||
pageData:
|
||||
home: 'core_workflow'
|
||||
object: 'Workflow'
|
||||
objects: 'Workflows'
|
||||
pagerAjax: true
|
||||
pagerBaseUrl: '#manage/core_workflow/'
|
||||
pagerSelected: ( @page || 1 )
|
||||
pagerPerPage: 150
|
||||
navupdate: '#core_workflow'
|
||||
notes: [
|
||||
'Core Workflows are actions or constraints on selections in forms. Depending on an action, it is possible to hide or restrict fields or to change the obligation to fill them in.'
|
||||
]
|
||||
buttons: [
|
||||
{ name: 'New Workflow', 'data-type': 'new', class: 'btn--success' }
|
||||
]
|
||||
container: @el.closest('.content')
|
||||
veryLarge: true
|
||||
handlers: [
|
||||
App.FormHandlerCoreWorkflow.run
|
||||
App.FormHandlerAdminCoreWorkflow.run
|
||||
]
|
||||
)
|
||||
|
||||
setAttributes: ->
|
||||
for field in App.CoreWorkflow.configure_attributes
|
||||
if field.name is 'object'
|
||||
field.options = {}
|
||||
for value in App.FormHandlerCoreWorkflow.getObjects()
|
||||
field.options[value] = value
|
||||
else if field.name is 'preferences::screen'
|
||||
field.options = {}
|
||||
for value in App.FormHandlerCoreWorkflow.getScreens()
|
||||
field.options[value] = @screen2displayName(value)
|
||||
|
||||
screen2displayName: (screen) ->
|
||||
mapping = {
|
||||
create: 'Creation mask',
|
||||
create_middle: 'Creation mask',
|
||||
edit: 'Edit mask',
|
||||
overview_bulk: 'Overview bulk mask',
|
||||
}
|
||||
return mapping[screen] || screen
|
||||
|
||||
App.Config.set('CoreWorkflowObject', { prio: 1750, parent: '#system', name: 'Core Workflow', target: '#system/core_workflow', controller: CoreWorkflow, permission: ['admin.core_workflow'] }, 'NavBarAdmin')
|
|
@ -18,7 +18,8 @@ class CustomerTicketCreate extends App.ControllerAppContent
|
|||
App.Collection.loadAssets(data.assets)
|
||||
@formMeta = data.form_meta
|
||||
@render()
|
||||
@bindId = App.TicketCreateCollection.one(load)
|
||||
@bindId = App.TicketCreateCollection.bind(load, false)
|
||||
App.TicketCreateCollection.fetch()
|
||||
|
||||
render: (template = {}) ->
|
||||
if !@Config.get('customer_ticket_create')
|
||||
|
@ -43,32 +44,7 @@ class CustomerTicketCreate extends App.ControllerAppContent
|
|||
form_id: @form_id
|
||||
)
|
||||
|
||||
new App.ControllerForm(
|
||||
el: @el.find('.ticket-form-top')
|
||||
form_id: @form_id
|
||||
model: App.Ticket
|
||||
screen: 'create_top'
|
||||
handlersConfig: handlers
|
||||
filter: @formMeta.filter
|
||||
formMeta: @formMeta
|
||||
autofocus: true
|
||||
params: defaults
|
||||
)
|
||||
|
||||
new App.ControllerForm(
|
||||
el: @el.find('.article-form-top')
|
||||
form_id: @form_id
|
||||
model: App.TicketArticle
|
||||
screen: 'create_top'
|
||||
events:
|
||||
'fileUploadStart .richtext': => @submitDisable()
|
||||
'fileUploadStop .richtext': => @submitEnable()
|
||||
filter: @formMeta.filter
|
||||
formMeta: @formMeta
|
||||
params: defaults
|
||||
handlersConfig: handlers
|
||||
)
|
||||
new App.ControllerForm(
|
||||
@controllerFormCreateMiddle = new App.ControllerForm(
|
||||
el: @el.find('.ticket-form-middle')
|
||||
form_id: @form_id
|
||||
model: App.Ticket
|
||||
|
@ -80,13 +56,51 @@ class CustomerTicketCreate extends App.ControllerAppContent
|
|||
handlersConfig: handlers
|
||||
rejectNonExistentValues: true
|
||||
)
|
||||
|
||||
# tunnel events to make sure core workflow does know
|
||||
# about every change of all attributes (like subject)
|
||||
tunnelController = @controllerFormCreateMiddle
|
||||
class TicketCreateFormHandlerControllerFormCreateMiddle
|
||||
@run: (params, attribute, attributes, classname, form, ui) ->
|
||||
return if !ui.lastChangedAttribute
|
||||
tunnelController.lastChangedAttribute = ui.lastChangedAttribute
|
||||
params = App.ControllerForm.params(tunnelController.form)
|
||||
App.FormHandlerCoreWorkflow.run(params, tunnelController.attributes[0], tunnelController.attributes, tunnelController.idPrefix, tunnelController.form, tunnelController)
|
||||
|
||||
handlersTunnel = _.clone(handlers)
|
||||
handlersTunnel['000-TicketCreateFormHandlerControllerFormCreateMiddle'] = TicketCreateFormHandlerControllerFormCreateMiddle
|
||||
|
||||
@controllerFormCreateTop = new App.ControllerForm(
|
||||
el: @el.find('.ticket-form-top')
|
||||
form_id: @form_id
|
||||
model: App.Ticket
|
||||
screen: 'create_top'
|
||||
handlersConfig: handlersTunnel
|
||||
filter: @formMeta.filter
|
||||
formMeta: @formMeta
|
||||
autofocus: true
|
||||
params: defaults
|
||||
)
|
||||
@controllerFormCreateTopArticle = new App.ControllerForm(
|
||||
el: @el.find('.article-form-top')
|
||||
form_id: @form_id
|
||||
model: App.TicketArticle
|
||||
screen: 'create_top'
|
||||
events:
|
||||
'fileUploadStart .richtext': => @submitDisable()
|
||||
'fileUploadStop .richtext': => @submitEnable()
|
||||
filter: @formMeta.filter
|
||||
formMeta: @formMeta
|
||||
params: defaults
|
||||
handlersConfig: handlersTunnel
|
||||
)
|
||||
if !_.isEmpty(App.Ticket.attributesGet('create_bottom', false, true))
|
||||
new App.ControllerForm(
|
||||
@controllerFormCreateBottom = new App.ControllerForm(
|
||||
el: @el.find('.ticket-form-bottom')
|
||||
form_id: @form_id
|
||||
model: App.Ticket
|
||||
screen: 'create_bottom'
|
||||
handlersConfig: handlers
|
||||
handlersConfig: handlersTunnel
|
||||
filter: @formMeta.filter
|
||||
formMeta: @formMeta
|
||||
params: defaults
|
||||
|
@ -151,15 +165,18 @@ class CustomerTicketCreate extends App.ControllerAppContent
|
|||
|
||||
# validate form
|
||||
ticketErrorsTop = ticket.validate(
|
||||
screen: 'create_top'
|
||||
controllerForm: @controllerFormCreateTop
|
||||
target: e.target
|
||||
)
|
||||
ticketErrorsMiddle = ticket.validate(
|
||||
screen: 'create_middle'
|
||||
controllerForm: @controllerFormCreateMiddle
|
||||
target: e.target
|
||||
)
|
||||
article = new App.TicketArticle
|
||||
article.load(params['article'])
|
||||
articleErrors = article.validate(
|
||||
screen: 'create_top'
|
||||
controllerForm: @controllerFormCreateTop
|
||||
target: e.target
|
||||
)
|
||||
|
||||
# collect whole validation
|
||||
|
|
|
@ -67,7 +67,7 @@ class GettingStartedAdmin extends App.ControllerWizardFullScreen
|
|||
user.load(@params)
|
||||
|
||||
errors = user.validate(
|
||||
screen: 'signup'
|
||||
controllerForm: @form
|
||||
)
|
||||
if errors
|
||||
@log 'error new', errors
|
||||
|
|
|
@ -61,7 +61,7 @@ class GettingStartedAgent extends App.ControllerWizardFullScreen
|
|||
user.load(@params)
|
||||
|
||||
errors = user.validate(
|
||||
screen: 'invite_agent'
|
||||
controllerForm: @form
|
||||
)
|
||||
if errors
|
||||
@log 'error new', errors
|
||||
|
|
|
@ -273,7 +273,9 @@ class Edit extends App.ControllerGenericEdit
|
|||
@item.load(params)
|
||||
|
||||
# validate
|
||||
errors = @item.validate()
|
||||
errors = @item.validate(
|
||||
controllerForm: @controller
|
||||
)
|
||||
if errors
|
||||
@log 'error', errors
|
||||
@formValidate(form: e.target, errors: errors)
|
||||
|
|
|
@ -50,7 +50,7 @@ class Signup extends App.ControllerFullPage
|
|||
user.load(@params)
|
||||
|
||||
errors = user.validate(
|
||||
screen: 'signup'
|
||||
controllerForm: @form
|
||||
)
|
||||
|
||||
if errors
|
||||
|
|
|
@ -8,19 +8,21 @@ class App.TicketCustomer extends App.ControllerModal
|
|||
configure_attributes = [
|
||||
{ name: 'customer_id', display: 'Customer', tag: 'user_autocompletion', null: false, placeholder: 'Enter Person or Organization/Company', minLengt: 2, disableCreateObject: false },
|
||||
]
|
||||
controller = new App.ControllerForm(
|
||||
@controller = new App.ControllerForm(
|
||||
model:
|
||||
configure_attributes: configure_attributes,
|
||||
autofocus: true
|
||||
)
|
||||
controller.form
|
||||
@controller.form
|
||||
|
||||
onSubmit: (e) =>
|
||||
params = @formParam(e.target)
|
||||
|
||||
ticket = App.Ticket.find(@ticket_id)
|
||||
ticket.customer_id = params['customer_id']
|
||||
errors = ticket.validate()
|
||||
errors = ticket.validate(
|
||||
controllerForm: @controller
|
||||
)
|
||||
|
||||
if !_.isEmpty(errors)
|
||||
@log 'error', errors
|
||||
|
|
|
@ -39,7 +39,7 @@ class App.TicketOverview extends App.Controller
|
|||
load = (data) =>
|
||||
App.Collection.loadAssets(data.assets)
|
||||
@formMeta = data.form_meta
|
||||
@bindId = App.TicketCreateCollection.bind(load)
|
||||
@bindId = App.TicketOverviewCollection.bind(load)
|
||||
|
||||
startDragItem: (event) =>
|
||||
return if !@batchSupport
|
||||
|
@ -206,13 +206,22 @@ class App.TicketOverview extends App.Controller
|
|||
article: article
|
||||
)
|
||||
ticket.article = article
|
||||
ticket.save(
|
||||
ticket.ajax().update(
|
||||
ticket.attributes()
|
||||
# this option will prevent callbacks and invalid data states in case of an error
|
||||
failResponseNoTrigger: true
|
||||
done: (r) =>
|
||||
@batchCountIndex++
|
||||
|
||||
# refresh view after all tickets are proceeded
|
||||
if @batchCountIndex == @batchCount
|
||||
App.Event.trigger('overview:fetch')
|
||||
fail: (record, settings, details) ->
|
||||
console.log('record, settings, details', record, settings, details)
|
||||
App.Event.trigger('notify', {
|
||||
type: 'error'
|
||||
msg: App.i18n.translateContent('Bulk action stopped %s!', error)
|
||||
})
|
||||
)
|
||||
return
|
||||
|
||||
|
@ -225,13 +234,21 @@ class App.TicketOverview extends App.Controller
|
|||
ticket.owner_id = id
|
||||
if !_.isEmpty(groupId)
|
||||
ticket.group_id = groupId
|
||||
ticket.save(
|
||||
ticket.ajax().update(
|
||||
ticket.attributes()
|
||||
# this option will prevent callbacks and invalid data states in case of an error
|
||||
failResponseNoTrigger: true
|
||||
done: (r) =>
|
||||
@batchCountIndex++
|
||||
|
||||
# refresh view after all tickets are proceeded
|
||||
if @batchCountIndex == @batchCount
|
||||
App.Event.trigger('overview:fetch')
|
||||
fail: (record, settings, details) ->
|
||||
App.Event.trigger('notify', {
|
||||
type: 'error'
|
||||
msg: App.i18n.translateContent('Bulk action stopped %s!', settings.error)
|
||||
})
|
||||
)
|
||||
return
|
||||
|
||||
|
@ -242,13 +259,21 @@ class App.TicketOverview extends App.Controller
|
|||
#console.log "perform action #{action} with id #{id} on ", $(item).val()
|
||||
ticket = App.Ticket.find($(item).val())
|
||||
ticket.group_id = id
|
||||
ticket.save(
|
||||
ticket.ajax().update(
|
||||
ticket.attributes()
|
||||
# this option will prevent callbacks and invalid data states in case of an error
|
||||
failResponseNoTrigger: true
|
||||
done: (r) =>
|
||||
@batchCountIndex++
|
||||
|
||||
# refresh view after all tickets are proceeded
|
||||
if @batchCountIndex == @batchCount
|
||||
App.Event.trigger('overview:fetch')
|
||||
fail: (record, settings, details) ->
|
||||
App.Event.trigger('notify', {
|
||||
type: 'error'
|
||||
msg: App.i18n.translateContent('Bulk action stopped %s!', error)
|
||||
})
|
||||
)
|
||||
return
|
||||
|
||||
|
@ -673,6 +698,8 @@ class App.TicketOverview extends App.Controller
|
|||
@contentController.show()
|
||||
return
|
||||
|
||||
App.TicketOverviewCollection.fetch()
|
||||
|
||||
# remember last view
|
||||
@viewLast = @view
|
||||
|
||||
|
@ -707,7 +734,7 @@ class App.TicketOverview extends App.Controller
|
|||
release: =>
|
||||
@keyboardOff()
|
||||
super
|
||||
App.TicketCreateCollection.unbindById(@bindId)
|
||||
App.TicketOverviewCollection.unbindById(@bindId)
|
||||
|
||||
keyboardOn: =>
|
||||
$(window).off 'keydown.overview_navigation'
|
||||
|
@ -1134,6 +1161,8 @@ class Table extends App.Controller
|
|||
|
||||
@lastChecked = e.currentTarget
|
||||
|
||||
@updateTicketIdsBulkForm()
|
||||
|
||||
callbackIconHeader = (headers) ->
|
||||
attribute =
|
||||
name: 'icon'
|
||||
|
@ -1273,6 +1302,11 @@ class Table extends App.Controller
|
|||
bulkAll.prop('indeterminate', true)
|
||||
)
|
||||
|
||||
updateTicketIdsBulkForm: =>
|
||||
items = $('.content.active .table-overview .table').find('[name="bulk"]:checked')
|
||||
ticket_ids = _.map(items, (el) -> $(el).val() )
|
||||
@bulkForm.el.find('input[name=ticket_ids]').val(ticket_ids.join(',')).trigger('change')
|
||||
|
||||
renderCustomerNotTicketExistIfNeeded: (ticketListShow) =>
|
||||
user = App.User.current()
|
||||
@stopListening user, 'refresh'
|
||||
|
@ -1319,7 +1353,6 @@ class Table extends App.Controller
|
|||
onCloseCallback: @keyboardOn
|
||||
)
|
||||
|
||||
|
||||
class App.OverviewSettings extends App.ControllerModal
|
||||
buttonClose: true
|
||||
buttonCancel: true
|
||||
|
|
|
@ -856,7 +856,8 @@ class App.TicketZoom extends App.Controller
|
|||
|
||||
# validate ticket by model
|
||||
errors = ticket.validate(
|
||||
screen: 'edit'
|
||||
controllerForm: @sidebarWidget?.get('100-TicketEdit')?.edit?.controllerFormSidebarTicket
|
||||
target: e.target
|
||||
)
|
||||
if errors
|
||||
@log 'error', 'update', errors
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
class App.FormHandlerAdminCoreWorkflow
|
||||
@run: (params, attribute, attributes, classname, form, ui) ->
|
||||
return if attribute.name isnt 'object'
|
||||
|
||||
return if ui.FormHandlerAdminCoreWorkflowDone
|
||||
ui.FormHandlerAdminCoreWorkflowDone = true
|
||||
|
||||
$(form).find('select[name=object]').off('change.core_workflow_conditions').on('change.change.core_workflow_conditions', (e) ->
|
||||
for attribute in attributes
|
||||
continue if attribute.name isnt 'condition_saved' && attribute.name isnt 'condition_selected' && attribute.name isnt 'perform'
|
||||
|
||||
attribute.workflow_object = $(e.target).val()
|
||||
newElement = ui.formGenItem(attribute, classname, form)
|
||||
form.find('div.form-group[data-attribute-name="' + attribute.name + '"]').replaceWith(newElement)
|
||||
)
|
|
@ -0,0 +1,320 @@
|
|||
class App.FormHandlerCoreWorkflow
|
||||
|
||||
# contains the current form params state to prevent mass requests
|
||||
coreWorkflowParams = {}
|
||||
|
||||
# contains the running requests of each form
|
||||
coreWorkflowRequests = {}
|
||||
|
||||
# contains the restriction values for each attribute of each form
|
||||
coreWorkflowRestrictions = {}
|
||||
|
||||
# defines the objects and screen for which Core Workflow is active
|
||||
coreWorkflowScreens = {
|
||||
Ticket: ['create_middle', 'edit', 'overview_bulk']
|
||||
User: ['create', 'edit']
|
||||
Organization: ['create', 'edit']
|
||||
Sla: ['create', 'edit']
|
||||
CoreWorkflow: ['create', 'edit']
|
||||
Group: ['create', 'edit']
|
||||
}
|
||||
|
||||
# returns the objects for which Core Workflow is active
|
||||
@getObjects: ->
|
||||
return Object.keys(coreWorkflowScreens)
|
||||
|
||||
# returns the screens for which Core Workflow is active
|
||||
@getScreens: ->
|
||||
result = []
|
||||
for object, screens of coreWorkflowScreens
|
||||
for screen in screens
|
||||
continue if screen in result
|
||||
result.push(screen)
|
||||
return result
|
||||
|
||||
# returns active Core Workflow requests. it is used to stabilize tests
|
||||
@getRequests: ->
|
||||
return coreWorkflowRequests
|
||||
|
||||
# Based on the model validation result the controller form
|
||||
# will delay the submit if a request of the Core Workflow is running
|
||||
@delaySubmit: (controllerForm, target) ->
|
||||
for key, value of coreWorkflowRequests
|
||||
if controllerForm.idPrefix is value.ui.idPrefix
|
||||
coreWorkflowRequests[key].triggerSubmit = target
|
||||
return true
|
||||
App.FormHandlerCoreWorkflow.triggerSubmit(target)
|
||||
|
||||
# the saved submit target will be executed after the request
|
||||
@triggerSubmit: (target) ->
|
||||
if $(target).get(0).tagName == 'FORM'
|
||||
target = $(target).find('button[type=submit]').first()
|
||||
|
||||
$(target).click()
|
||||
|
||||
# checks if the controller has a running Core Workflow request
|
||||
@requestsRunning: (controllerForm) ->
|
||||
for key, value of coreWorkflowRequests
|
||||
if controllerForm.idPrefix is value.ui.idPrefix
|
||||
return true
|
||||
return false
|
||||
|
||||
# checks if the Core Workflow should get activated for the screen
|
||||
@screenValid: (ui) ->
|
||||
return false if !ui.model
|
||||
return false if !ui.model.className
|
||||
return false if !ui.screen
|
||||
return false if coreWorkflowScreens[ui.model.className] is undefined
|
||||
return false if !_.contains(coreWorkflowScreens[ui.model.className], ui.screen)
|
||||
return true
|
||||
|
||||
# checks if the ajax or websocket endpoint should be used
|
||||
@useWebSockets: ->
|
||||
return !App.Config.get('core_workflow_ajax_mode')
|
||||
|
||||
# restricts the dropdown and tree select values of a form
|
||||
@restrictValues: (classname, form, ui, attributes, params, data) ->
|
||||
return if _.isEmpty(data.restrict_values)
|
||||
|
||||
for field, values of data.restrict_values
|
||||
for attribute in attributes
|
||||
continue if attribute.name isnt field
|
||||
|
||||
item = $.extend(true, {}, attribute)
|
||||
el = App.ControllerForm.findFieldByName(field, form)
|
||||
shown = App.ControllerForm.fieldIsShown(el)
|
||||
mandatory = App.ControllerForm.fieldIsMandatory(el)
|
||||
|
||||
# get deep value if needed for store attributes
|
||||
paramValue = params[item.name]
|
||||
if data.select[item.name]
|
||||
paramValue = data.select[item.name]
|
||||
coreWorkflowParams[classname][item.name] = paramValue
|
||||
delete coreWorkflowRestrictions[classname]
|
||||
|
||||
parts = attribute.name.split '::'
|
||||
if parts.length > 1
|
||||
deepValue = parts.reduce((memo, elem) ->
|
||||
memo?[elem]
|
||||
, params)
|
||||
|
||||
if deepValue isnt undefined
|
||||
paramValue = deepValue
|
||||
|
||||
# cache state for performance and only run
|
||||
# if values or param differ
|
||||
if coreWorkflowRestrictions?[classname]?[item.name]
|
||||
compare = values
|
||||
continue if _.isEqual(coreWorkflowRestrictions[classname][item.name], compare)
|
||||
|
||||
coreWorkflowRestrictions[classname] ||= {}
|
||||
coreWorkflowRestrictions[classname][item.name] = values
|
||||
|
||||
valueFound = false
|
||||
for value in values
|
||||
if value && paramValue
|
||||
if value.toString() == paramValue.toString()
|
||||
valueFound = true
|
||||
break
|
||||
if _.isArray(paramValue) && _.contains(paramValue, value.toString())
|
||||
valueFound = true
|
||||
break
|
||||
|
||||
item.filter = values
|
||||
if valueFound
|
||||
item.default = paramValue
|
||||
item.newValue = paramValue
|
||||
else
|
||||
item.default = ''
|
||||
item.newValue = ''
|
||||
|
||||
if attribute.relation
|
||||
item.rejectNonExistentValues = true
|
||||
|
||||
ui.params ||= {}
|
||||
newElement = ui.formGenItem(item, classname, form)
|
||||
|
||||
# copy existing events to new rendered element
|
||||
form.find('[name="' + field + '"]').closest('.form-group').find("[name!=''][name]").each(->
|
||||
target_name = $(@).attr('name')
|
||||
$.each($._data(@, 'events'), (eventType, eventArray) ->
|
||||
$.each(eventArray, (index, event) ->
|
||||
eventToBind = event.type
|
||||
if event.namespace.length > 0
|
||||
eventToBind = event.type + '.' + event.namespace
|
||||
target = newElement.find("[name='" + target_name + "']")
|
||||
if target.length > 0
|
||||
target.bind(eventToBind, event.data, event.handler)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
form.find('[name="' + field + '"]').closest('.form-group').replaceWith(newElement)
|
||||
|
||||
if shown
|
||||
ui.show(field, form)
|
||||
else
|
||||
ui.hide(field, form)
|
||||
if mandatory
|
||||
ui.mandantory(field, form)
|
||||
else
|
||||
ui.optional(field, form)
|
||||
|
||||
# fill in data in input fields
|
||||
@select: (classname, form, ui, attributes, params, data) ->
|
||||
return if _.isEmpty(data)
|
||||
|
||||
for field, values of data
|
||||
form.find('[name="' + field + '"]').val(data[field])
|
||||
coreWorkflowParams[classname][field] = data[field]
|
||||
|
||||
# fill in data in input fields
|
||||
@fillIn: (classname, form, ui, attributes, params, data) ->
|
||||
return if _.isEmpty(data)
|
||||
|
||||
for field, values of data
|
||||
form.find('[name="' + field + '"]').val(data[field])
|
||||
coreWorkflowParams[classname][field] = data[field]
|
||||
|
||||
# changes the visibility of form elements
|
||||
@changeVisibility: (form, ui, data) ->
|
||||
return if _.isEmpty(data)
|
||||
|
||||
for field, state of data
|
||||
if state is 'show'
|
||||
ui.show(field, form)
|
||||
else if state is 'hide'
|
||||
ui.hide(field, form)
|
||||
else if state is 'remove'
|
||||
ui.hide(field, form, true)
|
||||
|
||||
# changes the mandatory flag of form elements
|
||||
@changeMandatory: (form, ui, data) ->
|
||||
return if _.isEmpty(data)
|
||||
|
||||
for field, state of data
|
||||
if state
|
||||
ui.mandantory(field, form)
|
||||
else
|
||||
ui.optional(field, form)
|
||||
|
||||
# executes individual js commands of the Core Workflow engine
|
||||
@executeEval: (form, ui, data) ->
|
||||
return if _.isEmpty(data)
|
||||
|
||||
for statement in data
|
||||
eval(statement)
|
||||
|
||||
# runs callbacks which are defined for the controller form
|
||||
@runCallbacks: (ui) ->
|
||||
callbacks = ui?.core_workflow?.callbacks || []
|
||||
for callback in callbacks
|
||||
callback()
|
||||
|
||||
# runs a complete workflow based on a request result and the form params of the form handler
|
||||
@runWorkflow: (data, classname, form, ui, attributes, params) ->
|
||||
App.Collection.loadAssets(data.assets)
|
||||
App.FormHandlerCoreWorkflow.restrictValues(classname, form, ui, attributes, params, data)
|
||||
App.FormHandlerCoreWorkflow.select(classname, form, ui, attributes, params, data.select)
|
||||
App.FormHandlerCoreWorkflow.fillIn(classname, form, ui, attributes, params, data.fill_in)
|
||||
App.FormHandlerCoreWorkflow.changeVisibility(form, ui, data.visibility)
|
||||
App.FormHandlerCoreWorkflow.changeMandatory(form, ui, data.mandatory)
|
||||
App.FormHandlerCoreWorkflow.executeEval(form, ui, data.eval)
|
||||
App.FormHandlerCoreWorkflow.runCallbacks(ui)
|
||||
|
||||
# loads the request data and prepares the run of the workflow data
|
||||
@runRequest: (data) ->
|
||||
return if !coreWorkflowRequests[data.request_id]
|
||||
|
||||
triggerSubmit = coreWorkflowRequests[data.request_id].triggerSubmit
|
||||
classname = coreWorkflowRequests[data.request_id].classname
|
||||
form = coreWorkflowRequests[data.request_id].form
|
||||
ui = coreWorkflowRequests[data.request_id].ui
|
||||
attributes = coreWorkflowRequests[data.request_id].attributes
|
||||
params = coreWorkflowRequests[data.request_id].params
|
||||
|
||||
App.FormHandlerCoreWorkflow.runWorkflow(data, classname, form, ui, attributes, params)
|
||||
|
||||
delete coreWorkflowRequests[data.request_id]
|
||||
|
||||
if triggerSubmit
|
||||
App.FormHandlerCoreWorkflow.triggerSubmit(triggerSubmit)
|
||||
|
||||
# this will set the hook for the websocket if activated
|
||||
@setHook: =>
|
||||
return if @hooked
|
||||
return if !App.FormHandlerCoreWorkflow.useWebSockets()
|
||||
@hooked = true
|
||||
App.Event.bind(
|
||||
'core_workflow'
|
||||
(data) =>
|
||||
@runRequest(data)
|
||||
'ws:core_workflow'
|
||||
)
|
||||
|
||||
# this will return the needed form element
|
||||
@getForm: (form) ->
|
||||
return form.closest('form') if form.get(0).tagName != 'FORM'
|
||||
return $(form)
|
||||
|
||||
# cleanup of some bad params
|
||||
@cleanParams: (params_ref) ->
|
||||
params = $.extend(true, {}, params_ref)
|
||||
delete params.customer_id_completion
|
||||
delete params.tags
|
||||
delete params.formSenderType
|
||||
return params
|
||||
|
||||
# this will use the form handler information to send the data to the backend via ajax/websockets
|
||||
@request: (classname, form, ui, attributes, params) ->
|
||||
requestID = "CoreWorkflow-#{Math.floor( Math.random() * 999999 ).toString()}"
|
||||
coreWorkflowRequests[requestID] = { classname: classname, form: form, ui: ui, attributes: attributes, params: params }
|
||||
|
||||
requestData = {
|
||||
event: 'core_workflow',
|
||||
request_id: requestID,
|
||||
params: params,
|
||||
class_name: ui.model.className,
|
||||
screen: ui.screen
|
||||
}
|
||||
|
||||
if App.FormHandlerCoreWorkflow.useWebSockets()
|
||||
App.WebSocket.send(requestData)
|
||||
else
|
||||
ui.ajax(
|
||||
id: "core_workflow-#{requestData.request_id}"
|
||||
type: 'POST'
|
||||
url: "#{ui.apiPath}/core_workflows/perform"
|
||||
data: JSON.stringify(requestData)
|
||||
success: (data, status, xhr) =>
|
||||
@runRequest(data)
|
||||
error: (data) ->
|
||||
delete coreWorkflowRequests[requestID]
|
||||
return
|
||||
)
|
||||
|
||||
@run: (params_ref, attribute, attributes, classname, form, ui) ->
|
||||
|
||||
# skip on blacklisted tags
|
||||
return if _.contains(['ticket_selector', 'core_workflow_condition', 'core_workflow_perform'], attribute.tag)
|
||||
|
||||
# check if Core Workflow screen
|
||||
return if !App.FormHandlerCoreWorkflow.screenValid(ui)
|
||||
|
||||
# get params and add id from ui if needed
|
||||
params = App.FormHandlerCoreWorkflow.cleanParams(params_ref)
|
||||
if ui?.params?.id
|
||||
params.id = ui.params.id
|
||||
|
||||
# skip double checks
|
||||
return if _.isEqual(coreWorkflowParams[classname], params)
|
||||
coreWorkflowParams[classname] = params
|
||||
|
||||
# render intial state provided by screen options if given
|
||||
# for more performance and less requests
|
||||
if ui.formMeta && ui.formMeta.core_workflow && !ui.lastChangedAttribute
|
||||
App.FormHandlerCoreWorkflow.runWorkflow(ui.formMeta.core_workflow, classname, form, ui, attributes, params)
|
||||
return
|
||||
|
||||
App.FormHandlerCoreWorkflow.setHook()
|
||||
App.FormHandlerCoreWorkflow.request(classname, form, ui, attributes, params)
|
|
@ -1,33 +0,0 @@
|
|||
class TicketZoomFormHandlerDependencies
|
||||
|
||||
# central method, is getting called on every ticket form change
|
||||
@run: (params, attribute, attributes, classname, form, ui) ->
|
||||
return if !ui.formMeta
|
||||
return if !ui.formMeta.dependencies
|
||||
return if !ui.formMeta.dependencies[attribute.name]
|
||||
dependency = ui.formMeta.dependencies[attribute.name][ parseInt(params[attribute.name]) ]
|
||||
if !dependency
|
||||
dependency = ui.formMeta.dependencies[attribute.name][ params[attribute.name] ]
|
||||
if dependency
|
||||
for fieldNameToChange of dependency
|
||||
filter = []
|
||||
if dependency[fieldNameToChange]
|
||||
filter = dependency[fieldNameToChange]
|
||||
|
||||
# find element to replace
|
||||
for item in attributes
|
||||
if item.name is fieldNameToChange
|
||||
item['filter'] = {}
|
||||
item['filter'][ fieldNameToChange ] = filter
|
||||
item.default = params[item.name]
|
||||
item.newValue = params[item.name]
|
||||
#if !item.default
|
||||
# delete item['default']
|
||||
newElement = ui.formGenItem(item, classname, form)
|
||||
|
||||
# replace new option list
|
||||
if newElement
|
||||
form.find('[name="' + fieldNameToChange + '"]').closest('.form-group').replaceWith(newElement)
|
||||
|
||||
App.Config.set('100-ticketFormChanges', TicketZoomFormHandlerDependencies, 'TicketZoomFormHandler')
|
||||
App.Config.set('100-ticketFormChanges', TicketZoomFormHandlerDependencies, 'TicketCreateFormHandler')
|
|
@ -1,26 +0,0 @@
|
|||
class OwnerFormHandlerDependencies
|
||||
|
||||
# central method, is getting called on every ticket form change
|
||||
@run: (params, attribute, attributes, classname, form, ui) ->
|
||||
return if 'group_id' not of params
|
||||
return if 'owner_id' not of params
|
||||
|
||||
owner_attribute = _.find(attributes, (o) -> o.name == 'owner_id')
|
||||
return if !owner_attribute
|
||||
return if 'possible_groups_owners' not of owner_attribute
|
||||
|
||||
# fetch contents using User relation if a Group has been selected, otherwise render possible_groups_owners
|
||||
if params.group_id
|
||||
owner_attribute.relation = 'User'
|
||||
delete owner_attribute['options']
|
||||
else
|
||||
owner_attribute.options = owner_attribute.possible_groups_owners
|
||||
delete owner_attribute['relation']
|
||||
|
||||
# replace new option list
|
||||
owner_attribute.default = params[owner_attribute.name]
|
||||
owner_attribute.newValue = params[owner_attribute.name]
|
||||
newElement = ui.formGenItem(owner_attribute, classname, form)
|
||||
form.find('select[name="owner_id"]').closest('.form-group').replaceWith(newElement)
|
||||
|
||||
App.Config.set('150-ticketFormChanges', OwnerFormHandlerDependencies, 'TicketZoomFormHandler')
|
|
@ -4,6 +4,9 @@ class App.TicketZoomSidebar extends App.ControllerObserver
|
|||
customer_id: true
|
||||
organization_id: true
|
||||
|
||||
get: (key) ->
|
||||
return @sidebarBackends[key]
|
||||
|
||||
reload: (args) =>
|
||||
for key, backend of @sidebarBackends
|
||||
if backend && backend.reload
|
||||
|
|
|
@ -20,7 +20,7 @@ class Edit extends App.ControllerObserver
|
|||
if followUpPossible == 'new_ticket' && ticketState != 'closed' ||
|
||||
followUpPossible != 'new_ticket' ||
|
||||
@permissionCheck('admin') || ticket.currentView() is 'agent'
|
||||
new App.ControllerForm(
|
||||
@controllerFormSidebarTicket = new App.ControllerForm(
|
||||
elReplace: @el
|
||||
model: { className: 'Ticket', configure_attributes: @formMeta.configure_attributes || App.Ticket.configure_attributes }
|
||||
screen: 'edit'
|
||||
|
@ -30,10 +30,13 @@ class Edit extends App.ControllerObserver
|
|||
params: defaults
|
||||
isDisabled: !ticket.editable()
|
||||
taskKey: @taskKey
|
||||
core_workflow: {
|
||||
callbacks: [@markForm]
|
||||
}
|
||||
#bookmarkable: true
|
||||
)
|
||||
else
|
||||
new App.ControllerForm(
|
||||
@controllerFormSidebarTicket = new App.ControllerForm(
|
||||
elReplace: @el
|
||||
model: { configure_attributes: @formMeta.configure_attributes || App.Ticket.configure_attributes }
|
||||
screen: 'edit'
|
||||
|
@ -43,6 +46,9 @@ class Edit extends App.ControllerObserver
|
|||
params: defaults
|
||||
isDisabled: ticket.editable()
|
||||
taskKey: @taskKey
|
||||
core_workflow: {
|
||||
callbacks: [@markForm]
|
||||
}
|
||||
#bookmarkable: true
|
||||
)
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ class App.InviteUser extends App.ControllerWizardModal
|
|||
modal = $(App.view('widget/invite_user')(
|
||||
head: @head
|
||||
))
|
||||
new App.ControllerForm(
|
||||
@controller = new App.ControllerForm(
|
||||
el: modal.find('.js-form')
|
||||
model: App.User
|
||||
screen: @screen
|
||||
|
@ -60,7 +60,7 @@ class App.InviteUser extends App.ControllerWizardModal
|
|||
user.load(@params)
|
||||
|
||||
errors = user.validate(
|
||||
screen: @screen
|
||||
controllerForm: @controller
|
||||
)
|
||||
if errors
|
||||
@log 'error new', errors
|
||||
|
|
|
@ -26,7 +26,7 @@ class App.WidgetTemplate extends App.Controller
|
|||
@html App.view('widget/template')(
|
||||
template: template
|
||||
)
|
||||
new App.ControllerForm(
|
||||
@controller = new App.ControllerForm(
|
||||
el: @el.find('#form-template')
|
||||
model:
|
||||
configure_attributes: @configure_attributes
|
||||
|
@ -98,7 +98,9 @@ class App.WidgetTemplate extends App.Controller
|
|||
)
|
||||
|
||||
# validate form
|
||||
errors = template.validate()
|
||||
errors = template.validate(
|
||||
controllerForm: @controller
|
||||
)
|
||||
|
||||
# show errors in form
|
||||
if errors
|
||||
|
|
|
@ -24,6 +24,9 @@ class App.TicketBulkForm extends App.Controller
|
|||
localAttribute.null = true
|
||||
@configure_attributes_ticket.push localAttribute
|
||||
|
||||
# add field for ticket ids
|
||||
ticket_ids_attribute = { name: 'ticket_ids', display: false, tag: 'input', type: 'hidden', limit: 100, null: false }
|
||||
@configure_attributes_ticket.push ticket_ids_attribute
|
||||
|
||||
time_attribute = _.findWhere(@configure_attributes_ticket, {'name': 'pending_time'})
|
||||
if time_attribute
|
||||
|
@ -37,10 +40,10 @@ class App.TicketBulkForm extends App.Controller
|
|||
App.Collection.loadAssets(data.assets)
|
||||
@formMeta = data.form_meta
|
||||
@render()
|
||||
@bindId = App.TicketCreateCollection.bind(load)
|
||||
@bindId = App.TicketOverviewCollection.bind(load)
|
||||
|
||||
release: =>
|
||||
App.TicketCreateCollection.unbind(@bindId)
|
||||
App.TicketOverviewCollection.unbind(@bindId)
|
||||
|
||||
render: ->
|
||||
@el.css('right', App.Utils.getScrollBarWidth())
|
||||
|
@ -50,18 +53,13 @@ class App.TicketBulkForm extends App.Controller
|
|||
|
||||
handlers = @Config.get('TicketZoomFormHandler')
|
||||
|
||||
for attribute in @configure_attributes_ticket
|
||||
continue if attribute.name != 'owner_id'
|
||||
{users, groups} = @validUsersForTicketSelection()
|
||||
options = _.map(users, (user) -> {value: user.id, name: user.displayName()} )
|
||||
attribute.possible_groups_owners = options
|
||||
|
||||
new App.ControllerForm(
|
||||
@controllerFormBulk = new App.ControllerForm(
|
||||
el: @$('#form-ticket-bulk')
|
||||
model:
|
||||
configure_attributes: @configure_attributes_ticket
|
||||
className: 'create'
|
||||
className: 'Ticket'
|
||||
labelClass: 'input-group-addon'
|
||||
screen: 'overview_bulk'
|
||||
handlersConfig: handlers
|
||||
params: {}
|
||||
filter: @formMeta.filter
|
||||
|
@ -73,8 +71,9 @@ class App.TicketBulkForm extends App.Controller
|
|||
el: @$('#form-ticket-bulk-comment')
|
||||
model:
|
||||
configure_attributes: [{ name: 'body', display: 'Comment', tag: 'textarea', rows: 4, null: true, upload: false, item_class: 'flex' }]
|
||||
className: 'create'
|
||||
className: 'Ticket'
|
||||
labelClass: 'input-group-addon'
|
||||
screen: 'overview_bulk_comment'
|
||||
noFieldset: true
|
||||
)
|
||||
|
||||
|
@ -87,8 +86,9 @@ class App.TicketBulkForm extends App.Controller
|
|||
el: @$('#form-ticket-bulk-typeVisibility')
|
||||
model:
|
||||
configure_attributes: @confirm_attributes
|
||||
className: 'create'
|
||||
className: 'Ticket'
|
||||
labelClass: 'input-group-addon'
|
||||
screen: 'overview_bulk_visibility'
|
||||
noFieldset: true
|
||||
)
|
||||
|
||||
|
@ -183,7 +183,7 @@ class App.TicketBulkForm extends App.Controller
|
|||
|
||||
# validate ticket
|
||||
errors = ticket.validate(
|
||||
screen: 'edit'
|
||||
controllerForm: @controllerFormBulk
|
||||
)
|
||||
if errors
|
||||
@log 'error', 'update', errors
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
class _Singleton extends App._CollectionSingletonBase
|
||||
event: 'ticket_overview_attributes'
|
||||
restEndpoint: '/ticket_overview'
|
||||
|
||||
class App.TicketOverviewCollection
|
||||
_instance = new _Singleton
|
||||
|
||||
@get: ->
|
||||
_instance.get()
|
||||
|
||||
@one: (callback, init = true) ->
|
||||
_instance.bind(callback, init, true)
|
||||
|
||||
@bind: (callback, init = true) ->
|
||||
_instance.bind(callback, init, false)
|
||||
|
||||
@unbind: (callback) ->
|
||||
_instance.unbind(callback)
|
||||
|
||||
@unbindById: (id) ->
|
||||
_instance.unbindById(id)
|
||||
|
||||
@trigger: ->
|
||||
_instance.trigger()
|
||||
|
||||
@fetch: ->
|
||||
_instance.fetch()
|
|
@ -40,7 +40,7 @@ class UserNew extends App.ControllerModal
|
|||
content: ->
|
||||
@controller = new App.ControllerForm(
|
||||
model: App.User
|
||||
screen: 'edit'
|
||||
screen: 'create'
|
||||
autofocus: true
|
||||
)
|
||||
@controller.form
|
||||
|
@ -64,7 +64,9 @@ class UserNew extends App.ControllerModal
|
|||
user = new App.User
|
||||
user.load(params)
|
||||
|
||||
errors = user.validate()
|
||||
errors = user.validate(
|
||||
controllerForm: @controller
|
||||
)
|
||||
if errors
|
||||
@log 'error', errors
|
||||
@formValidate(form: e.target, errors: errors)
|
||||
|
|
|
@ -11,7 +11,7 @@ App.ValidUsersForTicketSelectionMethods =
|
|||
users = @usersInGroups(ticket_group_ids)
|
||||
|
||||
# get the list of possible groups for the current user
|
||||
# from the TicketCreateCollection
|
||||
# from the TicketOverviewCollection
|
||||
# (filled for e.g. the TicketCreation or TicketZoom assignment)
|
||||
# and order them by name
|
||||
group_ids = _.keys(@formMeta?.dependencies?.group_id)
|
||||
|
@ -19,7 +19,7 @@ App.ValidUsersForTicketSelectionMethods =
|
|||
groups_sorted = _.sortBy(groups, (group) -> group.name)
|
||||
|
||||
# get the number of visible users per group
|
||||
# from the TicketCreateCollection
|
||||
# from the TicketOverviewCollection
|
||||
# (filled for e.g. the TicketCreation or TicketZoom assignment)
|
||||
for group in groups
|
||||
group.valid_users_count = @formMeta?.dependencies?.group_id?[group.id]?.owner_id.length || 0
|
||||
|
|
|
@ -234,16 +234,20 @@ class Singleton extends Base
|
|||
|
||||
failResponse: (options) =>
|
||||
(xhr, statusText, error, settings) =>
|
||||
if options.failResponseNoTrigger isnt true
|
||||
switch settings.type
|
||||
when 'POST' then @createFailed()
|
||||
when 'DELETE' then @destroyFailed()
|
||||
# add errors to calllback
|
||||
@record.trigger('ajaxError', @record, xhr, statusText, error, settings)
|
||||
|
||||
#options.fail?.call(@record, settings)
|
||||
detailsRaw = xhr.responseText
|
||||
if !_.isEmpty(detailsRaw)
|
||||
details = JSON.parse(detailsRaw)
|
||||
options.fail?.call(@record, settings, details)
|
||||
|
||||
if options.failResponseNoTrigger isnt true
|
||||
@record.trigger('destroy', @record)
|
||||
# /add errors to calllback
|
||||
|
||||
|
|
|
@ -82,35 +82,15 @@ class App.Model extends Spine.Model
|
|||
''
|
||||
|
||||
@validate: (data = {}) ->
|
||||
screen = data?.controllerForm?.screen
|
||||
|
||||
# based on model attributes
|
||||
if App[ data['model'] ] && App[ data['model'] ].attributesGet
|
||||
attributes = App[ data['model'] ].attributesGet(data['screen'])
|
||||
attributes = App[ data['model'] ].attributesGet(screen)
|
||||
|
||||
# based on custom attributes
|
||||
else if data['model'].configure_attributes
|
||||
attributes = App.Model.attributesGet(data['screen'], data['model'].configure_attributes)
|
||||
|
||||
# check required_if attributes
|
||||
for attributeName, attribute of attributes
|
||||
if attribute['required_if']
|
||||
|
||||
for key, values of attribute['required_if']
|
||||
|
||||
localValues = data['params'][key]
|
||||
if !_.isArray( localValues )
|
||||
localValues = [ localValues ]
|
||||
|
||||
match = false
|
||||
for value in values
|
||||
if localValues
|
||||
for localValue in localValues
|
||||
if value && localValue && value.toString() is localValue.toString()
|
||||
match = true
|
||||
if match is true
|
||||
attribute['null'] = false
|
||||
else
|
||||
attribute['null'] = true
|
||||
attributes = App.Model.attributesGet(screen, data['model'].configure_attributes)
|
||||
|
||||
# check attributes/each attribute of object
|
||||
errors = {}
|
||||
|
@ -120,7 +100,7 @@ class App.Model extends Spine.Model
|
|||
if !attribute.readonly
|
||||
|
||||
# check required // if null is defined && null is false
|
||||
if 'null' of attribute && !attribute['null']
|
||||
if data.controllerForm && data.controllerForm.attributeIsMandatory(attribute.name)
|
||||
|
||||
# check :: fields
|
||||
parts = attribute.name.split '::'
|
||||
|
@ -168,8 +148,12 @@ class App.Model extends Spine.Model
|
|||
|
||||
# validate value
|
||||
|
||||
if data?.controllerForm && App.FormHandlerCoreWorkflow.requestsRunning(data.controllerForm)
|
||||
errors['_core_workflow'] = { target: data.target, controllerForm: data.controllerForm }
|
||||
|
||||
# return error object
|
||||
if !_.isEmpty(errors)
|
||||
if !errors['_core_workflow']
|
||||
App.Log.error('Model', 'validation failed', errors)
|
||||
return errors
|
||||
|
||||
|
@ -256,7 +240,8 @@ set new attributes of model (remove already available attributes)
|
|||
App.Model.validate(
|
||||
model: @constructor.className
|
||||
params: @
|
||||
screen: params.screen
|
||||
controllerForm: params.controllerForm
|
||||
target: params.target
|
||||
)
|
||||
|
||||
isOnline: ->
|
||||
|
|
27
app/assets/javascripts/app/models/core_workflow.coffee
Normal file
27
app/assets/javascripts/app/models/core_workflow.coffee
Normal file
|
@ -0,0 +1,27 @@
|
|||
class App.CoreWorkflow extends App.Model
|
||||
@configure 'CoreWorkflow', 'name', 'object', 'preferences', 'condition_saved', 'condition_selected', 'perform', 'priority', 'active'
|
||||
@extend Spine.Model.Ajax
|
||||
@url: @apiPath + '/core_workflows'
|
||||
@configure_attributes = [
|
||||
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false },
|
||||
{ name: 'object', display: 'Object', tag: 'select', null: false, nulloption: true },
|
||||
{ name: 'preferences::screen', display: 'Action', tag: 'select', translate: true, null: true, multiple: true, nulloption: true },
|
||||
{ name: 'condition_selected', display: 'Selected conditions', tag: 'core_workflow_condition', null: true, preview: false },
|
||||
{ name: 'condition_saved', display: 'Saved conditions', tag: 'core_workflow_condition', null: true, preview: false },
|
||||
{ name: 'perform', display: 'Action', tag: 'core_workflow_perform', null: true, preview: false },
|
||||
{ name: 'stop_after_match', display: 'Stop after match', tag: 'boolean', null: false, default: false },
|
||||
{ name: 'priority', display: 'Priority', tag: 'integer', type: 'text', limit: 100, null: false, default: 500 },
|
||||
{ name: 'active', display: 'Active', tag: 'active', default: true },
|
||||
{ name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
|
||||
]
|
||||
@configure_delete = true
|
||||
@configure_clone = true
|
||||
@configure_overview = [
|
||||
'name',
|
||||
'priority',
|
||||
]
|
||||
|
||||
@description = '''
|
||||
Core Workflows are actions or constraints on selections in forms. Depending on an action, it is possible to hide or restrict fields or to change the obligation to fill them in.
|
||||
'''
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
class App.CoreWorkflowCustomModule extends App.Model
|
||||
@configure 'CoreWorkflowCustomModule', 'name'
|
||||
@configure_attributes = [
|
||||
{ name: 'name', display: 'Name', tag: 'input', type: 'text', null: false },
|
||||
]
|
||||
|
|
@ -11,9 +11,9 @@ class App.Sla extends App.Model
|
|||
{ name: 'created_at', display: 'Created', tag: 'datetime', readonly: 1 },
|
||||
{ name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 },
|
||||
{ name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
|
||||
{ name: 'first_response_time', null: false, skipRendering: true, required_if: { 'first_response_time_enabled': ['on'] } },
|
||||
{ name: 'update_time', null: false, skipRendering: true, required_if: { 'update_time_enabled': ['on'] } },
|
||||
{ name: 'solution_time', null: false, skipRendering: true, required_if: { 'solution_time_enabled': ['on'] } },
|
||||
{ name: 'first_response_time',skipRendering: true },
|
||||
{ name: 'update_time', skipRendering: true },
|
||||
{ name: 'solution_time', skipRendering: true },
|
||||
]
|
||||
@configure_delete = true
|
||||
@configure_overview = [
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<input type="hidden" class="empty" name="<%= @attribute.name %>::" value="" />
|
|
@ -11,12 +11,14 @@
|
|||
<%- @Icon('arrow-down', 'dropdown-arrow') %>
|
||||
</div>
|
||||
</div>
|
||||
<% if @pre_condition: %>
|
||||
<div class="controls">
|
||||
<div class="u-positionOrigin js-preCondition">
|
||||
<select></select>
|
||||
<%- @Icon('arrow-down', 'dropdown-arrow') %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="controls js-value horizontal horizontal-filter-value"></div>
|
||||
</div>
|
||||
<div class="filter-controls">
|
|
@ -1,4 +1,4 @@
|
|||
<div data-attribute-name="<%= @attribute.name %>" class="<%= @attribute.tag %> form-group<%= " form-group--block" if @attribute.style == 'block' %><%= " #{ @attribute.item_class }" if @attribute.item_class %>"<%= " data-width=#{ @attribute.grid_width }" if @attribute.grid_width %>>
|
||||
<div data-attribute-name="<%= @attribute.name %>" class="<%= @attribute.tag %> form-group<%= " form-group--block" if @attribute.style == 'block' %><%= " #{ @attribute.item_class }" if @attribute.item_class %><%= " is-required" if !@attribute.null %>"<%= " data-width=#{ @attribute.grid_width }" if @attribute.grid_width %>>
|
||||
<% if @attribute.style == 'block': %>
|
||||
<h2>
|
||||
<% end %>
|
||||
|
|
|
@ -12,6 +12,9 @@ module ApplicationController::RendersModels
|
|||
|
||||
clean_params = object.association_name_to_id_convert(params)
|
||||
clean_params = object.param_cleanup(clean_params, true)
|
||||
if object.included_modules.include?(ChecksCoreWorkflow)
|
||||
clean_params[:screen] = 'create'
|
||||
end
|
||||
|
||||
# create object
|
||||
generic_object = object.new(clean_params)
|
||||
|
@ -46,6 +49,9 @@ module ApplicationController::RendersModels
|
|||
|
||||
clean_params = object.association_name_to_id_convert(params)
|
||||
clean_params = object.param_cleanup(clean_params, true)
|
||||
if object.included_modules.include?(ChecksCoreWorkflow)
|
||||
clean_params[:screen] = 'update'
|
||||
end
|
||||
|
||||
generic_object.with_lock do
|
||||
|
||||
|
|
30
app/controllers/core_workflows_controller.rb
Normal file
30
app/controllers/core_workflows_controller.rb
Normal file
|
@ -0,0 +1,30 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflowsController < ApplicationController
|
||||
prepend_before_action { authentication_check && authorize! }
|
||||
|
||||
def index
|
||||
model_index_render(CoreWorkflow.changeable, params)
|
||||
end
|
||||
|
||||
def show
|
||||
model_show_render(CoreWorkflow.changeable, params)
|
||||
end
|
||||
|
||||
def create
|
||||
model_create_render(CoreWorkflow.changeable, params)
|
||||
end
|
||||
|
||||
def update
|
||||
model_update_render(CoreWorkflow.changeable, params)
|
||||
end
|
||||
|
||||
def destroy
|
||||
model_destroy_render(CoreWorkflow.changeable, params)
|
||||
end
|
||||
|
||||
def perform
|
||||
render json: CoreWorkflow.perform(payload: params, user: current_user)
|
||||
end
|
||||
|
||||
end
|
|
@ -3,6 +3,17 @@
|
|||
class TicketOverviewsController < ApplicationController
|
||||
prepend_before_action :authentication_check
|
||||
|
||||
# GET /api/v1/ticket_overview
|
||||
def data
|
||||
|
||||
# get attributes to update
|
||||
attributes_to_change = Ticket::ScreenOptions.attributes_to_change(
|
||||
view: 'ticket_overview',
|
||||
current_user: current_user,
|
||||
)
|
||||
render json: attributes_to_change
|
||||
end
|
||||
|
||||
# GET /api/v1/ticket_overviews
|
||||
def show
|
||||
|
||||
|
|
|
@ -136,6 +136,7 @@ class TicketsController < ApplicationController
|
|||
end
|
||||
|
||||
clean_params = Ticket.param_cleanup(clean_params, true)
|
||||
clean_params[:screen] = 'create_middle'
|
||||
ticket = Ticket.new(clean_params)
|
||||
authorize!(ticket, :create?)
|
||||
|
||||
|
@ -232,6 +233,7 @@ class TicketsController < ApplicationController
|
|||
|
||||
# only apply preferences changes (keep not updated keys/values)
|
||||
clean_params = ticket.param_preferences_merge(clean_params)
|
||||
clean_params[:screen] = 'edit'
|
||||
|
||||
# disable changes on ticket number
|
||||
clean_params.delete('number')
|
||||
|
@ -426,6 +428,7 @@ class TicketsController < ApplicationController
|
|||
# get attributes to update
|
||||
attributes_to_change = Ticket::ScreenOptions.attributes_to_change(
|
||||
view: 'ticket_create',
|
||||
screen: 'create_middle',
|
||||
current_user: current_user,
|
||||
)
|
||||
render json: attributes_to_change
|
||||
|
@ -659,7 +662,8 @@ class TicketsController < ApplicationController
|
|||
# get attributes to update
|
||||
attributes_to_change = Ticket::ScreenOptions.attributes_to_change(
|
||||
current_user: current_user,
|
||||
ticket: ticket
|
||||
ticket: ticket,
|
||||
screen: 'edit',
|
||||
)
|
||||
|
||||
# get related users
|
||||
|
|
|
@ -120,6 +120,7 @@ class UsersController < ApplicationController
|
|||
user.with_lock do
|
||||
clean_params = User.association_name_to_id_convert(params)
|
||||
clean_params = User.param_cleanup(clean_params, true)
|
||||
clean_params[:screen] = 'update'
|
||||
user.update!(clean_params)
|
||||
|
||||
# presence and permissions were checked via `check_attributes_by_current_user_permission`
|
||||
|
@ -887,7 +888,7 @@ curl http://localhost/api/v1/users/avatar -v -u #{login}:#{password} -H "Content
|
|||
private
|
||||
|
||||
def clean_user_params
|
||||
User.param_cleanup(User.association_name_to_id_convert(params), true)
|
||||
User.param_cleanup(User.association_name_to_id_convert(params), true).merge(screen: 'create')
|
||||
end
|
||||
|
||||
# @summary Creates a User record with the provided attribute values.
|
||||
|
|
|
@ -65,11 +65,14 @@ get assets and record_ids of selector
|
|||
attribute = item.split('.')
|
||||
next if !attribute[1]
|
||||
|
||||
if attribute[0] == 'customer' || attribute[0] == 'session'
|
||||
attribute[0] = 'user'
|
||||
end
|
||||
|
||||
begin
|
||||
attribute_class = attribute[0].to_classname.constantize
|
||||
rescue => e
|
||||
next if attribute[0] == 'article'
|
||||
next if attribute[0] == 'customer'
|
||||
next if attribute[0] == 'execution_time'
|
||||
|
||||
logger.error "Unable to get asset for '#{attribute[0]}': #{e.inspect}"
|
||||
|
|
|
@ -22,7 +22,7 @@ returns
|
|||
|
||||
=end
|
||||
|
||||
def param_cleanup(params, new_object = false, inside_nested = false)
|
||||
def param_cleanup(params, new_object = false, inside_nested = false, exceptions = true)
|
||||
|
||||
if params.respond_to?(:permit!)
|
||||
params = params.permit!.to_h
|
||||
|
@ -52,6 +52,8 @@ returns
|
|||
new.attributes.each_key do |attribute|
|
||||
next if !data.key?(attribute)
|
||||
|
||||
invalid = false
|
||||
|
||||
# check reference records, referenced by _id attributes
|
||||
reflect_on_all_associations.map do |assoc|
|
||||
class_name = assoc.options[:class_name]
|
||||
|
@ -62,8 +64,14 @@ returns
|
|||
next if data[name].blank?
|
||||
next if assoc.klass.lookup(id: data[name])
|
||||
|
||||
raise Exceptions::UnprocessableEntity, "Invalid value for param '#{name}': #{data[name].inspect}"
|
||||
raise Exceptions::UnprocessableEntity, "Invalid value for param '#{name}': #{data[name].inspect}" if exceptions
|
||||
|
||||
invalid = true
|
||||
break
|
||||
end
|
||||
|
||||
next if invalid
|
||||
|
||||
clean_params[attribute] = data[attribute]
|
||||
end
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ module ApplicationModel::HasCache
|
|||
def cache_update(other)
|
||||
cache_delete if respond_to?('cache_delete')
|
||||
other.cache_delete if other.respond_to?('cache_delete')
|
||||
ActiveSupport::CurrentAttributes.clear_all
|
||||
true
|
||||
end
|
||||
|
||||
|
|
|
@ -22,11 +22,32 @@ returns
|
|||
=end
|
||||
|
||||
def permissions?(auth_query)
|
||||
RequestCache.permissions?(self, auth_query)
|
||||
end
|
||||
|
||||
class RequestCache < ActiveSupport::CurrentAttributes
|
||||
attribute :permission_cache
|
||||
|
||||
def self.permissions?(authorizable, auth_query)
|
||||
self.permission_cache ||= {}
|
||||
|
||||
begin
|
||||
authorizable_key = authorizable.to_global_id.to_s
|
||||
rescue
|
||||
return instance.permissions?(authorizable, auth_query)
|
||||
end
|
||||
auth_query_key = Array(auth_query).join('|')
|
||||
|
||||
self.permission_cache[authorizable_key] ||= {}
|
||||
self.permission_cache[authorizable_key][auth_query_key] ||= instance.permissions?(authorizable, auth_query)
|
||||
end
|
||||
|
||||
def permissions?(authorizable, auth_query)
|
||||
verbatim, wildcards = acceptable_permissions_for(auth_query)
|
||||
|
||||
permissions.where(name: verbatim).then do |base_query|
|
||||
authorizable.permissions.where(name: verbatim).then do |base_query|
|
||||
wildcards.reduce(base_query) do |query, name|
|
||||
query.or(permissions.where('permissions.name LIKE ?', name.sub('.*', '.%')))
|
||||
query.or(authorizable.permissions.where('permissions.name LIKE ?', name.sub('.*', '.%')))
|
||||
end
|
||||
end.exists?
|
||||
end
|
||||
|
@ -40,3 +61,4 @@ returns
|
|||
.partition { |name| name.end_with?('.*') }.reverse
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
59
app/models/concerns/checks_core_workflow.rb
Normal file
59
app/models/concerns/checks_core_workflow.rb
Normal file
|
@ -0,0 +1,59 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
module ChecksCoreWorkflow
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
before_validation :validate_workflows
|
||||
|
||||
attr_accessor :screen
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_workflows
|
||||
return if !screen
|
||||
return if !UserInfo.current_user_id
|
||||
|
||||
perform_result = CoreWorkflow.perform(payload: {
|
||||
'event' => 'core_workflow',
|
||||
'request_id' => 'ChecksCoreWorkflow.validate_workflows',
|
||||
'class_name' => self.class.to_s,
|
||||
'screen' => screen,
|
||||
'params' => attributes
|
||||
}, user: User.find(UserInfo.current_user_id))
|
||||
|
||||
check_restrict_values(perform_result)
|
||||
check_visibility(perform_result)
|
||||
check_mandatory(perform_result)
|
||||
end
|
||||
|
||||
def check_restrict_values(perform_result)
|
||||
changes.each_key do |key|
|
||||
next if perform_result[:restrict_values][key].blank?
|
||||
next if self[key].blank?
|
||||
|
||||
value_found = perform_result[:restrict_values][key].any? { |value| value.to_s == self[key].to_s }
|
||||
next if value_found
|
||||
|
||||
raise Exceptions::UnprocessableEntity, "Invalid value '#{self[key]}' for field '#{key}'!"
|
||||
end
|
||||
end
|
||||
|
||||
def check_visibility(perform_result)
|
||||
perform_result[:visibility].each_key do |key|
|
||||
next if perform_result[:visibility][key] != 'remove'
|
||||
|
||||
self[key] = nil
|
||||
end
|
||||
end
|
||||
|
||||
def check_mandatory(perform_result)
|
||||
perform_result[:mandatory].each_key do |key|
|
||||
next if !perform_result[:mandatory][key]
|
||||
next if self[key].present?
|
||||
|
||||
raise Exceptions::UnprocessableEntity, "Missing required value for field '#{key}'!"
|
||||
end
|
||||
end
|
||||
end
|
29
app/models/core_workflow.rb
Normal file
29
app/models/core_workflow.rb
Normal file
|
@ -0,0 +1,29 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflow < ApplicationModel
|
||||
include ChecksClientNotification
|
||||
include CoreWorkflow::Assets
|
||||
|
||||
default_scope { order(priority: :asc, id: :asc) }
|
||||
scope :active, -> { where(active: true) }
|
||||
scope :changeable, -> { where(changeable: true) }
|
||||
scope :object, ->(object) { where(object: [object, nil]) }
|
||||
|
||||
store :preferences
|
||||
store :condition_saved
|
||||
store :condition_selected
|
||||
store :perform
|
||||
|
||||
validates :name, presence: true
|
||||
|
||||
def self.perform(payload:, user:, assets: {}, assets_in_result: true, result: {})
|
||||
CoreWorkflow::Result.new(payload: payload, user: user, assets: assets, assets_in_result: assets_in_result, result: result).run
|
||||
rescue => e
|
||||
return {} if e.is_a?(ArgumentError)
|
||||
raise e if !Rails.env.production?
|
||||
|
||||
Rails.logger.error 'Error performing Core Workflow engine.'
|
||||
Rails.logger.error e
|
||||
{}
|
||||
end
|
||||
end
|
41
app/models/core_workflow/assets.rb
Normal file
41
app/models/core_workflow/assets.rb
Normal file
|
@ -0,0 +1,41 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflow
|
||||
module Assets
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def assets(data)
|
||||
app_model_workflow = CoreWorkflow.to_app_model
|
||||
data[ app_model_workflow ] ||= {}
|
||||
|
||||
return data if data[ app_model_workflow ][ id ]
|
||||
|
||||
data = assets_object(data)
|
||||
assets_user(data)
|
||||
end
|
||||
end
|
||||
|
||||
def assets_object(data)
|
||||
app_model_workflow = CoreWorkflow.to_app_model
|
||||
data[ app_model_workflow ][ id ] = attributes_with_association_ids
|
||||
data = assets_of_selector('condition_selected', data)
|
||||
data = assets_of_selector('condition_saved', data)
|
||||
assets_of_selector('perform', data)
|
||||
end
|
||||
|
||||
def assets_user(data)
|
||||
app_model_user = User.to_app_model
|
||||
data[ app_model_user ] ||= {}
|
||||
|
||||
%w[created_by_id updated_by_id].each do |local_user_id|
|
||||
next if !self[ local_user_id ]
|
||||
next if data[ app_model_user ][ self[ local_user_id ] ]
|
||||
|
||||
user = User.lookup(id: self[ local_user_id ])
|
||||
next if !user
|
||||
|
||||
data = user.assets(data)
|
||||
end
|
||||
data
|
||||
end
|
||||
end
|
198
app/models/core_workflow/attributes.rb
Normal file
198
app/models/core_workflow/attributes.rb
Normal file
|
@ -0,0 +1,198 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
require 'digest/md5'
|
||||
|
||||
class CoreWorkflow::Attributes
|
||||
attr_accessor :user, :payload, :assets
|
||||
|
||||
def initialize(result_object:)
|
||||
@result_object = result_object
|
||||
@user = result_object.user
|
||||
@payload = result_object.payload
|
||||
@assets = result_object.assets
|
||||
end
|
||||
|
||||
def payload_class
|
||||
@payload['class_name'].constantize
|
||||
end
|
||||
|
||||
def selected_only
|
||||
|
||||
# params loading and preparing is very expensive so cache it
|
||||
checksum = Digest::MD5.hexdigest(Marshal.dump(@payload['params']))
|
||||
return @selected_only[checksum] if @selected_only.present? && @selected_only[checksum]
|
||||
|
||||
@selected_only = {}
|
||||
@selected_only[checksum] = begin
|
||||
clean_params = payload_class.association_name_to_id_convert(@payload['params'])
|
||||
clean_params = payload_class.param_cleanup(clean_params, true, false, false)
|
||||
payload_class.new(clean_params)
|
||||
end
|
||||
end
|
||||
|
||||
def overwrite_selected(result)
|
||||
selected_attributes = selected_only.attributes
|
||||
selected_attributes.each_key do |key|
|
||||
next if selected_attributes[key].nil?
|
||||
|
||||
result[key.to_sym] = selected_attributes[key]
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
def selected
|
||||
if @payload['params']['id'] && payload_class.exists?(id: @payload['params']['id'])
|
||||
result = saved_only
|
||||
overwrite_selected(result)
|
||||
else
|
||||
selected_only
|
||||
end
|
||||
end
|
||||
|
||||
def saved_only
|
||||
return if @payload['params']['id'].blank?
|
||||
|
||||
# dont use lookup here because the cache will not
|
||||
# know about new attributes and make crashes
|
||||
@saved_only ||= payload_class.find_by(id: @payload['params']['id'])
|
||||
end
|
||||
|
||||
def saved
|
||||
@saved ||= saved_only || payload_class.new
|
||||
end
|
||||
|
||||
def object_elements
|
||||
@object_elements ||= ObjectManager::Object.new(@payload['class_name']).attributes(@user, saved_only, data_only: false).each_with_object([]) do |element, result|
|
||||
result << element.data.merge(screens: element.screens)
|
||||
end
|
||||
end
|
||||
|
||||
def screen_value(attribute, type)
|
||||
attribute[:screens].dig(@payload['screen'], type)
|
||||
end
|
||||
|
||||
# dont cache this else the result object will work with references and cache bugs occur
|
||||
def shown_default
|
||||
object_elements.each_with_object({}) do |attribute, result|
|
||||
result[ attribute[:name] ] = if @payload['request_id'] == 'ChecksCoreWorkflow.validate_workflows'
|
||||
'show'
|
||||
else
|
||||
screen_value(attribute, 'shown') == false ? 'hide' : 'show'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# dont cache this else the result object will work with references and cache bugs occur
|
||||
def mandatory_default
|
||||
object_elements.each_with_object({}) do |attribute, result|
|
||||
result[ attribute[:name] ] = if @payload['request_id'] == 'ChecksCoreWorkflow.validate_workflows'
|
||||
false
|
||||
elsif screen_value(attribute, 'required').nil?
|
||||
!screen_value(attribute, 'null')
|
||||
else
|
||||
screen_value(attribute, 'required')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# dont cache this else the result object will work with references and cache bugs occur
|
||||
def auto_select_default
|
||||
object_elements.each_with_object({}) do |attribute, result|
|
||||
next if !attribute[:only_shown_if_selectable]
|
||||
|
||||
result[ attribute[:name] ] = true
|
||||
end
|
||||
end
|
||||
|
||||
def options_array(options)
|
||||
result = []
|
||||
|
||||
options.each do |option|
|
||||
result << option['value']
|
||||
if option['children'].present?
|
||||
result += options_array(option['children'])
|
||||
end
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def options_hash(options)
|
||||
options.keys
|
||||
end
|
||||
|
||||
def options_relation(attribute)
|
||||
key = "#{attribute[:relation]}_#{attribute[:name]}"
|
||||
@options_relation ||= {}
|
||||
@options_relation[key] ||= "CoreWorkflow::Attributes::#{attribute[:relation]}".constantize.new(attributes: self, attribute: attribute)
|
||||
@options_relation[key].values
|
||||
end
|
||||
|
||||
def attribute_filter?(attribute)
|
||||
screen_value(attribute, 'filter').present?
|
||||
end
|
||||
|
||||
def attribute_options_array?(attribute)
|
||||
attribute[:options].present? && attribute[:options].instance_of?(Array)
|
||||
end
|
||||
|
||||
def attribute_options_hash?(attribute)
|
||||
attribute[:options].present? && attribute[:options].instance_of?(Hash)
|
||||
end
|
||||
|
||||
def attribute_options_relation?(attribute)
|
||||
attribute[:relation].present?
|
||||
end
|
||||
|
||||
def values(attribute)
|
||||
values = nil
|
||||
if attribute_filter?(attribute)
|
||||
values = screen_value(attribute, 'filter')
|
||||
elsif attribute_options_array?(attribute)
|
||||
values = options_array(attribute[:options])
|
||||
elsif attribute_options_hash?(attribute)
|
||||
values = options_hash(attribute[:options])
|
||||
elsif attribute_options_relation?(attribute)
|
||||
values = options_relation(attribute)
|
||||
end
|
||||
values
|
||||
end
|
||||
|
||||
def values_empty(attribute, values)
|
||||
return values if values == ['']
|
||||
|
||||
saved_value = saved_attribute_value(attribute)
|
||||
if saved_value.present? && values.exclude?(saved_value)
|
||||
values |= Array(saved_value.to_s)
|
||||
end
|
||||
|
||||
if attribute[:nulloption] && values.exclude?('')
|
||||
values.unshift('')
|
||||
end
|
||||
|
||||
values
|
||||
end
|
||||
|
||||
def restrict_values_default
|
||||
result = {}
|
||||
object_elements.each do |attribute|
|
||||
values = values(attribute)
|
||||
next if values.blank?
|
||||
|
||||
values = values_empty(attribute, values)
|
||||
result[ attribute[:name] ] = values.map(&:to_s)
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
def saved_attribute_value(attribute)
|
||||
saved_attribute_value = saved_only&.try(attribute[:name])
|
||||
|
||||
# special case for owner_id
|
||||
if saved_only&.class == Ticket && attribute[:name] == 'owner_id' && saved_attribute_value == 1
|
||||
saved_attribute_value = nil
|
||||
end
|
||||
|
||||
saved_attribute_value
|
||||
end
|
||||
end
|
12
app/models/core_workflow/attributes/base.rb
Normal file
12
app/models/core_workflow/attributes/base.rb
Normal file
|
@ -0,0 +1,12 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflow::Attributes::Base
|
||||
def initialize(attributes:, attribute:)
|
||||
@attributes = attributes
|
||||
@attribute = attribute
|
||||
end
|
||||
|
||||
def values
|
||||
[]
|
||||
end
|
||||
end
|
7
app/models/core_workflow/attributes/email_address.rb
Normal file
7
app/models/core_workflow/attributes/email_address.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflow::Attributes::EmailAddress < CoreWorkflow::Attributes::Base
|
||||
def values
|
||||
@values ||= EmailAddress.all.pluck(:id)
|
||||
end
|
||||
end
|
33
app/models/core_workflow/attributes/group.rb
Normal file
33
app/models/core_workflow/attributes/group.rb
Normal file
|
@ -0,0 +1,33 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflow::Attributes::Group < CoreWorkflow::Attributes::Base
|
||||
def values
|
||||
groups.each do |group|
|
||||
assets(group)
|
||||
end
|
||||
|
||||
if groups.blank?
|
||||
['']
|
||||
else
|
||||
groups.pluck(:id)
|
||||
end
|
||||
end
|
||||
|
||||
def groups
|
||||
@groups ||= if @attributes.user.permissions?('ticket.agent')
|
||||
if @attributes.payload['screen'] == 'create_middle'
|
||||
@attributes.user.groups_access(%w[create])
|
||||
else
|
||||
@attributes.user.groups_access(%w[create change])
|
||||
end
|
||||
else
|
||||
Group.where(active: true)
|
||||
end
|
||||
end
|
||||
|
||||
def assets(group)
|
||||
return if @attributes.assets[Group.to_app_model] && @attributes.assets[Group.to_app_model][group.id]
|
||||
|
||||
@attributes.assets = group.assets(@attributes.assets)
|
||||
end
|
||||
end
|
4
app/models/core_workflow/attributes/organization.rb
Normal file
4
app/models/core_workflow/attributes/organization.rb
Normal file
|
@ -0,0 +1,4 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflow::Attributes::Organization < CoreWorkflow::Attributes::Base
|
||||
end
|
7
app/models/core_workflow/attributes/signature.rb
Normal file
7
app/models/core_workflow/attributes/signature.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflow::Attributes::Signature < CoreWorkflow::Attributes::Base
|
||||
def values
|
||||
@values ||= Signature.all.pluck(:id)
|
||||
end
|
||||
end
|
12
app/models/core_workflow/attributes/ticket_priority.rb
Normal file
12
app/models/core_workflow/attributes/ticket_priority.rb
Normal file
|
@ -0,0 +1,12 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflow::Attributes::TicketPriority < CoreWorkflow::Attributes::Base
|
||||
def values
|
||||
@values ||= begin
|
||||
Ticket::Priority.where(active: true).each_with_object([]) do |priority, priority_ids|
|
||||
@attributes.assets = priority.assets(@attributes.assets)
|
||||
priority_ids.push priority.id
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
36
app/models/core_workflow/attributes/ticket_state.rb
Normal file
36
app/models/core_workflow/attributes/ticket_state.rb
Normal file
|
@ -0,0 +1,36 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflow::Attributes::TicketState < CoreWorkflow::Attributes::Base
|
||||
def values
|
||||
@values ||= begin
|
||||
state_ids = []
|
||||
if state_type && state_types.exclude?(state_type.name)
|
||||
state_ids.push @attributes.saved.state_id
|
||||
end
|
||||
Ticket::State.joins(:state_type).where(ticket_state_types: { name: state_types }).each do |state|
|
||||
state_ids.push state.id
|
||||
assets(state)
|
||||
end
|
||||
state_ids
|
||||
end
|
||||
end
|
||||
|
||||
def state_type
|
||||
return if @attributes.saved.id.blank?
|
||||
|
||||
@attributes.saved.state.state_type
|
||||
end
|
||||
|
||||
def state_types
|
||||
state_types = ['open', 'closed', 'pending action', 'pending reminder']
|
||||
return state_types if @attributes.payload['screen'] != 'create_middle'
|
||||
|
||||
state_types.unshift('new')
|
||||
end
|
||||
|
||||
def assets(state)
|
||||
return if @attributes.assets[Ticket::State.to_app_model] && @attributes.assets[Ticket::State.to_app_model][state.id]
|
||||
|
||||
@attributes.assets = state.assets(@attributes.assets)
|
||||
end
|
||||
end
|
101
app/models/core_workflow/attributes/user.rb
Normal file
101
app/models/core_workflow/attributes/user.rb
Normal file
|
@ -0,0 +1,101 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflow::Attributes::User < CoreWorkflow::Attributes::Base
|
||||
|
||||
def values
|
||||
return ticket_owner_id_bulk if @attributes.payload['screen'] == 'overview_bulk'
|
||||
return ticket_owner_id if @attributes.payload['class_name'] == 'Ticket' && @attribute[:name] == 'owner_id'
|
||||
|
||||
[]
|
||||
end
|
||||
|
||||
def group_agent_user_ids(group_id)
|
||||
@group_agent_user_ids ||= {}
|
||||
@group_agent_user_ids[group_id] ||= User.joins(', groups_users').where("users.id = groups_users.user_id AND groups_users.access = 'full' AND groups_users.group_id = ? AND users.id IN (?)", group_id, agent_user_ids).pluck(:id)
|
||||
end
|
||||
|
||||
def group_agent_roles_ids(group_id)
|
||||
@group_agent_roles_ids ||= {}
|
||||
@group_agent_roles_ids[group_id] ||= Role.joins(', roles_groups').where("roles.id = roles_groups.role_id AND roles_groups.access = 'full' AND roles_groups.group_id = ? AND roles.id IN (?)", group_id, agent_role_ids).pluck(:id)
|
||||
end
|
||||
|
||||
def agent_user_ids
|
||||
@agent_user_ids ||= User.joins(:roles).where(users: { active: true }).where('roles_users.role_id' => agent_role_ids).pluck(:id)
|
||||
end
|
||||
|
||||
def agent_role_ids
|
||||
@agent_role_ids ||= Role.with_permissions('ticket.agent').pluck(:id)
|
||||
end
|
||||
|
||||
def group_agent_role_user_ids(group_id)
|
||||
@group_agent_role_user_ids ||= {}
|
||||
@group_agent_role_user_ids[group_id] ||= User.joins(:roles).where(roles: { id: group_agent_roles_ids(group_id) }).pluck(:id)
|
||||
end
|
||||
|
||||
def ticket_owner_id
|
||||
return [''] if @attributes.selected_only.group_id.blank?
|
||||
|
||||
group_owner_ids
|
||||
end
|
||||
|
||||
def group_owner_ids
|
||||
user_ids = []
|
||||
|
||||
# dont show system user in frontend but allow to reset it to 1 on update/create of the ticket
|
||||
if @attributes.payload['request_id'] == 'ChecksCoreWorkflow.validate_workflows'
|
||||
user_ids = [1]
|
||||
end
|
||||
|
||||
User.where(id: group_owner_ids_user_ids, active: true).each do |user|
|
||||
user_ids << user.id
|
||||
assets(user)
|
||||
end
|
||||
|
||||
user_ids
|
||||
end
|
||||
|
||||
def group_owner_ids_user_ids
|
||||
group_agent_user_ids(@attributes.selected.group_id).concat(group_agent_role_user_ids(@attributes.selected.group_id)).uniq
|
||||
end
|
||||
|
||||
def group_ids_bulk
|
||||
@group_ids_bulk ||= begin
|
||||
ticket_ids = String(@attributes.payload['params']['ticket_ids']).split(',').map(&:to_i)
|
||||
Ticket.distinct.where(id: ticket_ids).pluck(:group_id)
|
||||
end
|
||||
end
|
||||
|
||||
def group_users_bulk
|
||||
@group_users_bulk ||= begin
|
||||
group_users_bulk_user_count.keys.select { |user| group_users_bulk_user_count[user] == group_ids_bulk.count }
|
||||
end
|
||||
end
|
||||
|
||||
def group_users_bulk_user_count
|
||||
@group_users_bulk_user_count ||= begin
|
||||
user_count = {}
|
||||
group_ids_bulk.each do |group_id|
|
||||
User.where(id: group_agent_user_ids(group_id).concat(group_agent_role_user_ids(group_id)).uniq, active: true).each do |user|
|
||||
user_count[user] ||= 0
|
||||
user_count[user] += 1
|
||||
end
|
||||
end
|
||||
user_count
|
||||
end
|
||||
end
|
||||
|
||||
def ticket_owner_id_bulk
|
||||
return group_owner_ids if @attributes.selected.group_id.present?
|
||||
|
||||
return [''] if group_users_bulk.blank?
|
||||
|
||||
group_users_bulk.each { |user| assets(user) }
|
||||
group_users_bulk.map(&:id)
|
||||
end
|
||||
|
||||
def assets(user)
|
||||
return if @attributes.assets[User.to_app_model] && @attributes.assets[User.to_app_model][user.id]
|
||||
|
||||
@attributes.assets = user.assets(@attributes.assets)
|
||||
end
|
||||
end
|
105
app/models/core_workflow/condition.rb
Normal file
105
app/models/core_workflow/condition.rb
Normal file
|
@ -0,0 +1,105 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflow::Condition
|
||||
include ::Mixin::HasBackends
|
||||
|
||||
attr_accessor :user, :payload, :workflow, :attribute_object, :result_object, :check
|
||||
|
||||
def initialize(result_object:, workflow:)
|
||||
@user = result_object.user
|
||||
@payload = result_object.payload
|
||||
@workflow = workflow
|
||||
@attribute_object = result_object.attributes
|
||||
@result_object = result_object
|
||||
@check = nil
|
||||
end
|
||||
|
||||
def attributes
|
||||
@attribute_object.send(@check)
|
||||
end
|
||||
|
||||
def condition_key_value_object(key_split)
|
||||
case key_split[0]
|
||||
when 'session'
|
||||
key_split.shift
|
||||
obj = user
|
||||
when attributes.class.to_s.downcase
|
||||
key_split.shift
|
||||
obj = attributes
|
||||
else
|
||||
obj = attributes
|
||||
end
|
||||
obj
|
||||
end
|
||||
|
||||
def condition_key_value(key)
|
||||
return Array(key) if key == 'custom.module'
|
||||
|
||||
key_split = key.split('.')
|
||||
obj = condition_key_value_object(key_split)
|
||||
key_split.each do |attribute|
|
||||
if obj.instance_of?(User) && attribute =~ %r{^group_ids_(full|create|change|read|overview)$}
|
||||
obj = obj.group_ids_access($1)
|
||||
break
|
||||
end
|
||||
|
||||
obj = obj.try(attribute.to_sym)
|
||||
break if obj.blank?
|
||||
end
|
||||
|
||||
condition_value_result(obj)
|
||||
end
|
||||
|
||||
def condition_value_result(obj)
|
||||
Array(obj).map(&:to_s).map(&:html2text)
|
||||
end
|
||||
|
||||
def condition_value_match?(key, condition, value)
|
||||
"CoreWorkflow::Condition::#{condition['operator'].tr(' ', '_').camelize}".constantize&.new(condition_object: self, key: key, condition: condition, value: value)&.match
|
||||
end
|
||||
|
||||
def condition_match?(key, condition)
|
||||
value_key = condition_key_value(key)
|
||||
condition_value_match?(key, condition, value_key)
|
||||
end
|
||||
|
||||
def condition_attributes_match?(check)
|
||||
@check = check
|
||||
|
||||
condition = @workflow.send(:"condition_#{@check}")
|
||||
return true if condition.blank?
|
||||
|
||||
result = true
|
||||
condition.each do |key, value|
|
||||
next if condition_match?(key, value)
|
||||
|
||||
result = false
|
||||
|
||||
break
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def object_match?
|
||||
return true if @workflow.object.blank?
|
||||
|
||||
@workflow.object.include?(@payload['class_name'])
|
||||
end
|
||||
|
||||
def screen_match?
|
||||
return true if @workflow.preferences['screen'].blank?
|
||||
|
||||
Array(@workflow.preferences['screen']).include?(@payload['screen'])
|
||||
end
|
||||
|
||||
def match_all?
|
||||
return if !object_match?
|
||||
return if !screen_match?
|
||||
return if !condition_attributes_match?('saved')
|
||||
return if !condition_attributes_match?('selected')
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
end
|
24
app/models/core_workflow/condition/backend.rb
Normal file
24
app/models/core_workflow/condition/backend.rb
Normal file
|
@ -0,0 +1,24 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflow::Condition::Backend
|
||||
def initialize(condition_object:, key:, condition:, value:)
|
||||
@key = key
|
||||
@condition_object = condition_object
|
||||
@condition = condition
|
||||
@value = value
|
||||
end
|
||||
|
||||
attr_reader :value
|
||||
|
||||
def object?(object)
|
||||
@condition_object.attributes.instance_of?(object)
|
||||
end
|
||||
|
||||
def condition_value
|
||||
Array(@condition['value']).map(&:to_s)
|
||||
end
|
||||
|
||||
def match
|
||||
false
|
||||
end
|
||||
end
|
24
app/models/core_workflow/condition/contains.rb
Normal file
24
app/models/core_workflow/condition/contains.rb
Normal file
|
@ -0,0 +1,24 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflow::Condition::Contains < CoreWorkflow::Condition::Backend
|
||||
def match
|
||||
result = false
|
||||
value.each do |current_value|
|
||||
current_match = false
|
||||
condition_value.each do |current_condition_value|
|
||||
next if current_condition_value.exclude?(current_value)
|
||||
|
||||
current_match = true
|
||||
|
||||
break
|
||||
end
|
||||
|
||||
next if !current_match
|
||||
|
||||
result = true
|
||||
|
||||
break
|
||||
end
|
||||
result
|
||||
end
|
||||
end
|
22
app/models/core_workflow/condition/contains_all.rb
Normal file
22
app/models/core_workflow/condition/contains_all.rb
Normal file
|
@ -0,0 +1,22 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflow::Condition::ContainsAll < CoreWorkflow::Condition::Backend
|
||||
def match
|
||||
result = false
|
||||
value.each do |current_value|
|
||||
current_match = 0
|
||||
condition_value.each do |current_condition_value|
|
||||
next if current_condition_value.exclude?(current_value)
|
||||
|
||||
current_match += 1
|
||||
end
|
||||
|
||||
next if current_match != condition_value.count
|
||||
|
||||
result = true
|
||||
|
||||
break
|
||||
end
|
||||
result
|
||||
end
|
||||
end
|
24
app/models/core_workflow/condition/contains_all_not.rb
Normal file
24
app/models/core_workflow/condition/contains_all_not.rb
Normal file
|
@ -0,0 +1,24 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflow::Condition::ContainsAllNot < CoreWorkflow::Condition::Backend
|
||||
def match
|
||||
return true if value.blank?
|
||||
|
||||
result = false
|
||||
value.each do |current_value|
|
||||
current_match = 0
|
||||
condition_value.each do |current_condition_value|
|
||||
next if current_condition_value.include?(current_value)
|
||||
|
||||
current_match += 1
|
||||
end
|
||||
|
||||
next if current_match != condition_value.count
|
||||
|
||||
result = true
|
||||
|
||||
break
|
||||
end
|
||||
result
|
||||
end
|
||||
end
|
26
app/models/core_workflow/condition/contains_not.rb
Normal file
26
app/models/core_workflow/condition/contains_not.rb
Normal file
|
@ -0,0 +1,26 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflow::Condition::ContainsNot < CoreWorkflow::Condition::Backend
|
||||
def match
|
||||
return true if value.blank?
|
||||
|
||||
result = false
|
||||
value.each do |current_value|
|
||||
current_match = false
|
||||
condition_value.each do |current_condition_value|
|
||||
next if current_condition_value.include?(current_value)
|
||||
|
||||
current_match = true
|
||||
|
||||
break
|
||||
end
|
||||
|
||||
next if !current_match
|
||||
|
||||
result = true
|
||||
|
||||
break
|
||||
end
|
||||
result
|
||||
end
|
||||
end
|
15
app/models/core_workflow/condition/is.rb
Normal file
15
app/models/core_workflow/condition/is.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflow::Condition::Is < CoreWorkflow::Condition::Backend
|
||||
def match
|
||||
result = false
|
||||
value.each do |current_value|
|
||||
next if condition_value.exclude?(current_value)
|
||||
|
||||
result = true
|
||||
|
||||
break
|
||||
end
|
||||
result
|
||||
end
|
||||
end
|
17
app/models/core_workflow/condition/is_not.rb
Normal file
17
app/models/core_workflow/condition/is_not.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflow::Condition::IsNot < CoreWorkflow::Condition::Backend
|
||||
def match
|
||||
return true if value.blank?
|
||||
|
||||
result = false
|
||||
value.each do |current_value|
|
||||
next if condition_value.include?(current_value)
|
||||
|
||||
result = true
|
||||
|
||||
break
|
||||
end
|
||||
result
|
||||
end
|
||||
end
|
11
app/models/core_workflow/condition/is_set.rb
Normal file
11
app/models/core_workflow/condition/is_set.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflow::Condition::IsSet < CoreWorkflow::Condition::Backend
|
||||
def match
|
||||
return false if object?(Ticket) && @key == 'ticket.owner_id' && value == ['1']
|
||||
return false if value == ['']
|
||||
return true if value.present?
|
||||
|
||||
false
|
||||
end
|
||||
end
|
27
app/models/core_workflow/condition/match_all_modules.rb
Normal file
27
app/models/core_workflow/condition/match_all_modules.rb
Normal file
|
@ -0,0 +1,27 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflow::Condition::MatchAllModules < CoreWorkflow::Condition::Backend
|
||||
def match
|
||||
return true if condition_value.blank?
|
||||
|
||||
result = false
|
||||
value.each do |_current_value|
|
||||
current_match = 0
|
||||
condition_value.each do |current_condition_value|
|
||||
custom_module = current_condition_value.constantize.new(condition_object: @condition_object, result_object: @result_object)
|
||||
|
||||
check = custom_module.send(:"#{@condition_object.check}_attribute_match?")
|
||||
next if !check
|
||||
|
||||
current_match += 1
|
||||
end
|
||||
|
||||
next if current_match != condition_value.count
|
||||
|
||||
result = true
|
||||
|
||||
break
|
||||
end
|
||||
result
|
||||
end
|
||||
end
|
20
app/models/core_workflow/condition/match_no_modules.rb
Normal file
20
app/models/core_workflow/condition/match_no_modules.rb
Normal file
|
@ -0,0 +1,20 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflow::Condition::MatchNoModules < CoreWorkflow::Condition::Backend
|
||||
def match
|
||||
result = true
|
||||
value.each do |_current_value|
|
||||
condition_value.each do |current_condition_value|
|
||||
custom_module = current_condition_value.constantize.new(condition_object: @condition_object, result_object: @result_object)
|
||||
|
||||
check = custom_module.send(:"#{@condition_object.check}_attribute_match?")
|
||||
next if !check
|
||||
|
||||
result = false
|
||||
|
||||
break
|
||||
end
|
||||
end
|
||||
result
|
||||
end
|
||||
end
|
20
app/models/core_workflow/condition/match_one_module.rb
Normal file
20
app/models/core_workflow/condition/match_one_module.rb
Normal file
|
@ -0,0 +1,20 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflow::Condition::MatchOneModule < CoreWorkflow::Condition::Backend
|
||||
def match
|
||||
return true if condition_value.blank?
|
||||
|
||||
result = false
|
||||
value.each do |_current_value|
|
||||
condition_value.each do |current_condition_value|
|
||||
custom_module = current_condition_value.constantize.new(condition_object: @condition_object, result_object: @result_object)
|
||||
|
||||
result = custom_module.send(:"#{@condition_object.check}_attribute_match?")
|
||||
next if !result
|
||||
|
||||
break
|
||||
end
|
||||
end
|
||||
result
|
||||
end
|
||||
end
|
11
app/models/core_workflow/condition/not_set.rb
Normal file
11
app/models/core_workflow/condition/not_set.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflow::Condition::NotSet < CoreWorkflow::Condition::Backend
|
||||
def match
|
||||
return true if value.blank?
|
||||
return true if value == ['']
|
||||
return true if object?(Ticket) && @key == 'ticket.owner_id' && value == ['1']
|
||||
|
||||
false
|
||||
end
|
||||
end
|
24
app/models/core_workflow/condition/regex_match.rb
Normal file
24
app/models/core_workflow/condition/regex_match.rb
Normal file
|
@ -0,0 +1,24 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflow::Condition::RegexMatch < CoreWorkflow::Condition::Backend
|
||||
def match
|
||||
result = false
|
||||
value.each do |current_value|
|
||||
current_match = false
|
||||
condition_value.each do |current_condition_value|
|
||||
next if !%r{#{current_condition_value}}.match?(current_value)
|
||||
|
||||
current_match = true
|
||||
|
||||
break
|
||||
end
|
||||
|
||||
next if !current_match
|
||||
|
||||
result = true
|
||||
|
||||
break
|
||||
end
|
||||
result
|
||||
end
|
||||
end
|
26
app/models/core_workflow/condition/regex_mismatch.rb
Normal file
26
app/models/core_workflow/condition/regex_mismatch.rb
Normal file
|
@ -0,0 +1,26 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflow::Condition::RegexMismatch < CoreWorkflow::Condition::Backend
|
||||
def match
|
||||
return true if value.blank?
|
||||
|
||||
result = false
|
||||
value.each do |current_value|
|
||||
current_match = false
|
||||
condition_value.each do |current_condition_value|
|
||||
next if %r{#{current_condition_value}}.match?(current_value)
|
||||
|
||||
current_match = true
|
||||
|
||||
break
|
||||
end
|
||||
|
||||
next if !current_match
|
||||
|
||||
result = true
|
||||
|
||||
break
|
||||
end
|
||||
result
|
||||
end
|
||||
end
|
9
app/models/core_workflow/custom.rb
Normal file
9
app/models/core_workflow/custom.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflow::Custom
|
||||
include ::Mixin::HasBackends
|
||||
|
||||
def self.list
|
||||
backends.map(&:to_s)
|
||||
end
|
||||
end
|
37
app/models/core_workflow/custom/admin_core_workflow.rb
Normal file
37
app/models/core_workflow/custom/admin_core_workflow.rb
Normal file
|
@ -0,0 +1,37 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflow::Custom::AdminCoreWorkflow < CoreWorkflow::Custom::Backend
|
||||
def saved_attribute_match?
|
||||
object?(CoreWorkflow)
|
||||
end
|
||||
|
||||
def selected_attribute_match?
|
||||
object?(CoreWorkflow)
|
||||
end
|
||||
|
||||
def perform
|
||||
perform_object_defaults
|
||||
perform_screen_by_object
|
||||
end
|
||||
|
||||
def perform_object_defaults
|
||||
result('set_fixed_to', 'object', ['', 'Ticket', 'Organization', 'User', 'Group'])
|
||||
end
|
||||
|
||||
def perform_screen_by_object
|
||||
if selected.object.blank?
|
||||
result('set_fixed_to', 'preferences::screen', [''])
|
||||
return
|
||||
end
|
||||
|
||||
result('set_fixed_to', 'preferences::screen', screens_by_object.uniq)
|
||||
end
|
||||
|
||||
def screens_by_object
|
||||
result = []
|
||||
ObjectManager::Object.new(selected.object).attributes(@condition_object.user).each do |field|
|
||||
result += field[:screen].keys
|
||||
end
|
||||
result
|
||||
end
|
||||
end
|
37
app/models/core_workflow/custom/admin_sla.rb
Normal file
37
app/models/core_workflow/custom/admin_sla.rb
Normal file
|
@ -0,0 +1,37 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflow::Custom::AdminSla < CoreWorkflow::Custom::Backend
|
||||
def saved_attribute_match?
|
||||
object?(Sla)
|
||||
end
|
||||
|
||||
def selected_attribute_match?
|
||||
object?(Sla)
|
||||
end
|
||||
|
||||
def first_response_time_enabled
|
||||
return 'set_mandatory' if params['first_response_time_enabled'].present?
|
||||
|
||||
'set_optional'
|
||||
end
|
||||
|
||||
def update_time_enabled
|
||||
return 'set_mandatory' if params['update_time_enabled'].present?
|
||||
|
||||
'set_optional'
|
||||
end
|
||||
|
||||
def solution_time_enabled
|
||||
return 'set_mandatory' if params['solution_time_enabled'].present?
|
||||
|
||||
'set_optional'
|
||||
end
|
||||
|
||||
def perform
|
||||
|
||||
# make fields mandatory if checkbox is checked
|
||||
result(first_response_time_enabled, 'first_response_time_in_text')
|
||||
result(update_time_enabled, 'update_time_in_text')
|
||||
result(solution_time_enabled, 'solution_time_in_text')
|
||||
end
|
||||
end
|
46
app/models/core_workflow/custom/backend.rb
Normal file
46
app/models/core_workflow/custom/backend.rb
Normal file
|
@ -0,0 +1,46 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflow::Custom::Backend
|
||||
def initialize(condition_object:, result_object:)
|
||||
@condition_object = condition_object
|
||||
@result_object = result_object
|
||||
end
|
||||
|
||||
def saved_attribute_match?
|
||||
false
|
||||
end
|
||||
|
||||
def selected_attribute_match?
|
||||
false
|
||||
end
|
||||
|
||||
def perform; end
|
||||
|
||||
def object?(object)
|
||||
@condition_object.attributes.instance_of?(object)
|
||||
end
|
||||
|
||||
def selected
|
||||
@condition_object.attribute_object.selected
|
||||
end
|
||||
|
||||
def selected_only
|
||||
@condition_object.attribute_object.selected_only
|
||||
end
|
||||
|
||||
def saved
|
||||
@condition_object.attribute_object.saved
|
||||
end
|
||||
|
||||
def saved_only
|
||||
@condition_object.attribute_object.saved_only
|
||||
end
|
||||
|
||||
def params
|
||||
@condition_object.payload['params']
|
||||
end
|
||||
|
||||
def result(backend, field, value = nil)
|
||||
@result_object.run_backend_value(backend, field, value)
|
||||
end
|
||||
end
|
32
app/models/core_workflow/custom/pending_time.rb
Normal file
32
app/models/core_workflow/custom/pending_time.rb
Normal file
|
@ -0,0 +1,32 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflow::Custom::PendingTime < CoreWorkflow::Custom::Backend
|
||||
def saved_attribute_match?
|
||||
object?(Ticket)
|
||||
end
|
||||
|
||||
def selected_attribute_match?
|
||||
object?(Ticket)
|
||||
end
|
||||
|
||||
def perform
|
||||
result(visibility, 'pending_time')
|
||||
result(mandatory, 'pending_time')
|
||||
end
|
||||
|
||||
def visibility
|
||||
return 'show' if pending?
|
||||
|
||||
'remove'
|
||||
end
|
||||
|
||||
def mandatory
|
||||
return 'set_mandatory' if pending?
|
||||
|
||||
'set_optional'
|
||||
end
|
||||
|
||||
def pending?
|
||||
['pending reminder', 'pending action'].include?(selected&.state&.state_type&.name)
|
||||
end
|
||||
end
|
138
app/models/core_workflow/result.rb
Normal file
138
app/models/core_workflow/result.rb
Normal file
|
@ -0,0 +1,138 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflow::Result
|
||||
include ::Mixin::HasBackends
|
||||
|
||||
attr_accessor :payload, :user, :assets, :assets_in_result, :result, :rerun
|
||||
|
||||
def initialize(payload:, user:, assets: {}, assets_in_result: true, result: {})
|
||||
raise ArgumentError, 'No payload->class_name given!' if !payload['class_name']
|
||||
raise ArgumentError, 'No payload->screen given!' if !payload['screen']
|
||||
|
||||
@payload = payload
|
||||
@user = user
|
||||
@assets = assets
|
||||
@assets_in_result = assets_in_result
|
||||
@result = result
|
||||
@rerun = false
|
||||
end
|
||||
|
||||
def attributes
|
||||
@attributes ||= CoreWorkflow::Attributes.new(result_object: self)
|
||||
end
|
||||
|
||||
def workflows
|
||||
CoreWorkflow.active.object(payload['class_name'])
|
||||
end
|
||||
|
||||
def set_default
|
||||
@rerun = false
|
||||
|
||||
@result = {
|
||||
request_id: payload['request_id'],
|
||||
restrict_values: {},
|
||||
visibility: attributes.shown_default,
|
||||
mandatory: attributes.mandatory_default,
|
||||
select: @result[:select] || {},
|
||||
fill_in: @result[:fill_in] || {},
|
||||
eval: [],
|
||||
matched_workflows: @result[:matched_workflows] || [],
|
||||
rerun_count: @result[:rerun_count] || 0,
|
||||
}
|
||||
|
||||
# restrict init defaults to make sure param values to removed if not allowed
|
||||
attributes.restrict_values_default.each do |field, values|
|
||||
run_backend_value('set_fixed_to', field, values)
|
||||
end
|
||||
|
||||
set_default_only_shown_if_selectable
|
||||
end
|
||||
|
||||
def set_default_only_shown_if_selectable
|
||||
|
||||
# only_shown_if_selectable should not work on bulk feature
|
||||
return if @payload['screen'] == 'overview_bulk'
|
||||
|
||||
auto_hide = {}
|
||||
attributes.auto_select_default.each do |field, state|
|
||||
result = run_backend_value('auto_select', field, state)
|
||||
next if result.compact.blank?
|
||||
|
||||
auto_hide[field] = true
|
||||
end
|
||||
|
||||
auto_hide.each do |field, state|
|
||||
run_backend_value('hide', field, state)
|
||||
end
|
||||
end
|
||||
|
||||
def run
|
||||
set_default
|
||||
|
||||
workflows.each do |workflow|
|
||||
condition = CoreWorkflow::Condition.new(result_object: self, workflow: workflow)
|
||||
next if !condition.match_all?
|
||||
|
||||
run_workflow(workflow)
|
||||
run_custom(workflow, condition)
|
||||
match_workflow(workflow)
|
||||
|
||||
break if workflow.stop_after_match
|
||||
end
|
||||
|
||||
consider_rerun
|
||||
end
|
||||
|
||||
def run_workflow(workflow)
|
||||
Array(workflow.perform).each do |field, config|
|
||||
run_backend(field, config)
|
||||
end
|
||||
end
|
||||
|
||||
def run_custom(workflow, condition)
|
||||
Array(workflow.perform.dig('custom.module', 'execute')).each do |module_path|
|
||||
custom_module = module_path.constantize.new(condition_object: condition, result_object: self)
|
||||
custom_module.perform
|
||||
end
|
||||
end
|
||||
|
||||
def run_backend(field, perform_config)
|
||||
result = []
|
||||
Array(perform_config['operator']).each do |backend|
|
||||
result << "CoreWorkflow::Result::#{backend.classify}".constantize.new(result_object: self, field: field, perform_config: perform_config).run
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
def run_backend_value(backend, field, value)
|
||||
perform_config = {
|
||||
'operator' => backend,
|
||||
backend => value,
|
||||
}
|
||||
|
||||
run_backend(field, perform_config)
|
||||
end
|
||||
|
||||
def match_workflow(workflow)
|
||||
@result[:matched_workflows] |= Array(workflow.id)
|
||||
end
|
||||
|
||||
def assets_in_result?
|
||||
return false if !@assets_in_result
|
||||
|
||||
@result[:assets] = assets
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def consider_rerun
|
||||
if @rerun && @result[:rerun_count] < 25
|
||||
@result[:rerun_count] += 1
|
||||
return run
|
||||
end
|
||||
|
||||
assets_in_result?
|
||||
|
||||
@result
|
||||
end
|
||||
end
|
8
app/models/core_workflow/result/add_option.rb
Normal file
8
app/models/core_workflow/result/add_option.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflow::Result::AddOption < CoreWorkflow::Result::BaseOption
|
||||
def run
|
||||
@result_object.result[:restrict_values][field] |= Array(@perform_config['add_option'])
|
||||
true
|
||||
end
|
||||
end
|
26
app/models/core_workflow/result/auto_select.rb
Normal file
26
app/models/core_workflow/result/auto_select.rb
Normal file
|
@ -0,0 +1,26 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflow::Result::AutoSelect < CoreWorkflow::Result::Backend
|
||||
def run
|
||||
return true if params_set? && !too_many_values?
|
||||
return if params_set?
|
||||
return if too_many_values?
|
||||
|
||||
@result_object.result[:select][field] = last_value
|
||||
@result_object.payload['params'][field] = last_value
|
||||
set_rerun
|
||||
true
|
||||
end
|
||||
|
||||
def last_value
|
||||
@result_object.result[:restrict_values][field].last
|
||||
end
|
||||
|
||||
def params_set?
|
||||
@result_object.payload['params'][field] == last_value
|
||||
end
|
||||
|
||||
def too_many_values?
|
||||
@result_object.result[:restrict_values][field].count { |v| v != '' } != 1
|
||||
end
|
||||
end
|
21
app/models/core_workflow/result/backend.rb
Normal file
21
app/models/core_workflow/result/backend.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflow::Result::Backend
|
||||
def initialize(result_object:, field:, perform_config:)
|
||||
@result_object = result_object
|
||||
@field = field
|
||||
@perform_config = perform_config
|
||||
end
|
||||
|
||||
def field
|
||||
@field.sub(%r{.*\.}, '')
|
||||
end
|
||||
|
||||
def set_rerun
|
||||
@result_object.rerun = true
|
||||
end
|
||||
|
||||
def result(backend, field, value = nil)
|
||||
@result_object.run_backend_value(backend, field, value)
|
||||
end
|
||||
end
|
36
app/models/core_workflow/result/base_option.rb
Normal file
36
app/models/core_workflow/result/base_option.rb
Normal file
|
@ -0,0 +1,36 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflow::Result::BaseOption < CoreWorkflow::Result::Backend
|
||||
def remove_excluded_param_values
|
||||
return if skip?
|
||||
|
||||
if @result_object.payload['params'][field].is_a?(Array)
|
||||
remove_array
|
||||
elsif excluded_by_restrict_values?(@result_object.payload['params'][field])
|
||||
remove_string
|
||||
end
|
||||
end
|
||||
|
||||
def skip?
|
||||
@result_object.payload['params'][field].blank?
|
||||
end
|
||||
|
||||
def remove_array
|
||||
@result_object.payload['params'][field] = @result_object.payload['params'][field].reject do |v|
|
||||
excluded = excluded_by_restrict_values?(v)
|
||||
if excluded
|
||||
set_rerun
|
||||
end
|
||||
excluded
|
||||
end
|
||||
end
|
||||
|
||||
def remove_string
|
||||
@result_object.payload['params'][field] = nil
|
||||
set_rerun
|
||||
end
|
||||
|
||||
def excluded_by_restrict_values?(value)
|
||||
@result_object.result[:restrict_values][field].exclude?(value.to_s)
|
||||
end
|
||||
end
|
32
app/models/core_workflow/result/fill_in.rb
Normal file
32
app/models/core_workflow/result/fill_in.rb
Normal file
|
@ -0,0 +1,32 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflow::Result::FillIn < CoreWorkflow::Result::Backend
|
||||
def run
|
||||
return if skip?
|
||||
|
||||
@result_object.result[:fill_in][field] = fill_in_value
|
||||
@result_object.payload['params'][field] = @result_object.result[:fill_in][field]
|
||||
set_rerun
|
||||
true
|
||||
end
|
||||
|
||||
def skip?
|
||||
return true if fill_in_value.blank?
|
||||
return true if params_set?
|
||||
return true if fill_in_set?
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
def fill_in_value
|
||||
@perform_config['fill_in']
|
||||
end
|
||||
|
||||
def params_set?
|
||||
@result_object.payload['params'][field] && fill_in_value == @result_object.payload['params'][field]
|
||||
end
|
||||
|
||||
def fill_in_set?
|
||||
@result_object.result[:fill_in][field] && fill_in_value == @result_object.result[:fill_in][field]
|
||||
end
|
||||
end
|
32
app/models/core_workflow/result/fill_in_empty.rb
Normal file
32
app/models/core_workflow/result/fill_in_empty.rb
Normal file
|
@ -0,0 +1,32 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflow::Result::FillInEmpty < CoreWorkflow::Result::Backend
|
||||
def run
|
||||
return if skip?
|
||||
|
||||
@result_object.result[:fill_in][field] = fill_in_value
|
||||
@result_object.payload['params'][field] = @result_object.result[:fill_in][field]
|
||||
set_rerun
|
||||
true
|
||||
end
|
||||
|
||||
def skip?
|
||||
return true if fill_in_value.blank?
|
||||
return true if params_set?
|
||||
return true if fill_in_set?
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
def fill_in_value
|
||||
@perform_config['fill_in_empty']
|
||||
end
|
||||
|
||||
def params_set?
|
||||
@result_object.payload['params'][field].present?
|
||||
end
|
||||
|
||||
def fill_in_set?
|
||||
@result_object.result[:fill_in][field]
|
||||
end
|
||||
end
|
8
app/models/core_workflow/result/hide.rb
Normal file
8
app/models/core_workflow/result/hide.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflow::Result::Hide < CoreWorkflow::Result::Backend
|
||||
def run
|
||||
@result_object.result[:visibility][field] = 'hide'
|
||||
true
|
||||
end
|
||||
end
|
8
app/models/core_workflow/result/remove.rb
Normal file
8
app/models/core_workflow/result/remove.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflow::Result::Remove < CoreWorkflow::Result::Backend
|
||||
def run
|
||||
@result_object.result[:visibility][field] = 'remove'
|
||||
true
|
||||
end
|
||||
end
|
10
app/models/core_workflow/result/remove_option.rb
Normal file
10
app/models/core_workflow/result/remove_option.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflow::Result::RemoveOption < CoreWorkflow::Result::BaseOption
|
||||
def run
|
||||
@result_object.result[:restrict_values][field] ||= Array(@result_object.payload['params'][field])
|
||||
@result_object.result[:restrict_values][field] -= Array(@perform_config['remove_option'])
|
||||
remove_excluded_param_values
|
||||
true
|
||||
end
|
||||
end
|
32
app/models/core_workflow/result/select.rb
Normal file
32
app/models/core_workflow/result/select.rb
Normal file
|
@ -0,0 +1,32 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflow::Result::Select < CoreWorkflow::Result::Backend
|
||||
def run
|
||||
return if skip?
|
||||
|
||||
@result_object.result[:select][field] = select_value
|
||||
@result_object.payload['params'][field] = @result_object.result[:select][field]
|
||||
set_rerun
|
||||
true
|
||||
end
|
||||
|
||||
def skip?
|
||||
return true if select_value.blank?
|
||||
return true if params_set?
|
||||
return true if select_set?
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
def select_value
|
||||
@select_value ||= Array(@perform_config['select']).reject { |v| @result_object.result[:restrict_values][field].exclude?(v) }.first
|
||||
end
|
||||
|
||||
def params_set?
|
||||
@result_object.payload['params'][field] && select_value == @result_object.payload['params'][field]
|
||||
end
|
||||
|
||||
def select_set?
|
||||
@result_object.result[:select][field] && select_value == @result_object.result[:select][field]
|
||||
end
|
||||
end
|
25
app/models/core_workflow/result/set_fixed_to.rb
Normal file
25
app/models/core_workflow/result/set_fixed_to.rb
Normal file
|
@ -0,0 +1,25 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflow::Result::SetFixedTo < CoreWorkflow::Result::BaseOption
|
||||
def run
|
||||
@result_object.result[:restrict_values][field] = if restriction_set?
|
||||
restrict_values
|
||||
else
|
||||
replace_values
|
||||
end
|
||||
remove_excluded_param_values
|
||||
true
|
||||
end
|
||||
|
||||
def restriction_set?
|
||||
@result_object.result[:restrict_values][field]
|
||||
end
|
||||
|
||||
def restrict_values
|
||||
@result_object.result[:restrict_values][field].reject { |v| Array(@perform_config['set_fixed_to']).exclude?(v) }
|
||||
end
|
||||
|
||||
def replace_values
|
||||
Array(@perform_config['set_fixed_to'])
|
||||
end
|
||||
end
|
8
app/models/core_workflow/result/set_mandatory.rb
Normal file
8
app/models/core_workflow/result/set_mandatory.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflow::Result::SetMandatory < CoreWorkflow::Result::Backend
|
||||
def run
|
||||
@result_object.result[:mandatory][field] = true
|
||||
true
|
||||
end
|
||||
end
|
8
app/models/core_workflow/result/set_optional.rb
Normal file
8
app/models/core_workflow/result/set_optional.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflow::Result::SetOptional < CoreWorkflow::Result::Backend
|
||||
def run
|
||||
@result_object.result[:mandatory][field] = false
|
||||
true
|
||||
end
|
||||
end
|
8
app/models/core_workflow/result/show.rb
Normal file
8
app/models/core_workflow/result/show.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflow::Result::Show < CoreWorkflow::Result::Backend
|
||||
def run
|
||||
@result_object.result[:visibility][field] = 'show'
|
||||
true
|
||||
end
|
||||
end
|
|
@ -4,6 +4,7 @@ class Group < ApplicationModel
|
|||
include CanBeImported
|
||||
include HasActivityStreamLog
|
||||
include ChecksClientNotification
|
||||
include ChecksCoreWorkflow
|
||||
include ChecksHtmlSanitized
|
||||
include ChecksLatestChangeObserved
|
||||
include HasHistory
|
||||
|
|
|
@ -24,7 +24,7 @@ returns:
|
|||
|
||||
=end
|
||||
|
||||
def attributes(user, record = nil)
|
||||
def attributes(user, record = nil, data_only: true)
|
||||
@attributes ||= begin
|
||||
attribute_records.each_with_object([]) do |attribute_record, result|
|
||||
|
||||
|
@ -36,7 +36,11 @@ returns:
|
|||
|
||||
next if !element.visible?
|
||||
|
||||
if data_only
|
||||
result.push element.data
|
||||
else
|
||||
result.push element
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
class Organization < ApplicationModel
|
||||
include HasActivityStreamLog
|
||||
include ChecksClientNotification
|
||||
include ChecksCoreWorkflow
|
||||
include ChecksLatestChangeObserved
|
||||
include HasHistory
|
||||
include HasSearchIndexBackend
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue