Init version of object manager.

This commit is contained in:
Martin Edenhofer 2016-05-19 10:20:38 +02:00
parent b00df72760
commit 1248f4080b
42 changed files with 2435 additions and 759 deletions

View file

@ -204,6 +204,8 @@ class App.ControllerForm extends App.Controller
attribute.autofocus = 'autofocus' attribute.autofocus = 'autofocus'
# set required option # set required option
if attribute.required is true
attribute.null = false
if !attribute.null if !attribute.null
attribute.required = 'required' attribute.required = 'required'
else else
@ -231,7 +233,7 @@ class App.ControllerForm extends App.Controller
# check if we have a references # check if we have a references
parts = attribute.name.split '::' parts = attribute.name.split '::'
if parts[0] && parts[1] if parts[0] && parts[1]
if @params[ parts[0] ] && @params[ parts[0] ][ parts[1] ] if @params[ parts[0] ] && parts[1] of @params[ parts[0] ]
attribute.value = @params[ parts[0] ][ parts[1] ] attribute.value = @params[ parts[0] ][ parts[1] ]
# set params value to default # set params value to default
@ -480,15 +482,22 @@ class App.ControllerForm extends App.Controller
for key of param for key of param
parts = key.split '::' parts = key.split '::'
if parts[0] && parts[1] if parts[0] && parts[1]
if !(parts[0] of inputSelectObject) if parts[1] && !inputSelectObject[ parts[0] ]
inputSelectObject[ parts[0] ] = {} inputSelectObject[ parts[0] ] = {}
if !parts[2] if parts[2] && !inputSelectObject[ parts[0] ][ parts[1] ]
inputSelectObject[ parts[0] ][ parts[1] ] = param[ key ] inputSelectObject[ parts[0] ][ parts[1] ] = {}
else if parts[3] && !inputSelectObject[ parts[0] ][ parts[1] ][ parts[2] ]
if !(parts[1] of inputSelectObject[ parts[0] ]) inputSelectObject[ parts[0] ][ parts[1] ][ parts[2] ] = {}
inputSelectObject[ parts[0] ][ parts[1] ] = {}
if parts[3]
inputSelectObject[ parts[0] ][ parts[1] ][ parts[2] ][ parts[3] ] = param[ key ]
delete param[ key ]
else if parts[2]
inputSelectObject[ parts[0] ][ parts[1] ][ parts[2] ] = param[ key ] inputSelectObject[ parts[0] ][ parts[1] ][ parts[2] ] = param[ key ]
delete param[ key ] delete param[ key ]
else if parts[1]
inputSelectObject[ parts[0] ][ parts[1] ] = param[ key ]
delete param[ key ]
# set new object params # set new object params
for key of inputSelectObject for key of inputSelectObject

View file

@ -40,7 +40,6 @@ class App.DashboardFirstSteps extends App.Controller
#container: @el.closest('.content') #container: @el.closest('.content')
head: 'Invite Colleagues' head: 'Invite Colleagues'
screen: 'invite_agent' screen: 'invite_agent'
role: 'Agent'
) )
inviteCustomer: (e) -> inviteCustomer: (e) ->

View file

@ -4,7 +4,7 @@ class App.UiElement.active extends App.UiElement.ApplicationUiElement
# set attributes # set attributes
attribute.null = false attribute.null = false
attribute.translation = true attribute.translate = true
# build options list # build options list
attribute.options = [ attribute.options = [

View file

@ -8,6 +8,7 @@ class App.UiElement.boolean extends App.UiElement.ApplicationUiElement
{ name: 'yes', value: true } { name: 'yes', value: true }
{ name: 'no', value: false } { name: 'no', value: false }
] ]
attribute.translate = true
# set data type # set data type
if attribute.name if attribute.name

View file

@ -0,0 +1,304 @@
# coffeelint: disable=camel_case_classes
class App.UiElement.object_manager_attribute extends App.UiElement.ApplicationUiElement
@render: (attribute, params = {}) ->
item = $(App.view('object_manager/attribute')(attribute: attribute))
updateDataMap = (localParams, localAttribute, localAttributes, localClassname, localForm, localA) =>
localItem = localForm.closest('.js-data')
values = []
values = {a: 123, b: 'aaa'}
element = $(App.view("object_manager/attribute/#{localParams.data_type}")(
attribute: attribute
values: values
))
@[localParams.data_type](element, localParams, params)
localItem.find('.js-dataMap').html(element)
localItem.find('.js-dataScreens').html(@dataScreens(attribute, localParams, params))
options =
datetime: 'Datetime'
date: 'Date'
input: 'Text'
# select: 'Select'
# boolean: 'Boolean'
# integer: 'Integer'
# autocompletion: 'Autocompletion (AJAX remote URL)'
configureAttributes = [
{ name: attribute.name, display: '', tag: 'select', null: false, options: options, translate: true, default: 'input', disabled: attribute.disabled },
]
dataType = new App.ControllerForm(
model:
configure_attributes: configureAttributes
noFieldset: true
handlers: [
updateDataMap,
]
params: params
)
item.find('.js-dataType').html(dataType.form)
item
@dataScreens: (attribute, localParams, params) ->
object = params.object
objects =
Ticket:
Customer:
create:
shown: true
required: false
edit:
shown: true
required: false
Agent:
create_bottom:
show: true
required: false
edit:
shown: true
required: false
User:
Customer:
create:
shown: true
required: false
view:
shown: true
signup:
shown: false
required: false
Agent:
create:
shown: true
required: false
edit:
shown: true
required: false
view:
shown: true
invite_customer:
show: false
required: false
Admin:
create:
shown: true
required: false
edit:
shown: true
required: false
view:
shown: true
invite_agent:
show: false
required: false
invite_customer:
show: false
required: false
Organization:
Customer:
view:
shown: true
Agent:
create:
shown: true
required: false
edit:
shown: true
required: false
view:
shown: true
Admin:
create:
shown: true
required: false
edit:
shown: true
required: false
view:
shown: true
Group:
Admin:
create:
shown: true
required: false
edit:
shown: true
required: false
view:
shown: true
init = false
if params && !params.id
init = true
$(App.view('object_manager/screens')(
attribute: attribute
data: objects[object]
params: params
init: init
))
@input: (item, localParams, params) ->
configureAttributes = [
{ name: 'data_option::default', display: 'Default', tag: 'input', type: 'text', null: true, default: '' },
]
inputDefault = new App.ControllerForm(
model:
configure_attributes: configureAttributes
noFieldset: true
params: params
)
configureAttributes = [
{ name: 'data_option::type', display: 'Type', tag: 'select', null: false, default: 'text', options: {text: 'Text', phone: 'Phone', fax: 'Fax', email: 'Email', url: 'Url'}, translate: true },
]
inputType = new App.ControllerForm(
model:
configure_attributes: configureAttributes
noFieldset: true
params: params
)
configureAttributes = [
{ name: 'data_option::maxlength', display: 'Maxlength', tag: 'integer', null: false, default: 120 },
]
inputMaxlength = new App.ControllerForm(
model:
configure_attributes: configureAttributes
noFieldset: true
params: params
)
item.find('.js-inputDefault').html(inputDefault.form)
item.find('.js-inputType').html(inputType.form)
item.find('.js-inputMaxlength').html(inputMaxlength.form)
@datetime: (item, localParams, params) ->
configureAttributes = [
{ name: 'data_option::future', display: 'Allow future', tag: 'boolean', null: false, default: true },
]
datetimeFuture = new App.ControllerForm(
model:
configure_attributes: configureAttributes
noFieldset: true
params: params
)
configureAttributes = [
{ name: 'data_option::past', display: 'Allow past', tag: 'boolean', null: false, default: true },
]
datetimePast = new App.ControllerForm(
model:
configure_attributes: configureAttributes
noFieldset: true
params: params
)
configureAttributes = [
{ name: 'data_option::diff', display: 'Default time Diff (minutes)', tag: 'integer', null: false, default: 24 },
]
datetimeDiff = new App.ControllerForm(
model:
configure_attributes: configureAttributes
noFieldset: true
params: params
)
item.find('.js-datetimeFuture').html(datetimeFuture.form)
item.find('.js-datetimePast').html(datetimePast.form)
item.find('.js-datetimeDiff').html(datetimeDiff.form)
@date: (item, localParams, params) ->
configureAttributes = [
{ name: 'data_option::future', display: 'Allow future', tag: 'boolean', null: false, default: true },
]
dateFuture = new App.ControllerForm(
model:
configure_attributes: configureAttributes
noFieldset: true
params: params
)
configureAttributes = [
{ name: 'data_option::past', display: 'Allow past', tag: 'boolean', null: false, default: true },
]
datePast = new App.ControllerForm(
model:
configure_attributes: configureAttributes
noFieldset: true
params: params
)
configureAttributes = [
{ name: 'data_option::diff', display: 'Default time Diff (hours)', tag: 'integer', null: false, default: 24 },
]
dateDiff = new App.ControllerForm(
model:
configure_attributes: configureAttributes
noFieldset: true
params: params
)
item.find('.js-dateFuture').html(dateFuture.form)
item.find('.js-datePast').html(datePast.form)
item.find('.js-dateDiff').html(dateDiff.form)
@integer: (item, localParams, params) ->
configureAttributes = [
{ name: 'data_option::default', display: 'Default', tag: 'integer', null: true, default: '', min: 1 },
]
integerDefault = new App.ControllerForm(
model:
configure_attributes: configureAttributes
noFieldset: true
params: params
)
configureAttributes = [
{ name: 'data_option::min', display: 'Minimal', tag: 'integer', null: false, default: 0, min: 1 },
]
integerMin = new App.ControllerForm(
model:
configure_attributes: configureAttributes
noFieldset: true
params: params
)
configureAttributes = [
{ name: 'data_option::max', display: 'Maximal', tag: 'integer', null: false, default: 999999999, min: 2 },
]
integerMax = new App.ControllerForm(
model:
configure_attributes: configureAttributes
noFieldset: true
params: params
)
item.find('.js-integerDefault').html(integerDefault.form)
item.find('.js-integerMin').html(integerMin.form)
item.find('.js-integerMax').html(integerMax.form)
@select: (item, localParams, params) ->
@boolean: (item, localParams, params) ->
@autocompletion: (item, localParams, params) ->
configureAttributes = [
{ name: 'data_option::default', display: 'Default', tag: 'input', type: 'text', null: true, default: '' },
]
autocompletionDefault = new App.ControllerForm(
model:
configure_attributes: configureAttributes
noFieldset: true
params: params
)
configureAttributes = [
{ name: 'data_option::url', display: 'Url (AJAX Endpoint)', tag: 'input', type: 'url', null: false, default: '', placeholder: 'https://example.com/serials' },
]
autocompletionUrl = new App.ControllerForm(
model:
configure_attributes: configureAttributes
noFieldset: true
params: params
)
configureAttributes = [
{ name: 'data_option::method', display: 'Method (AJAX Endpoint)', tag: 'input', type: 'url', null: false, default: '', placeholder: 'GET' },
]
autocompletionMethod = new App.ControllerForm(
model:
configure_attributes: configureAttributes
noFieldset: true
params: params
)
item.find('.js-autocompletionDefault').html(autocompletionDefault.form)
item.find('.js-autocompletionUrl').html(autocompletionUrl.form)
item.find('.js-autocompletionMethod').html(autocompletionMethod.form)

View file

@ -0,0 +1,125 @@
# coffeelint: disable=camel_case_classes
class App.UiElement.user_permission
@render: (attribute, params = {}) ->
attribute.options = {}
# get selectable roles and selected roles
roles = []
rolesSelected = {}
rolesRaw = App.Role.search(sortBy: 'name')
for role in rolesRaw
if role.active
roles.push role
if params.role_ids
for role_id in params.role_ids
if role_id.toString() is role.id.toString()
rolesSelected[role.id] = true
# get selectable groups and selected groups
groups = []
groupsSelected = {}
groupsRaw = App.Group.search(sortBy: 'name')
for group in groupsRaw
if group.active
groups.push group
if params.group_ids
for group_id in params.group_ids
if group_id.toString() is group.id.toString()
groupsSelected[group.id] = true
# if only one group is selectable, hide all groups
hideGroups = false
if groups.length <= 1
hideGroups = true
if attribute.hideMode
if attribute.hideMode.rolesSelected
roles = []
rolesSelected = {}
for roleName in attribute.hideMode.rolesSelected
role = App.Role.findByAttribute('name', roleName)
if role
roles.push role
rolesSelected[role.id] = true
if attribute.hideMode.rolesNot
for roleRaw in rolesRaw
hit = false
for roleName in attribute.hideMode.rolesNot
if roleRaw.active && roleRaw.name is roleName
hit = true
if !hit
roles.push roleRaw
# if agent is on new users selected, select all groups
if _.isEmpty(attribute.value)
agentRole = App.Role.findByAttribute('name', 'Agent')
if rolesSelected[agentRole.id]
for group in groups
groupsSelected[group.id] = true
# uniq and sort roles
roles = _.indexBy(roles, 'name')
roles = _.sortBy(roles, (i) -> return i.name)
item = $( App.view('generic/user_permission')(
attribute: attribute
roles: roles
groups: groups
params: params
rolesSelected: rolesSelected
groupsSelected: groupsSelected
hideGroups: hideGroups
) )
getCurrentRoles = ->
currentRoles = []
item.find('[name=role_ids]').each( ->
element = $(@)
checked = element.prop('checked')
return if !checked
role_id = element.prop('value')
role = App.Role.find(role_id)
return if !role
currentRoles.push role
)
currentRoles
# if customer, remove admin and agent
item.find('[name=role_ids]').bind('change', (e) ->
element = $(e.currentTarget)
checked = element.prop('checked')
role_id = element.prop('value')
return if !role_id
role = App.Role.find(role_id)
return if !role
# if agent got deselected
# - hide groups
if !checked
if role.name is 'Agent'
item.find('.js-groupList').addClass('hidden')
return
# if agent is selected
# - show groups
if role.name is 'Agent'
item.find('.js-groupList:not(.js-groupListHide)').removeClass('hidden')
# if role customer is selected
# - deselect agent & admin
# - hide groups
if role.name is 'Customer'
for currentRole in getCurrentRoles()
if currentRole.name is 'Admin' || currentRole.name is 'Agent'
item.find("[name=role_ids][value=#{currentRole.id}]").prop('checked', false)
item.find('.js-groupList').addClass('hidden')
# if role agent or admin is selected
# - deselect customer
else if role.name is 'Agent' || role.name is 'Admin'
for currentRole in getCurrentRoles()
if currentRole.name is 'Customer'
item.find("[name=role_ids][value=#{currentRole.id}]").prop('checked', false)
)
item

View file

@ -14,7 +14,7 @@ class Index extends App.ControllerTabs
@ajax( @ajax(
id: 'object_manager_attributes_list' id: 'object_manager_attributes_list'
type: 'GET' type: 'GET'
url: @apiPath + '/object_manager_attributes_list' url: "#{@apiPath}/object_manager_attributes_list"
processData: true processData: true
success: (data, status, xhr) => success: (data, status, xhr) =>
@stopLoading() @stopLoading()
@ -36,22 +36,21 @@ class Index extends App.ControllerTabs
class Items extends App.ControllerContent class Items extends App.ControllerContent
events: events:
'click [data-type="delete"]': 'destroy' 'click .js-delete': 'destroy'
'click .js-up': 'move' 'click .js-new': 'new'
'click .js-down': 'move' 'click .js-edit': 'edit'
'click .js-new': 'new' 'click .js-discard': 'discard'
'click .js-edit': 'edit' 'click .js-execute': 'execute'
constructor: -> constructor: ->
super super
# check authentication # check authentication
return if !@authenticate() return if !@authenticate()
@subscribeId = App.ObjectManagerAttribute.subscribe(@render) @subscribeId = App.ObjectManagerAttribute.subscribe(@render)
App.ObjectManagerAttribute.fetch() App.ObjectManagerAttribute.fetch()
# ajax call
release: => release: =>
if @subscribeId if @subscribeId
App.ObjectManagerAttribute.unsubscribe(@subscribeId) App.ObjectManagerAttribute.unsubscribe(@subscribeId)
@ -63,63 +62,22 @@ class Items extends App.ControllerContent
sortBy: 'position' sortBy: 'position'
) )
itemsToChange = []
for item in App.ObjectManagerAttribute.search(sortBy: 'object')
if item.to_create is true || item.to_delete is true
itemsToChange.push item
@html App.view('object_manager/index')( @html App.view('object_manager/index')(
head: @object head: @object
items: items items: items
itemsToChange: itemsToChange
) )
###
new App.ControllerTable(
el: @el.find('.table-overview')
model: App.ObjectManagerAttribute
objects: objects
bindRow:
events:
'click': @edit
)
###
move: (e) =>
e.preventDefault()
e.stopPropagation()
id = $( e.target ).closest('tr').data('id')
direction = 'up'
if $( e.target ).hasClass('js-down')
direction = 'down'
console.log('M', id, direction)
items = App.ObjectManagerAttribute.search(
filter:
object: @object
sortBy: 'position'
)
count = 0
for item in items
if item.id is id
if direction is 'up'
itemToMove = items[ count - 1 ]
else
itemToMove = items[ count + 1 ]
if itemToMove
movePosition = itemToMove.position
if movePosition is item.position
if direction is 'up'
movePosition -= 1
else
movePosition += 1
itemToMove.position = item.position
itemToMove.save()
console.log(itemToMove, itemToMove.position, count)
item.position = movePosition
item.save()
console.log(item, movePosition, count)
count += 1
new: (e) => new: (e) =>
e.preventDefault() e.preventDefault()
new App.ControllerGenericNew( new New(
pageData: pageData:
head: @object
title: 'Attribute' title: 'Attribute'
home: 'object_manager' home: 'object_manager'
object: 'ObjectManagerAttribute' object: 'ObjectManagerAttribute'
@ -127,6 +85,8 @@ class Items extends App.ControllerContent
navupdate: '#object_manager' navupdate: '#object_manager'
genericObject: 'ObjectManagerAttribute' genericObject: 'ObjectManagerAttribute'
container: @el.closest('.content') container: @el.closest('.content')
item:
object: @object
) )
edit: (e) => edit: (e) =>
@ -134,136 +94,135 @@ class Items extends App.ControllerContent
id = $( e.target ).closest('tr').data('id') id = $( e.target ).closest('tr').data('id')
new Edit( new Edit(
pageData: pageData:
object: 'ObjectManagerAttribute' head: @object
title: 'Attribute'
home: 'object_manager'
object: 'ObjectManagerAttribute'
objects: 'ObjectManagerAttributes'
navupdate: '#object_manager'
genericObject: 'ObjectManagerAttribute' genericObject: 'ObjectManagerAttribute'
container: @el.closest('.content')
callback: @render callback: @render
id: id id: id
container: @el.closest('.content')
) )
destroy: (e) -> destroy: (e) ->
e.stopPropagation()
e.preventDefault() e.preventDefault()
sessionId = $( e.target ).data('session-id') id = $(e.target).closest('tr').data('id')
item = App.ObjectManagerAttribute.find(id)
@ajax( @ajax(
id: 'sessions/' + sessionId id: "object_manager_attributes/#{id}"
type: 'DELETE' type: 'DELETE'
url: @apiPath + '/sessions/' + sessionId url: "#{@apiPath}/object_manager_attributes/#{id}"
success: (data) => success: (data) =>
@load() @render()
) )
class Edit extends App.ControllerModal discard: (e) ->
buttonClose: true e.preventDefault()
buttonCancel: true @ajax(
buttonSubmit: true id: 'object_manager_attributes_discard_changes'
head: 'Edit' type: 'POST'
url: "#{@apiPath}/object_manager_attributes_discard_changes"
success: (data) =>
@render()
)
execute: (e) ->
e.preventDefault()
@ajax(
id: 'object_manager_attributes_execute_migrations'
type: 'POST'
url: "#{@apiPath}/object_manager_attributes_execute_migrations"
success: (data) =>
@render()
)
class New extends App.ControllerGenericNew
onSubmit: (e) =>
params = @formParam(e.target)
params.object = @pageData.head
object = new App[ @genericObject ]
object.load(params)
# validate
errors = object.validate()
if errors
@log 'error', errors
@formValidate( form: e.target, errors: errors )
return false
# disable form
@formDisable(e)
# save object
ui = @
object.save(
done: ->
if ui.callback
item = App[ ui.genericObject ].fullLocal(@id)
ui.callback(item)
ui.close()
fail: (settings, details) ->
ui.log 'errors', details
ui.formEnable(e)
ui.controller.showAlert(details.error_human || details.error || 'Unable to create object!')
)
class Edit extends App.ControllerGenericEdit
content: => content: =>
content = $( App.view('object_manager/edit')( @item = App[ @genericObject ].find( @id )
head: @object @head = @pageData.head || @pageData.object
items: []
) )
item = App.ObjectManagerAttribute.find(@id) # set disabled attributes
configure_attributes = clone(App[ @genericObject ].configure_attributes)
for attribute in configure_attributes
if attribute.name is 'name'
attribute.disabled = true
#if attribute.name is 'data_type'
# attribute.disabled = true
options = @controller = new App.ControllerForm(
input: 'Text (normal - one line)' model:
select: 'Selection' configure_attributes: configure_attributes
datetime: 'Datetime' params: @item
date: 'Date' screen: @screen || 'edit'
textarea: 'Text (normal - multiline)'
richtext: 'Text (richtext)'
checkbox: 'Checkbox'
boolean: 'yes/no'
configureAttributesTop = [
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, 'null': false },
{ name: 'display', display: 'Anzeige', tag: 'input', type: 'text', limit: 100, 'null': false },
{ name: 'data_type', display: 'Format', tag: 'select', multiple: false, nulloption: true, null: false, options: options, translate: true },
]
controller = new App.ControllerForm(
model: { configure_attributes: configureAttributesTop, className: '' },
params: item
#screen: @screen || 'edit'
el: content.find('.js-top')
autofocus: true
)
# input
configureAttributesInput = [
{ name: 'data_option::type', display: 'Type', tag: 'select', multiple: false, nulloption: true, null: false, options: { text: 'text', email: 'email', url: 'url', email: 'email', password: 'password', phone: 'phone'}, translate: true },
{ name: 'data_option::maxlength', display: 'Max. Length', tag: 'input', type: 'text', limit: 100, 'null': false },
{ name: 'data_option::null', display: 'Required', tag: 'select', multiple: false, nulloption: false, null: false, options: { true: 'no', false: 'yes' }, translate: true },
{ name: 'data_option::autocapitalize', display: 'autocapitalize', tag: 'select', multiple: false, nulloption: true, null: false, options: { true: 'no', false: 'yes' }, translate: true },
{ name: 'data_option::autocomplete', display: 'autocomplete', tag: 'select', multiple: false, nulloption: true, null: false, options: { true: 'no', false: 'yes' }, translate: true },
{ name: 'data_option::default', display: 'Default', tag: 'input', type: 'text', limit: 100, null: true },
{ name: 'data_option::note', display: 'note', tag: 'input', type: 'text', limit: 100, null: true },
]
controller = new App.ControllerForm(
model: { configure_attributes: configureAttributesInput, className: '' },
params: item
el: content.find('.js-input')
autofocus: true
)
# textarea
configureAttributesTextarea = [
{ name: 'data_option::maxlength', display: 'Max. Length', tag: 'input', type: 'text', limit: 100, null: false },
{ name: 'data_option::null', display: 'Required', tag: 'select', multiple: false, nulloption: false, null: false, options: { true: 'no', false: 'yes' }, translate: true },
{ name: 'data_option::autocapitalize', display: 'autocapitalize', tag: 'select', multiple: false, nulloption: true, null: false, options: { true: 'no', false: 'yes' }, translate: true },
{ name: 'data_option::note', display: 'autocomplete', tag: 'input', type: 'text', limit: 100, null: true },
]
controller = new App.ControllerForm(
model: { configure_attributes: configureAttributesTextarea, className: '' },
params: item
el: content.find('.js-textarea')
autofocus: true
)
# select
configureAttributesSelect = [
{ name: 'data_option::nulloption', display: 'Empty Selection', tag: 'select', multiple: false, nulloption: false, null: false, options: { true: 'no', false: 'yes' }, translate: true },
{ name: 'data_option::null', display: 'Required', tag: 'boolean', multiple: false, nulloption: false, null: false, options: { true: 'no', false: 'yes' }, translate: true },
{ name: 'data_option::relation', display: 'Relation', tag: 'input', type: 'text', limit: 100, null: true },
{ name: 'data_option::options', display: 'Options', tag: 'hash', multiple: true, null: false },
{ name: 'data_option::translate', display: 'Übersetzen', tag: 'select', multiple: false, nulloption: false, null: false, options: { true: 'no', false: 'yes' }, translate: true },
{ name: 'data_option::note', display: 'Note', tag: 'input', type: 'text', limit: 100, null: true },
]
controller = new App.ControllerForm(
model: { configure_attributes: configureAttributesSelect, className: '' },
params: item
el: content.find('.js-select')
autofocus: true autofocus: true
) )
@controller.form
### onSubmit: (e) =>
:options => { params = @formParam(e.target)
'Incident' => 'Incident', params.object = @pageData.head
'Problem' => 'Problem', @item.load(params)
'Request for Change' => 'Request for Change',
},
###
content.find('[name=data_type]').on( # validate
'change', errors = @item.validate()
(e) -> if errors
dataType = $( e.target ).val() @log 'error', errors
content.find('.js-middle > div').addClass('hide') @formValidate( form: e.target, errors: errors )
content.find(".js-#{dataType}").removeClass('hide') return false
# disable form
@formDisable(e)
# save object
ui = @
@item.save(
done: ->
if ui.callback
item = App[ ui.genericObject ].fullLocal(@id)
ui.callback(item)
ui.close()
fail: (settings, details) ->
ui.log 'errors'
ui.formEnable(e)
ui.controller.showAlert(details.error_human || details.error || 'Unable to update object!')
) )
content.find('[name=data_type]').trigger('change')
configureAttributesBottom = [
{ name: 'active', display: 'Active', tag: 'active', default: true },
]
controller = new App.ControllerForm(
model: { configure_attributes: configureAttributesBottom, className: '' },
params: item
#screen: @screen || 'edit'
el: content.find('.js-bottom')
)
controller.form
App.Config.set( 'SystemObject', { prio: 1700, parent: '#system', name: 'Objects', target: '#system/object_manager', controller: Index, role: ['Admin'] }, 'NavBarAdmin' ) App.Config.set( 'SystemObject', { prio: 1700, parent: '#system', name: 'Objects', target: '#system/object_manager', controller: Index, role: ['Admin'] }, 'NavBarAdmin' )

View file

@ -43,16 +43,16 @@ class App.InviteUser extends App.WizardModal
e.preventDefault() e.preventDefault()
@showSlide('js-waiting') @showSlide('js-waiting')
@formDisable(e) @formDisable(e)
@params = @formParam(e.target) @params = @formParam(e.target)
@params.role_ids = [0]
# set invite flag # set invite flag
@params.invite = true @params.invite = true
# find agent role # find agent role
role = App.Role.findByAttribute('name', @role) if @role
if role role = App.Role.findByAttribute('name', @role)
@params.role_ids = role.id if role
@params.role_ids = role.id
user = new App.User user = new App.User
user.load(@params) user.load(@params)
@ -62,7 +62,7 @@ class App.InviteUser extends App.WizardModal
) )
if errors if errors
@log 'error new', errors @log 'error new', errors
@formValidate( form: e.target, errors: errors ) @formValidate(form: e.target, errors: errors)
@formEnable(e) @formEnable(e)
@showSlide('js-user') @showSlide('js-user')
return false return false
@ -77,4 +77,4 @@ class App.InviteUser extends App.WizardModal
@formEnable(e) @formEnable(e)
@showSlide('js-user') @showSlide('js-user')
@showAlert('js-user', details.error_human || details.error) @showAlert('js-user', details.error_human || details.error)
) )

View file

@ -597,33 +597,33 @@ class App.Model extends Spine.Model
all_complied = [] all_complied = []
if !params if !params
for item in all for item in all
item_new = @find( item.id ) item_new = @find(item.id)
all_complied.push @_fillUp(item_new) all_complied.push @_fillUp(item_new)
return all_complied return all_complied
for item in all for item in all
item_new = @find( item.id ) item_new = @find(item.id)
all_complied.push @_fillUp(item_new) all_complied.push @_fillUp(item_new)
# filter search # filter search
if params.filter if params.filter
all_complied = @_filter( all_complied, params.filter ) all_complied = @_filter(all_complied, params.filter)
# use extend filter search # use extend filter search
if params.filterExtended if params.filterExtended
all_complied = @_filterExtended( all_complied, params.filterExtended ) all_complied = @_filterExtended(all_complied, params.filterExtended)
# sort by # sort by
if params.sortBy != null if params.sortBy != null
all_complied = @_sortBy( all_complied, params.sortBy ) all_complied = @_sortBy(all_complied, params.sortBy)
# order # order
if params.order if params.order
all_complied = @_order( all_complied, params.order ) all_complied = @_order(all_complied, params.order)
all_complied all_complied
@_sortBy: ( collection, attribute ) -> @_sortBy: (collection, attribute) ->
_.sortBy( collection, (item) -> _.sortBy(collection, (item) ->
# set displayName as default sort attribute # set displayName as default sort attribute
if !attribute if !attribute
@ -646,20 +646,20 @@ class App.Model extends Spine.Model
item[ attribute ] item[ attribute ]
) )
@_order: ( collection, attribute ) -> @_order: (collection, attribute) ->
if attribute is 'DESC' if attribute is 'DESC'
return collection.reverse() return collection.reverse()
collection collection
@_filter: ( collection, filter ) -> @_filter: (collection, filter) ->
for key, value of filter for key, value of filter
collection = _.filter( collection, (item) -> collection = _.filter(collection, (item) ->
if item[key] is value if item[key] is value
return item return item
) )
collection collection
@_filterExtended: ( collection, filters ) -> @_filterExtended: (collection, filters) ->
collection = _.filter( collection, (item) -> collection = _.filter( collection, (item) ->
# check all filters # check all filters

View file

@ -6,8 +6,8 @@ class App.Group extends App.Model
@configure_attributes = [ @configure_attributes = [
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false }, { name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false },
{ name: 'assignment_timeout', display: 'Assignment Timeout', tag: 'input', note: 'Assignment timeout in minutes if assigned agent is not working on it. Ticket will be shown as unassigend.', type: 'text', limit: 100, null: true }, { name: 'assignment_timeout', display: 'Assignment Timeout', tag: 'input', note: 'Assignment timeout in minutes if assigned agent is not working on it. Ticket will be shown as unassigend.', type: 'text', limit: 100, null: true },
{ name: 'follow_up_possible', display: 'Follow up possible',tag: 'select', default: 'yes', options: { yes: 'yes', reject: 'reject follow up/do not reopen Ticket', 'new_ticket': 'do not reopen Ticket but create new Ticket' }, null: false, note: 'Follow up for closed ticket possible or not.' }, { name: 'follow_up_possible', display: 'Follow up possible',tag: 'select', default: 'yes', options: { yes: 'yes', reject: 'reject follow up/do not reopen Ticket', 'new_ticket': 'do not reopen Ticket but create new Ticket' }, null: false, note: 'Follow up for closed ticket possible or not.', translate: true },
{ name: 'follow_up_assignment', display: 'Assign Follow Ups', tag: 'select', default: 'yes', options: { true: 'yes', false: 'no' }, 'null': false, note: 'Assign follow up to latest agent again.' }, { name: 'follow_up_assignment', display: 'Assign Follow Ups', tag: 'select', default: 'yes', options: { true: 'yes', false: 'no' }, null: false, note: 'Assign follow up to latest agent again.', translate: true },
{ name: 'email_address_id', display: 'Email', tag: 'select', multiple: false, null: true, relation: 'EmailAddress', nulloption: true, do_not_log: true }, { name: 'email_address_id', display: 'Email', tag: 'select', multiple: false, null: true, relation: 'EmailAddress', nulloption: true, do_not_log: true },
{ name: 'signature_id', display: 'Signature', tag: 'select', multiple: false, null: true, relation: 'Signature', nulloption: true, do_not_log: true }, { name: 'signature_id', display: 'Signature', tag: 'select', multiple: false, null: true, relation: 'Signature', nulloption: true, do_not_log: true },
{ name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, null: true }, { name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, null: true },

View file

@ -1,20 +1,13 @@
class App.ObjectManagerAttribute extends App.Model class App.ObjectManagerAttribute extends App.Model
@configure 'ObjectManagerAttribute', 'name', 'object', 'display', 'active', 'editable', 'data_type', 'data_option', 'screens', 'position', 'updated_at' @configure 'ObjectManagerAttribute', 'name', 'object', 'display', 'active', 'editable', 'data_type', 'data_option', 'screens', 'position'
@extend Spine.Model.Ajax @extend Spine.Model.Ajax
@url: @apiPath + '/object_manager_attributes' @url: @apiPath + '/object_manager_attributes'
@configure_attributes = [ @configure_attributes = [
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false }, { name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false },
{ name: 'display', display: 'Anzeige', tag: 'input', type: 'text', limit: 100, null: false }, { name: 'display', display: 'Anzeige', tag: 'input', type: 'text', limit: 100, null: false },
{ name: 'object', display: 'Object', tag: 'input', readonly: 1 }, { name: 'object', display: 'Object', tag: 'input', readonly: 1 },
{ name: 'position', display: 'Position', tag: 'input', readonly: 1 }, { name: 'position', display: 'Position', tag: 'input', readonly: 1 },
{ name: 'active', display: 'Active', tag: 'active', default: true }, { name: 'active', display: 'Active', tag: 'active', default: true },
{ name: 'data_type', display: 'Format', tag: 'input', type: 'text', limit: 100, null: false }, { name: 'data_type', display: 'Format', tag: 'object_manager_attribute', null: false },
{ name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 }, { name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
] ]
@configure_overview = [
#'name',
'display',
'position',
'data_type',
]
@configure_delete = true

View file

@ -12,8 +12,7 @@ class App.User extends App.Model
{ name: 'organization_id', display: 'Organization', tag: 'select', multiple: false, nulloption: true, null: true, relation: 'Organization', signup: false, info: true, invite_customer: true }, { name: 'organization_id', display: 'Organization', tag: 'select', multiple: false, nulloption: true, null: true, relation: 'Organization', signup: false, info: true, invite_customer: true },
{ name: 'password', display: 'Password', tag: 'input', type: 'password', limit: 50, null: true, autocomplete: 'off', signup: true, }, { name: 'password', display: 'Password', tag: 'input', type: 'password', limit: 50, null: true, autocomplete: 'off', signup: true, },
{ name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, null: true, info: true, invite_customer: true }, { name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, null: true, info: true, invite_customer: true },
{ name: 'role_ids', display: 'Roles', tag: 'checkbox', multiple: true, null: false, relation: 'Role' }, { name: 'role_ids', display: 'Permissions', tag: 'user_permission', null: false, invite_agent: true, invite_customer: true, item_class: 'checkbox' },
{ name: 'group_ids', display: 'Groups', tag: 'checkbox', multiple: true, null: true, relation: 'Group', invite_agent: true, invite_customer: true },
{ name: 'active', display: 'Active', tag: 'active', default: true }, { name: 'active', display: 'Active', tag: 'active', default: true },
{ name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 }, { name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 },
{ name: 'created_at', display: 'Created at', tag: 'datetime', readonly: 1 }, { name: 'created_at', display: 'Created at', tag: 'datetime', readonly: 1 },

View file

@ -1 +1,4 @@
<input id="<%= @attribute.id %>" type="<%= @attribute.type %>" name="<%= @attribute.name %>" value="<%= @attribute.value %>" class="form-control <%= @attribute.class %>" <% if @attribute.placeholder: %>placeholder="<%- @Ti(@attribute.placeholder) %>"<% end %> <%= @attribute.required %> <%= @attribute.autofocus %> <%- @attribute.autocapitalize %> <%- @attribute.autocomplete %>/> <input id="<%= @attribute.id %>" type="<%= @attribute.type %>" name="<%= @attribute.name %>" value="<%= @attribute.value %>" class="form-control <%= @attribute.class %>" <% if @attribute.placeholder: %>placeholder="<%- @Ti(@attribute.placeholder) %>"<% end %> <%= @attribute.required %> <%= @attribute.autofocus %> <%- @attribute.autocapitalize %> <%- @attribute.autocomplete %> <% if @attribute.min isnt undefined: %> min="<%= @attribute.min %>"<% end %><% if @attribute.max isnt undefined: %> max="<%= @attribute.max %>"<% end %><% if @attribute.step: %> step="<%= @attribute.step %>"<% end %><% if @attribute.disabled: %> disabled<% end %>/>
<% if @attribute.disabled: %>
<input type="hidden" name="<%= @attribute.name %>" value="<%= @attribute.value %>">
<% end %>

View file

@ -1,5 +1,5 @@
<div class="controls controls--select"> <div class="controls controls--select">
<select id="<%= @attribute.id %>" class="form-control<%= " #{ @attribute.class }" if @attribute.class %>" name="<%= @attribute.name %>" <%= @attribute.multiple %> <%= @attribute.required %> <%= @attribute.autofocus %>> <select id="<%= @attribute.id %>" class="form-control<%= " #{ @attribute.class }" if @attribute.class %>" name="<%= @attribute.name %>" <%= @attribute.multiple %> <%= @attribute.required %> <%= @attribute.autofocus %> <% if @attribute.disabled: %> disabled<% end %>>
<% if @attribute.options: %> <% if @attribute.options: %>
<% for row in @attribute.options: %> <% for row in @attribute.options: %>
<option value="<%= row.value %>" <%= row.selected %> <%= row.disabled %>><%= row.name %></option> <option value="<%= row.value %>" <%= row.selected %> <%= row.disabled %>><%= row.name %></option>
@ -9,4 +9,13 @@
<% if not @attribute.multiple: %> <% if not @attribute.multiple: %>
<%- @Icon('arrow-down') %> <%- @Icon('arrow-down') %>
<% end %> <% end %>
<% if @attribute.disabled: %>
<% if @attribute.options: %>
<% for row in @attribute.options: %>
<% if row.selected: %>
<input type="hidden" name="<%= @attribute.name %>" value="<%= row.value %>">
<% end %>
<% end %>
<% end %>
<% end %>
</div> </div>

View file

@ -0,0 +1,22 @@
<div class="checkbox <%= @attribute.class %> checkbox">
<% for role in @roles: %>
<label class="inline-label checkbox-replacement">
<input type="checkbox" value="<%= role.id %>" name="role_ids" <% if @rolesSelected[role.id]: %>checked<% end %>/>
<%- @Icon('checkbox', 'icon-unchecked') %>
<%- @Icon('checkbox-checked', 'icon-checked') %>
<span class="label-text"><%= role.displayName() %> <% if role.note: %>- <span class="help-text"><%= role.note %></span><% end %></span>
</label>
<% if role.name is 'Agent': %>
<div style="padding-left: 20px;" class="js-groupList <% if @hideGroups: %>js-groupListHide hidden<% end %>">
<% for group in @groups: %>
<label class="inline-label checkbox-replacement">
<input type="checkbox" value="<%= group.id %>" name="group_ids" <% if @groupsSelected[group.id]: %>checked<% end %>/>
<%- @Icon('checkbox', 'icon-unchecked') %>
<%- @Icon('checkbox-checked', 'icon-checked') %>
<span class="label-text"><%= group.displayName() %> <% if group.note: %>- <span class="help-text"><%= group.note %></span><% end %></span>
</label>
<% end %>
</div>
<% end %>
<% end %>
</div>

View file

@ -0,0 +1,5 @@
<div class="js-data">
<div class="js-dataType"></div>
<div class="js-dataMap" style="padding: 30px 0 0 30px;"></div>
<div class="js-dataScreens" style="padding: 30px 0 0 30px;"></div>
</div>

View file

@ -0,0 +1,6 @@
<div>
Auto-Vervollständigung
<div class="js-autocompletionDefault"></div>
<div class="js-autocompletionUrl"></div>
<div class="js-autocompletionMethod"></div>
</div>

View file

@ -0,0 +1,29 @@
<div>
<div class="js-selectDefault"></div>
<div class="js-selectOption"></div>
<table class="settings-list" style="width: 100%;">
<thead>
<tr>
<th><%- @T('Key') %>
<th><%- @T('Display') %>
<th style="width: 30px"><%- @T('Default') %>
</thead>
<tbody>
<tr>
<td class="settings-list-control-cell">
true
<td class="settings-list-control-cell">
<input class="form-control form-control--small js-summary" type="text" name="summary" value="<%= @display %>" placeholder="<%- @T('yes') %>" required/>
<td class="settings-list-row-control">
<input class="" type="radio" />
<tr>
<td class="settings-list-control-cell">
false
<td class="settings-list-control-cell">
<input class="form-control form-control--small js-summary" type="text" name="summary" value="<%= @display %>" placeholder="<%- @T('no') %>" required/>
<td class="settings-list-row-control">
<input class="" type="radio" />
</tbody>
</table>
</div>

View file

@ -0,0 +1,5 @@
<div>
<div class="js-dateFuture"></div>
<div class="js-datePast"></div>
<div class="js-dateDiff"></div>
</div>

View file

@ -0,0 +1,5 @@
<div>
<div class="js-datetimeFuture"></div>
<div class="js-datetimePast"></div>
<div class="js-datetimeDiff"></div>
</div>

View file

@ -0,0 +1,5 @@
<div>
<div class="js-inputDefault"></div>
<div class="js-inputType"></div>
<div class="js-inputMaxlength"></div>
</div>

View file

@ -0,0 +1,5 @@
<div>
<div class="js-integerDefault"></div>
<div class="js-integerMin"></div>
<div class="js-integerMax"></div>
</div>

View file

@ -0,0 +1,41 @@
<div>
<div class="js-selectDefault"></div>
<div class="js-selectOption"></div>
<table class="settings-list" style="width: 100%;">
<thead>
<tr>
<th><%- @T('Key') %>
<th><%- @T('Display') %>
<th style="width: 30px"><%- @T('Default') %>
<th style="width: 30px"><%- @T('Action') %>
</thead>
<tbody>
<% for key, display of @values: %>
<tr>
<td class="settings-list-control-cell">
<input class="form-control form-control--small js-summary" type="text" name="summary" value="<%= key %>" required/>
<td class="settings-list-control-cell">
<input class="form-control form-control--small js-summary" type="text" name="summary" value="<%= display %>" required/>
<td class="settings-list-row-control">
<input class="" type="radio" />
<td class="settings-list-row-control">
<div class="btn btn--text js-remove">
<%- @Icon('trash') %> <%- @T('Remove') %>
</div>
<% end %>
<tr>
<td class="settings-list-control-cell">
<input class="form-control form-control--small js-summary" type="text" placeholder="<%- @T('Key') %>"/>
<td class="settings-list-control-cell">
<input class="form-control form-control--small js-summary" type="text" placeholder="<%- @T('Display') %>"/>
<td class="settings-list-row-control">
<input class="" type="radio" />
<td class="settings-list-row-control">
<div class="btn btn--text btn--create js-add">
<%- @Icon('plus-small') %> <%- @T('Add') %>
</div>
</tbody>
</table>
</div>

View file

@ -1,21 +1,42 @@
<div class="page-header"> <div class="page-header">
<div class="page-header-title"> <div class="page-header-title">
<h1><%- @T( @head ) %> <small><%- @T( 'Object Manager' ) %></small></h1> <h1><%- @T(@head) %> <small><%- @T('Object Manager') %></small></h1>
</div> </div>
<div class="page-header-meta"> <div class="page-header-meta">
<a class="btn js-restore"><%- @T( 'Restore Defaults' ) %></a> <!--
<a class="btn btn--success js-new"><%- @T( 'New Attribute' ) %></a> <a class="btn js-restore"><%- @T('Restore Defaults') %></a>
-->
<a class="btn btn--success js-new"><%- @T('New Attribute') %></a>
</div> </div>
</div> </div>
<div class="page-content"> <div class="page-content">
<% if !_.isEmpty(@itemsToChange): %>
<div class="box box--message"> <div class="box box--message">
<h2>Database Update required</h2> <h2>Database Update required</h2>
<p><%- @T( 'Changes were made that require a database update. This might take some time.' ) %></p> <p>
<%- @T('Changes were made that require a database update.') %>
<%- @T('This might take some time where the system can\'t be used.') %>
<%- @T('Please update database changes only in a maintenance timeslot.') %>
</p>
<p>
<%- @T('Changes') %>:
<ul>
<% for item in @itemsToChange: %>
<li>
<% if item.to_create is true: %>
<%- @T('Create') %> <%= item.object %>.<%= item.name %> (<%= item.data_type %>)
<% else if item.to_delete is true: %>
<%- @T('Delete') %> <%= item.object %>.<%= item.name %> (<%= item.data_type %>)
<% end %>
<% end %>
</p>
<div class="box-controls"> <div class="box-controls">
<div class="btn btn--text btn--secondary js-discard">Discard Changes</div> <div class="btn btn--text btn--secondary js-discard"><%- @T('Discard Changes') %></div>
<div class="btn btn--primary js-sync align-right"><%- @T( 'Update Database' ) %></div> <div class="btn btn--primary js-execute align-right"><%- @T('Update Database') %></div>
</div> </div>
</div> </div>
<% end %>
<!--
<div class="box box--message"> <div class="box box--message">
<div class="box-progress"> <div class="box-progress">
<div class="box-progress-title"><%- @T('Updating Database') %></div> <div class="box-progress-title"><%- @T('Updating Database') %></div>
@ -24,20 +45,31 @@
</div> </div>
</div> </div>
</div> </div>
-->
<table class="table table-striped table-hover is-disabled"> <table class="table table-striped table-hover is-disabled">
<thead> <thead>
<tr> <tr>
<th class=""><%- @T('Display') %></th> <th class=""><%- @T('Display') %></th>
<th class=""><%- @T('Name') %></th> <th class=""><%- @T('Name') %></th>
<th class=""><%- @T('Type') %></th> <th class="" style="width: 200px;"><%- @T('Type') %></th>
<th class="" style="width: 140px;"><%- @T('Action') %></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<% for item in @items: %> <% for item in @items: %>
<tr class="<% if item.active is false: %>is-inactive<% end %> js-edit u-clickable" data-id="<%- item.id %>"> <tr class="<% if item.editable is false: %>is-grayed-out u-notAllowed<% else: %><% if item.active is false: %>is-inactive<% end %> js-edit u-clickable<% end %>" data-id="<%- item.id %>">
<td><%= item.display %></td> <td><%= item.display %></td>
<td><%= item.name %></td> <td><%= item.name %></td>
<td><%= item.data_type %></td> <td><%= item.data_type %></td>
<td>
<% if item.to_create is true: %>
<%- @T('will be created') %>
<% else if item.to_delete is true: %>
<%- @T('will be deleted') %>
<% else if item.editable isnt false: %>
<a href="#" class="js-delete" title="<%- @Ti('Delete') %>"><%- @Icon('trash') %></a>
<% end %>
</td>
</tr> </tr>
<% end %> <% end %>
</tbody> </tbody>

View file

@ -0,0 +1,30 @@
<div>
<table class="settings-list" style="width: 100%;">
<thead>
<tr>
<th><%- @T('Role') %>
<th><%- @T('Screen') %>
<th style="width: 50%;"><%- @T('Options') %>
</thead>
<tbody>
<% for role, screenOptions of @data: %>
<tr>
<td class="settings-list-control-cell">
<%= role %>
<td class="settings-list-control-cell">
<td class="settings-list-row-control">
<% for screen, options of screenOptions: %>
<tr>
<td class="settings-list-control-cell">
<td class="settings-list-control-cell">
<%= screen %>
<td class="settings-list-row-control">
<% for key, defaultValue of options: %>
<%= @T(key) %>: <input name="{boolean}screens::<%= screen %>::<%= role %>::<%= key %>" type="checkbox" <% if (@init && defaultValue is true) || (@params && @params.screens && @params.screens[screen] && @params.screens[screen][role] && @params.screens[screen][role][key] is true) : %>checked<% end %> value="true">
<% end %>
<% end %>
<% end %>
</tbody>
</table>
</div>

View file

@ -329,7 +329,7 @@ pre code.hljs {
text-align: center; text-align: center;
white-space: nowrap; white-space: nowrap;
vertical-align: middle; vertical-align: middle;
.icon { .icon {
vertical-align: middle; vertical-align: middle;
margin-top: -3px; margin-top: -3px;
@ -339,7 +339,7 @@ pre code.hljs {
&.btn--icon--last .icon { &.btn--icon--last .icon {
margin-left: 5px; // so far only used in ticket_zoom secondaryAction dropup margin-left: 5px; // so far only used in ticket_zoom secondaryAction dropup
} }
&:focus { &:focus {
box-shadow: 0 0 0 3px hsl(201,62%,90%); box-shadow: 0 0 0 3px hsl(201,62%,90%);
} }
@ -348,7 +348,7 @@ pre code.hljs {
padding-top: 5px; padding-top: 5px;
padding-bottom: 4px; padding-bottom: 4px;
} }
&.btn--slim { &.btn--slim {
padding-left: 12px; padding-left: 12px;
padding-right: 12px; padding-right: 12px;
@ -365,7 +365,7 @@ pre code.hljs {
border: none; border: none;
margin: 5px 6px 0; margin: 5px 6px 0;
vertical-align: baseline; /* calendar_subscriptions.jst.eco */ vertical-align: baseline; /* calendar_subscriptions.jst.eco */
.icon { .icon {
vertical-align: middle; vertical-align: middle;
margin-right: 5px; margin-right: 5px;
@ -393,7 +393,7 @@ pre code.hljs {
padding: 2px 11px 0 !important; padding: 2px 11px 0 !important;
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
.icon { .icon {
margin: -2px 5px 0 -2px; margin: -2px 5px 0 -2px;
fill: hsl(0,0%,60%); fill: hsl(0,0%,60%);
@ -402,11 +402,11 @@ pre code.hljs {
.icon:only-child { .icon:only-child {
margin: 0; margin: 0;
} }
&.btn--slim { &.btn--slim {
padding-left: 7px !important; padding-left: 7px !important;
padding-right: 7px !important; padding-right: 7px !important;
&.btn--small { &.btn--small {
padding-left: 5px !important; padding-left: 5px !important;
padding-right: 5px !important; padding-right: 5px !important;
@ -425,7 +425,7 @@ pre code.hljs {
&.btn--onDark { &.btn--onDark {
background: none; background: none;
color: white; color: white;
svg { svg {
fill: currentColor; fill: currentColor;
opacity: 1; opacity: 1;
@ -453,7 +453,7 @@ pre code.hljs {
&.btn--success { &.btn--success {
color: white; color: white;
background: hsl(145,51%,45%); background: hsl(145,51%,45%);
&:active { &:active {
background: hsl(145,51%,35%); background: hsl(145,51%,35%);
} }
@ -461,7 +461,7 @@ pre code.hljs {
&.btn--secondary { &.btn--secondary {
background: white; background: white;
color: hsl(145,51%,45%); color: hsl(145,51%,45%);
&:active { &:active {
background: hsl(0,0%,98%); background: hsl(0,0%,98%);
} }
@ -486,7 +486,7 @@ pre code.hljs {
&.btn--secondary { &.btn--secondary {
background: white; background: white;
color: hsl(0,65%,55%); color: hsl(0,65%,55%);
&:active { &:active {
background: hsl(0,0%,98%); background: hsl(0,0%,98%);
} }
@ -502,12 +502,12 @@ pre code.hljs {
background: none; background: none;
vertical-align: baseline; vertical-align: baseline;
text-align: left; text-align: left;
.icon { .icon {
fill: currentColor; fill: currentColor;
opacity: 1; opacity: 1;
} }
&:active { &:active {
color: hsl(203,65%,40%); color: hsl(203,65%,40%);
background: none; background: none;
@ -516,7 +516,7 @@ pre code.hljs {
&.btn--secondary { &.btn--secondary {
color: hsl(0,0%,68%); color: hsl(0,0%,68%);
text-decoration: underline; text-decoration: underline;
&:active { &:active {
color: hsl(0,0%,53%); color: hsl(0,0%,53%);
} }
@ -524,7 +524,7 @@ pre code.hljs {
&.btn--positive { &.btn--positive {
color: hsl(145,51%,45%); color: hsl(145,51%,45%);
&:active { &:active {
color: hsl(145,51%,30%); color: hsl(145,51%,30%);
background: none; background: none;
@ -533,7 +533,7 @@ pre code.hljs {
&.btn--danger { &.btn--danger {
color: hsl(0,65%,55%); color: hsl(0,65%,55%);
&:active { &:active {
color: hsl(0,65%,40%); color: hsl(0,65%,40%);
background: none; background: none;
@ -543,7 +543,7 @@ pre code.hljs {
&.btn--subtle { &.btn--subtle {
text-decoration: underline; text-decoration: underline;
color: hsl(0,0%,85%); color: hsl(0,0%,85%);
&:active { &:active {
color: hsl(0,0%,75%); color: hsl(0,0%,75%);
} }
@ -560,7 +560,7 @@ pre code.hljs {
&.btn--quad { &.btn--quad {
padding: 10px 12px 9px; padding: 10px 12px 9px;
.icon { .icon {
margin-top: -1px; margin-top: -1px;
} }
@ -581,7 +581,7 @@ pre code.hljs {
&.btn--dropdown { &.btn--dropdown {
position: relative; position: relative;
select { select {
opacity: 0; opacity: 0;
width: 100%; width: 100%;
@ -627,13 +627,13 @@ pre code.hljs {
.visibility-change { .visibility-change {
/* /*
Interactive Visibility Change Classes: Interactive Visibility Change Classes:
<div class="visibility-change"> <div class="visibility-change">
<svg class="icon-marker" data-visible="active"><use xlink:href="#icon-marker" /></svg> <svg class="icon-marker" data-visible="active"><use xlink:href="#icon-marker" /></svg>
</div> </div>
Important: HTML Order active > hover > normal Important: HTML Order active > hover > normal
*/ */
@ -645,7 +645,7 @@ pre code.hljs {
&.is-active [data-visible=active] { &.is-active [data-visible=active] {
display: block; display: block;
& ~ [data-visible=normal] { & ~ [data-visible=normal] {
display: none display: none
} }
@ -653,7 +653,7 @@ pre code.hljs {
&:hover [data-visible=hover] { &:hover [data-visible=hover] {
display: block; display: block;
& ~ [data-visible=normal] { & ~ [data-visible=normal] {
display: none display: none
} }
@ -663,7 +663,7 @@ pre code.hljs {
.btn-group { .btn-group {
display: inline-flex; display: inline-flex;
flex-wrap: wrap; flex-wrap: wrap;
&.btn-group--full { &.btn-group--full {
display: flex; display: flex;
} }
@ -671,7 +671,7 @@ pre code.hljs {
& + .btn-group { & + .btn-group {
margin-top: 10px; margin-top: 10px;
padding-top: 10px; padding-top: 10px;
border-top: 1px solid hsl(240,2%,92%); border-top: 1px solid hsl(240,2%,92%);
} }
.btn + .btn { .btn + .btn {
@ -682,7 +682,7 @@ pre code.hljs {
padding: 6px 10px 5px; /* reporting main.eco */ padding: 6px 10px 5px; /* reporting main.eco */
display: inline-block; display: inline-block;
border-radius: 3px; border-radius: 3px;
&.is-selected { &.is-selected {
background: hsl(203,65%,55%); background: hsl(203,65%,55%);
color: white; color: white;
@ -695,11 +695,11 @@ pre code.hljs {
align-items: center; align-items: center;
position: relative; position: relative;
user-select: none; user-select: none;
.dropdown-menu { .dropdown-menu {
margin-bottom: 0; margin-bottom: 0;
} }
&.is-open .dropdown-menu { &.is-open .dropdown-menu {
display: block; display: block;
} }
@ -721,7 +721,7 @@ pre code.hljs {
height: 34px; height: 34px;
align-items: center; align-items: center;
line-height: 35px; line-height: 35px;
&.is-clickable { &.is-clickable {
background: hsl(203,65%,55%); background: hsl(203,65%,55%);
color: white; color: white;
@ -734,15 +734,15 @@ pre code.hljs {
&.is-blinking { &.is-blinking {
animation: pulsate 667ms ease-in-out infinite alternate; animation: pulsate 667ms ease-in-out infinite alternate;
} }
&:not(:last-child):not(:only-child) { &:not(:last-child):not(:only-child) {
border-right: none; border-right: none;
} }
&:first-child { &:first-child {
border-radius: 5px 0 0 5px; border-radius: 5px 0 0 5px;
} }
&:last-child { &:last-child {
border-radius: 0 5px 5px 0; border-radius: 0 5px 5px 0;
} }
@ -775,9 +775,9 @@ pre code.hljs {
line-height: 12px; line-height: 12px;
opacity: 0.5; opacity: 0.5;
position: relative; position: relative;
/* /*
border in its own layer to make it more border in its own layer to make it more
translucend but still depend on the currentColor translucend but still depend on the currentColor
*/ */
&:after { &:after {
@ -852,7 +852,7 @@ table {
.table { .table {
display: table; display: table;
small { small {
color: inherit; color: inherit;
} }
@ -876,7 +876,7 @@ table {
} }
td { td {
height: 40px; height: 40px;
} }
} }
.table th:not(.noTruncate) .table-column-title, .table th:not(.noTruncate) .table-column-title,
@ -924,7 +924,7 @@ th.align-right {
.table-hover > tbody > tr:hover > td { .table-hover > tbody > tr:hover > td {
background: white; background: white;
} }
.table-hover > tbody > tr:hover > th { .table-hover > tbody > tr:hover > th {
background: rgba(0,8,14,.015); background: rgba(0,8,14,.015);
} }
@ -938,7 +938,7 @@ th.align-right {
padding: 10px; padding: 10px;
margin-right: -10px; margin-right: -10px;
z-index: 1; z-index: 1;
&:after { &:after {
content: ""; content: "";
display: block; display: block;
@ -957,7 +957,7 @@ th.align-right {
.table tr.is-inactive { .table tr.is-inactive {
opacity: 0.5; opacity: 0.5;
text-decoration: line-through; text-decoration: line-through;
a { a {
color: #bbb; color: #bbb;
} }
@ -965,7 +965,7 @@ th.align-right {
.table tr.is-grayed-out { .table tr.is-grayed-out {
color: hsl(120,1%,77%); color: hsl(120,1%,77%);
.icon { .icon {
opacity: 0.33; opacity: 0.33;
} }
@ -1210,7 +1210,7 @@ h3 {
margin: 20px 0 8px; margin: 20px 0 8px;
color: hsl(207,7%,29%); color: hsl(207,7%,29%);
font-weight: normal; font-weight: normal;
.subtitle { .subtitle {
display: inline; display: inline;
font-size: 12px; font-size: 12px;
@ -1318,7 +1318,7 @@ fieldset > .form-group {
.form-group { .form-group {
margin-bottom: 16px; margin-bottom: 16px;
&.form-group--inactive { &.form-group--inactive {
opacity: 0.5; opacity: 0.5;
} }
@ -1335,7 +1335,7 @@ fieldset > .form-group {
.merge-group { .merge-group {
display: flex; display: flex;
align-items: stretch; align-items: stretch;
&.merge-group--inactive { &.merge-group--inactive {
} }
@ -1353,7 +1353,7 @@ fieldset > .form-group {
border-bottom: 1px solid #eee; border-bottom: 1px solid #eee;
} }
} }
.merge-target, .merge-target,
.merge-source { .merge-source {
flex: 1; flex: 1;
@ -1372,7 +1372,7 @@ fieldset > .form-group {
&:first-of-type { &:first-of-type {
margin-top: 6px; margin-top: 6px;
.merge-source, .merge-source,
.merge-target { .merge-target {
border-top: 1px solid #eee; border-top: 1px solid #eee;
@ -1382,7 +1382,7 @@ fieldset > .form-group {
&:last-of-type { &:last-of-type {
margin-bottom: 6px; margin-bottom: 6px;
.merge-source, .merge-source,
.merge-target { .merge-target {
border-bottom: 1px solid #eee; border-bottom: 1px solid #eee;
@ -1393,7 +1393,7 @@ fieldset > .form-group {
.merge-value { .merge-value {
margin-bottom: 3px; margin-bottom: 3px;
} }
.form-group { .form-group {
padding: 0; padding: 0;
} }
@ -1405,7 +1405,7 @@ fieldset > .form-group {
&.merge-group--multi { &.merge-group--multi {
.merge-value + .merge-value { .merge-value + .merge-value {
margin-top: 12px; margin-top: 12px;
} }
} }
} }
@ -1413,7 +1413,7 @@ fieldset > .form-group {
flex: 1; flex: 1;
align-self: flex-end; align-self: flex-end;
} }
.merge-control { .merge-control {
margin-bottom: 5px; margin-bottom: 5px;
height: 31px; height: 31px;
@ -1434,7 +1434,7 @@ fieldset > .form-group {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
.line-arrow { .line-arrow {
fill: #e6e6e6; fill: #e6e6e6;
} }
@ -1446,7 +1446,7 @@ fieldset > .form-group {
position: relative; position: relative;
display: flex; display: flex;
align-items: center; align-items: center;
label { label {
margin: 0; margin: 0;
} }
@ -1488,7 +1488,7 @@ fieldset > .form-group {
top: -2px; top: -2px;
position: relative; position: relative;
margin-left: auto; margin-left: auto;
.icon-help { .icon-help {
display: block; display: block;
} }
@ -1501,7 +1501,7 @@ fieldset > .form-group {
.form-group.formGroup--halfSize { .form-group.formGroup--halfSize {
width: 50%; width: 50%;
float: left; float: left;
.form-control { .form-control {
min-width: initial; min-width: initial;
} }
@ -1520,7 +1520,7 @@ fieldset > .form-group {
flex-shrink: 0; flex-shrink: 0;
} }
input[type="radio"], input[type="radio"],
input[type="checkbox"] { input[type="checkbox"] {
margin: 0; margin: 0;
} }
@ -1553,7 +1553,7 @@ textarea,
padding: 5px 8px 4px; padding: 5px 8px 4px;
height: 31px; height: 31px;
line-height: 20px; line-height: 20px;
&.form-control--multiline { &.form-control--multiline {
min-height: 31px; min-height: 31px;
} }
@ -1598,7 +1598,7 @@ input.time {
padding: 0 6px; padding: 0 6px;
line-height: 42px; line-height: 42px;
flex-shrink: 0; flex-shrink: 0;
&.form-control--small { &.form-control--small {
line-height: 31px; line-height: 31px;
} }
@ -1657,7 +1657,7 @@ select.form-control:not([multiple]) {
padding: 0; padding: 0;
line-height: inherit; line-height: inherit;
height: auto; height: auto;
&:focus { &:focus {
box-shadow: none; box-shadow: none;
} }
@ -1736,22 +1736,22 @@ input.has-error {
.controls--button { .controls--button {
display: flex; display: flex;
input, input,
.form-control { .form-control {
flex: 1; flex: 1;
border-right: none; border-right: none;
border-top-right-radius: 0; border-top-right-radius: 0;
border-bottom-right-radius: 0; border-bottom-right-radius: 0;
&:focus + .controls-button { &:focus + .controls-button {
.controls-button-inner { .controls-button-inner {
border-color: hsl(200,71%,59%); border-color: hsl(200,71%,59%);
} }
/*
fake the form-control outline /*
fake the form-control outline
*/ */
&:before { &:before {
@ -1852,7 +1852,7 @@ input.has-error {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@extend .zIndex-8; @extend .zIndex-8;
.modal-backdrop { .modal-backdrop {
bottom: 0; bottom: 0;
width: 200%; width: 200%;
@ -1915,7 +1915,7 @@ input.has-error {
.modal-control { .modal-control {
padding-left: 14px; padding-left: 14px;
padding-right: 14px; padding-right: 14px;
.btn.is-disabled { .btn.is-disabled {
opacity: 1; opacity: 1;
color: hsl(240,5%,83%); color: hsl(240,5%,83%);
@ -1958,7 +1958,7 @@ kbd {
display: flex; display: flex;
} }
.pagination > li > a, .pagination > li > a,
.pagination > li > span { .pagination > li > span {
padding: 0; padding: 0;
width: 31px; width: 31px;
@ -1966,11 +1966,11 @@ kbd {
border-color: #e5e5e5; border-color: #e5e5e5;
} }
.pagination > .active > a, .pagination > .active > a,
.pagination > .active > span, .pagination > .active > span,
.pagination > .active > a:hover, .pagination > .active > a:hover,
.pagination > .active > span:hover, .pagination > .active > span:hover,
.pagination > .active > a:focus, .pagination > .active > a:focus,
.pagination > .active > span:focus { .pagination > .active > span:focus {
background: #0F94D6; background: #0F94D6;
border-color: #0F94D6; border-color: #0F94D6;
@ -2001,7 +2001,7 @@ kbd {
.page-header-title { .page-header-title {
display: flex; display: flex;
align-items: center; align-items: center;
.zammad-switch { .zammad-switch {
margin-right: 9px; margin-right: 9px;
} }
@ -2017,7 +2017,7 @@ kbd {
justify-self: center; justify-self: center;
padding-left: 9px; padding-left: 9px;
margin: 0 auto; margin: 0 auto;
& + .page-header-meta { & + .page-header-meta {
margin-left: 0; margin-left: 0;
flex: none; flex: none;
@ -2031,7 +2031,7 @@ kbd {
justify-content: flex-end; justify-content: flex-end;
flex: 1; flex: 1;
min-width: 0; /* firefox flexbug */ min-width: 0; /* firefox flexbug */
.btn { .btn {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
@ -2081,7 +2081,7 @@ kbd {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.page-loading-label { .page-loading-label {
margin-left: 10px; margin-left: 10px;
margin-top: 1px; margin-top: 1px;
@ -2096,7 +2096,7 @@ kbd {
margin: 0; margin: 0;
color: #bcbcbc; color: #bcbcbc;
font-size: 12px; font-size: 12px;
&.help-block--center { &.help-block--center {
text-align: center; text-align: center;
} }
@ -2126,7 +2126,7 @@ kbd {
box-shadow: box-shadow:
0 8px 17px 0 rgba(0, 0, 0, 0.2), 0 8px 17px 0 rgba(0, 0, 0, 0.2),
0 6px 20px 0 rgba(0, 0, 0, 0.19); 0 6px 20px 0 rgba(0, 0, 0, 0.19);
label { label {
color: hsl(0,0%,60%); color: hsl(0,0%,60%);
} }
@ -2174,13 +2174,13 @@ kbd {
radial-gradient(circle at 2.58% 98.57%, #392e3e, transparent 51%), radial-gradient(circle at 2.58% 98.57%, #392e3e, transparent 51%),
radial-gradient(circle at 82.11% 97.15%, #5c404e, transparent 100%), radial-gradient(circle at 82.11% 97.15%, #5c404e, transparent 100%),
radial-gradient(circle at 50% 50%, #8b6b76, #8b6b76 100%); radial-gradient(circle at 50% 50%, #8b6b76, #8b6b76 100%);
a { a {
color: white; color: white;
} }
.hero-unit { .hero-unit {
box-shadow: box-shadow:
0 8px 17px 0 rgba(0, 0, 0, 0.1), 0 8px 17px 0 rgba(0, 0, 0, 0.1),
0 6px 20px 0 rgba(0, 0, 0, 0.09); 0 6px 20px 0 rgba(0, 0, 0, 0.09);
} }
@ -2227,7 +2227,7 @@ kbd {
bottom: 0; bottom: 0;
left: 0; left: 0;
right: 0; right: 0;
.icon-logo { .icon-logo {
margin-right: 8px; margin-right: 8px;
margin-top: -11px; margin-top: -11px;
@ -2299,12 +2299,12 @@ ol.tabs li {
flex: 1 1 auto; flex: 1 1 auto;
@extend .u-clickable; @extend .u-clickable;
white-space: nowrap; white-space: nowrap;
&.active { &.active {
color: white; color: white;
background: #444a4f; background: #444a4f;
box-shadow: none; box-shadow: none;
.tab-badge { .tab-badge {
color: hsl(204,3%,65%); color: hsl(204,3%,65%);
} }
@ -2316,19 +2316,19 @@ ol.tabs li {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
.arrow { .arrow {
margin-left: 10px; margin-left: 10px;
opacity: 0.75; opacity: 0.75;
} }
.icon { .icon {
fill: hsl(0,0%,70%); fill: hsl(0,0%,70%);
} }
&.active { &.active {
background: white; background: white;
.icon { .icon {
fill: #444a4f; fill: #444a4f;
opacity: 1; opacity: 1;
@ -2359,7 +2359,7 @@ ol.tabs li {
display: inline-flex; display: inline-flex;
margin-left: 0; margin-left: 0;
margin-right: 0; margin-right: 0;
.tab { .tab {
flex: none; flex: none;
} }
@ -2369,11 +2369,11 @@ ol.tabs li {
margin: 28px auto; margin: 28px auto;
font-size: 14px; font-size: 14px;
border-radius: 8px; border-radius: 8px;
.tab { .tab {
height: auto; height: auto;
padding: 10px 23px 9px; padding: 10px 23px 9px;
&:first-child { &:first-child {
border-radius: 8px 0 0 8px; border-radius: 8px 0 0 8px;
} }
@ -2443,7 +2443,7 @@ ol.tabs li {
align-items: center; align-items: center;
text-decoration: none; text-decoration: none;
width: calc(33.33% - 6px); width: calc(33.33% - 6px);
&.auth-provider--wide { &.auth-provider--wide {
padding-right: 25px; padding-right: 25px;
} }
@ -2538,11 +2538,11 @@ ol.tabs li {
} }
@keyframes rotateplane { @keyframes rotateplane {
0% { 0% {
transform: perspective(120px) rotateX(0deg) rotateY(0deg); transform: perspective(120px) rotateX(0deg) rotateY(0deg);
} 50% { } 50% {
transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg); transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg);
} 100% { } 100% {
transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg);
} }
} }
@ -2551,7 +2551,7 @@ ol.tabs li {
padding: 2px; padding: 2px;
margin: -2px 0; margin: -2px 0;
cursor: pointer; cursor: pointer;
/* :after technique for bigger click area */ /* :after technique for bigger click area */
&:after { &:after {
content: ""; content: "";
@ -2565,12 +2565,12 @@ ol.tabs li {
.icon-status { .icon-status {
fill: $ok-color; fill: $ok-color;
&.inline { &.inline {
margin-top: -3px; margin-top: -3px;
vertical-align: middle; vertical-align: middle;
} }
&.inactive { &.inactive {
fill: hsl(198,18%,86%); fill: hsl(198,18%,86%);
} }
@ -2611,7 +2611,7 @@ ol.tabs li {
*/ */
form { form {
margin: 0; margin: 0;
&.form--flexibleWidth .controls { &.form--flexibleWidth .controls {
display: table; display: table;
} }
@ -2622,7 +2622,7 @@ form {
display: flex; display: flex;
align-items: center; align-items: center;
margin-top: 10px; margin-top: 10px;
.btn + .btn:not(.align-right) { .btn + .btn:not(.align-right) {
margin-left: 20px; margin-left: 20px;
} }
@ -2688,13 +2688,13 @@ footer {
height: 41px; height: 41px;
display: none; display: none;
align-items: center; align-items: center;
.tabsHolder { .tabsHolder {
flex: 1; flex: 1;
margin-right: 20px; margin-right: 20px;
min-width: 0; /* Firefox bug fix */ min-width: 0; /* Firefox bug fix */
} }
.tabs { .tabs {
margin: 0; margin: 0;
position: relative; position: relative;
@ -2726,11 +2726,11 @@ footer {
min-width: $minWidth - $sidebarWidth - $navigationWidth; min-width: $minWidth - $sidebarWidth - $navigationWidth;
background: white; background: white;
z-index: 1; z-index: 1;
box-shadow: box-shadow:
0 -1px rgba(0,0,0,.05), 0 -1px rgba(0,0,0,.05),
0 -2px rgba(0,0,0,.03), 0 -2px rgba(0,0,0,.03),
0 -3px rgba(0,0,0,.01); 0 -3px rgba(0,0,0,.01);
@media only screen and (max-width: $largeScreenBreakpoint) { @media only screen and (max-width: $largeScreenBreakpoint) {
left: $navigationWidth; left: $navigationWidth;
min-width: $minWidth - $sidebarWidth; min-width: $minWidth - $sidebarWidth;
@ -2855,7 +2855,7 @@ footer {
justify-content: center; justify-content: center;
font-size: 16px; font-size: 16px;
color: hsl(0,0%,45%); color: hsl(0,0%,45%);
.icon { .icon {
margin-right: 10px; margin-right: 10px;
filter: grayscale(90%); filter: grayscale(90%);
@ -2931,7 +2931,7 @@ footer {
input:not(:checked) + label { // switch background input:not(:checked) + label { // switch background
background: hsl(202,68%,43%); background: hsl(202,68%,43%);
} }
label:after { label:after {
background: white; background: white;
} }
@ -5150,7 +5150,7 @@ footer {
border-radius: 3px; border-radius: 3px;
color: white; color: white;
border: none; border: none;
&.alert--info { &.alert--info {
background: hsl(203,65%,55%); background: hsl(203,65%,55%);
} }

View file

@ -9,14 +9,12 @@ class ObjectManagerAttributesController < ApplicationController
render json: { render json: {
objects: ObjectManager.list_frontend_objects, objects: ObjectManager.list_frontend_objects,
} }
#model_index_render(ObjectManager::Attribute, params)
end end
# GET /object_manager_attributes # GET /object_manager_attributes
def index def index
return if deny_if_not_role(Z_ROLENAME_ADMIN) return if deny_if_not_role(Z_ROLENAME_ADMIN)
render json: ObjectManager::Attribute.list_full render json: ObjectManager::Attribute.list_full
#model_index_render(ObjectManager::Attribute, params)
end end
# GET /object_manager_attributes/1 # GET /object_manager_attributes/1
@ -28,18 +26,68 @@ class ObjectManagerAttributesController < ApplicationController
# POST /object_manager_attributes # POST /object_manager_attributes
def create def create
return if deny_if_not_role(Z_ROLENAME_ADMIN) return if deny_if_not_role(Z_ROLENAME_ADMIN)
model_create_render(ObjectManager::Attribute, params) check_params
object_manager_attribute = ObjectManager::Attribute.add(
object: params[:object],
name: params[:name],
display: params[:display],
data_type: params[:data_type],
data_option: params[:data_option],
active: params[:active],
screens: params[:screens],
position: 1550,
editable: true,
)
render json: object_manager_attribute.attributes_with_associations, status: :created
end end
# PUT /object_manager_attributes/1 # PUT /object_manager_attributes/1
def update def update
return if deny_if_not_role(Z_ROLENAME_ADMIN) return if deny_if_not_role(Z_ROLENAME_ADMIN)
model_update_render(ObjectManager::Attribute, params) check_params
object_manager_attribute = ObjectManager::Attribute.add(
object: params[:object],
name: params[:name],
display: params[:display],
data_type: params[:data_type],
data_option: params[:data_option],
active: params[:active],
screens: params[:screens],
position: 1550,
editable: true,
)
render json: object_manager_attribute.attributes_with_associations, status: :ok
end end
# DELETE /object_manager_attributes/1 # DELETE /object_manager_attributes/1
def destroy def destroy
return if deny_if_not_role(Z_ROLENAME_ADMIN) return if deny_if_not_role(Z_ROLENAME_ADMIN)
model_destory_render(ObjectManager::Attribute, params) object_manager_attribute = ObjectManager::Attribute.find(params[:id])
ObjectManager::Attribute.remove(
object_lookup_id: object_manager_attribute.object_lookup_id,
name: object_manager_attribute.name,
)
model_destory_render_item
end
# POST /object_manager_attributes_discard_changes
def discard_changes
return if deny_if_not_role(Z_ROLENAME_ADMIN)
ObjectManager::Attribute.discard_changes
render json: {}, status: :ok
end
# POST /object_manager_attributes_execute_migrations
def execute_migrations
return if deny_if_not_role(Z_ROLENAME_ADMIN)
ObjectManager::Attribute.migration_execute
render json: {}, status: :ok
end
private
def check_params
return if !params[:data_option][:null].nil?
params[:data_option][:null] = true
end end
end end

View file

@ -13,8 +13,8 @@ class Link < ApplicationModel
=begin =begin
links = Link.list( links = Link.list(
:link_object => 'Ticket', link_object: 'Ticket',
:link_object_value => 1 link_object_value: 1
) )
=end =end
@ -55,19 +55,19 @@ class Link < ApplicationModel
=begin =begin
Link.add( Link.add(
:link_type => 'normal', link_type: 'normal',
:link_object_source => 'Ticket', link_object_source: 'Ticket',
:link_object_source_value => 6, link_object_source_value: 6,
:link_object_target => 'Ticket', link_object_target: 'Ticket',
:link_object_target_value => 31 link_object_target_value: 31
) )
Link.add( Link.add(
:link_types_id => 12, link_types_id: 12,
:link_object_source_id => 1, link_object_source_id: 1,
:link_object_source_value => 1, link_object_source_value: 1,
:link_object_target_id => 1, link_object_target_id: 1,
:link_object_target_value => 1 link_object_target_value: 1
) )
=end =end
@ -98,11 +98,11 @@ class Link < ApplicationModel
=begin =begin
Link.remove( Link.remove(
:link_type => 'normal', link_type: 'normal',
:link_object_source => 'Ticket', link_object_source: 'Ticket',
:link_object_source_value => 6, link_object_source_value: 6,
:link_object_target => 'Ticket', link_object_target: 'Ticket',
:link_object_target_value => 31 link_object_target_value: 31
) )
=end =end

View file

@ -6,7 +6,7 @@ class ObjectManager
list all backend managed object list all backend managed object
ObjectManager.list_objects() ObjectManager.list_objects
=end =end
@ -18,251 +18,12 @@ list all backend managed object
list all frontend managed object list all frontend managed object
ObjectManager.list_frontend_objects() ObjectManager.list_frontend_objects
=end =end
def self.list_frontend_objects def self.list_frontend_objects
%w(Ticket User Organization) #, 'Group' ] %w(Ticket User Organization Group)
end
end
class ObjectManager::Attribute < ApplicationModel
self.table_name = 'object_manager_attributes'
belongs_to :object_lookup, class_name: 'ObjectLookup'
validates :name, presence: true
store :screens
store :data_option
=begin
list of all attributes
result = ObjectManager::Attribute.list_full
result = [
{
name: 'some name',
display: '...',
}.
],
=end
def self.list_full
result = ObjectManager::Attribute.all
attributes = []
assets = {}
result.each {|item|
attribute = item.attributes
attribute[:object] = ObjectLookup.by_id( item.object_lookup_id )
attribute.delete('object_lookup_id')
attributes.push attribute
}
attributes
end
=begin
add a new attribute entry for an object
ObjectManager::Attribute.add(
:object => 'Ticket',
:name => 'group_id',
:frontend => 'Group',
:data_type => 'select',
:data_option => {
:relation => 'Group',
:relation_condition => { :access => 'rw' },
:multiple => false,
:null => true,
:translate => false,
},
:editable => false,
:active => true,
:screens => {
:create => {
'-all-' => {
:required => true,
},
},
:edit => {
:Agent => {
:required => true,
},
},
},
:pending_migration => false,
:position => 20,
:created_by_id => 1,
:updated_by_id => 1,
:created_at => '2014-06-04 10:00:00',
:updated_at => '2014-06-04 10:00:00',
)
=end
def self.add(data)
# lookups
if data[:object]
data[:object_lookup_id] = ObjectLookup.by_name( data[:object] )
end
data.delete(:object)
# check newest entry - is needed
result = ObjectManager::Attribute.find_by(
object_lookup_id: data[:object_lookup_id],
name: data[:name],
)
if result
return result.update_attributes(data)
end
# create history
ObjectManager::Attribute.create(data)
end
=begin
remove attribute entry for an object
ObjectManager::Attribute.remove(
object: 'Ticket',
name: 'group_id',
)
use "force: true" to delete also not editable fields
=end
def self.remove(data)
# lookups
if data[:object]
data[:object_lookup_id] = ObjectLookup.by_name(data[:object])
end
# check newest entry - is needed
result = ObjectManager::Attribute.find_by(
object_lookup_id: data[:object_lookup_id],
name: data[:name],
)
if !result
raise "ERROR: No such field #{data[:object]}.#{data[:name]}"
end
if !data[:force] && !result.editable
raise "ERROR: #{data[:object]}.#{data[:name]} can't be removed!"
end
result.destroy
end
=begin
get the attribute model based on object and name
attribute = ObjectManager::Attribute.get(
object: 'Ticket',
name: 'group_id',
)
=end
def self.get(data)
# lookups
if data[:object]
data[:object_lookup_id] = ObjectLookup.by_name( data[:object] )
end
ObjectManager::Attribute.find_by(
object_lookup_id: data[:object_lookup_id],
name: data[:name],
)
end
=begin
get user based list of object attributes
attribute_list = ObjectManager::Attribute.by_object('Ticket', user)
returns:
[
{ name: 'api_key', display: 'API KEY', tag: 'input', null: true, edit: true, maxlength: 32 },
{ name: 'api_ip_regexp', display: 'API IP RegExp', tag: 'input', null: true, edit: true },
{ name: 'api_ip_max', display: 'API IP Max', tag: 'input', null: true, edit: true },
]
=end
def self.by_object(object, user)
# lookups
if object
object_lookup_id = ObjectLookup.by_name(object)
end
# get attributes in right order
result = ObjectManager::Attribute.where(
object_lookup_id: object_lookup_id,
active: true,
).order('position ASC')
attributes = []
result.each {|item|
data = {
name: item.name,
display: item.display,
tag: item.data_type,
#:null => item.null,
}
if item.screens
data[:screen] = {}
item.screens.each {|screen, roles_options|
data[:screen][screen] = {}
roles_options.each {|role, options|
if role == '-all-'
data[:screen][screen] = options
elsif user && user.role?(role)
data[:screen][screen] = options
end
}
}
end
if item.data_option
data = data.merge(item.data_option.symbolize_keys)
end
attributes.push data
}
attributes
end
=begin
get user based list of object attributes as hash
attribute_list = ObjectManager::Attribute.by_object_as_hash('Ticket', user)
returns:
{
'api_key' => { name: 'api_key', display: 'API KEY', tag: 'input', null: true, edit: true, maxlength: 32 },
'api_ip_regexp' => { name: 'api_ip_regexp', display: 'API IP RegExp', tag: 'input', null: true, edit: true },
'api_ip_max' => { name: 'api_ip_max', display: 'API IP Max', tag: 'input', null: true, edit: true },
}
=end
def self.by_object_as_hash(object, user)
list = by_object(object, user)
hash = {}
list.each {|item|
hash[ item[:name] ] = item
}
hash
end end
end end

View file

@ -180,12 +180,18 @@ returns
tickets.each { |ticket| tickets.each { |ticket|
article_id = nil
article = Ticket::Article.last_customer_agent_article(ticket.id)
if article
article_id = article.id
end
# send notification # send notification
Transaction::BackgroundJob.run( Transaction::BackgroundJob.run(
object: 'Ticket', object: 'Ticket',
type: 'reminder_reached', type: 'reminder_reached',
object_id: ticket.id, object_id: ticket.id,
article_id: ticket.articles.last.id, article_id: article_id,
user_id: 1, user_id: 1,
) )
@ -220,13 +226,19 @@ returns
# get sla # get sla
sla = ticket.escalation_calculation_get_sla sla = ticket.escalation_calculation_get_sla
article_id = nil
article = Ticket::Article.last_customer_agent_article(ticket.id)
if article
article_id = article.id
end
# send escalation # send escalation
if ticket.escalation_time < Time.zone.now if ticket.escalation_time < Time.zone.now
Transaction::BackgroundJob.run( Transaction::BackgroundJob.run(
object: 'Ticket', object: 'Ticket',
type: 'escalation', type: 'escalation',
object_id: ticket.id, object_id: ticket.id,
article_id: ticket.articles.last.id, article_id: article_id,
user_id: 1, user_id: 1,
) )
result.push ticket result.push ticket
@ -238,7 +250,7 @@ returns
object: 'Ticket', object: 'Ticket',
type: 'escalation_warning', type: 'escalation_warning',
object_id: ticket.id, object_id: ticket.id,
article_id: ticket.articles.last.id, article_id: article_id,
user_id: 1, user_id: 1,
) )
result.push ticket result.push ticket

View file

@ -61,6 +61,11 @@ class Ticket::Article < ApplicationModel
article article
end end
def self.last_customer_agent_article(ticket_id)
sender = Ticket::Article::Sender.lookup(name: 'System')
Ticket::Article.where('ticket_id = ? AND sender_id NOT IN (?)', ticket_id, sender.id).order('created_at DESC').first
end
private private
# strip not wanted chars # strip not wanted chars

View file

@ -2,11 +2,13 @@ Zammad::Application.routes.draw do
api_path = Rails.configuration.api_path api_path = Rails.configuration.api_path
# object_manager # object_manager
match api_path + '/object_manager_attributes_list', to: 'object_manager_attributes#list', via: :get match api_path + '/object_manager_attributes_list', to: 'object_manager_attributes#list', via: :get
match api_path + '/object_manager_attributes', to: 'object_manager_attributes#index', via: :get match api_path + '/object_manager_attributes', to: 'object_manager_attributes#index', via: :get
match api_path + '/object_manager_attributes/:id', to: 'object_manager_attributes#show', via: :get match api_path + '/object_manager_attributes/:id', to: 'object_manager_attributes#show', via: :get
match api_path + '/object_manager_attributes', to: 'object_manager_attributes#create', via: :post match api_path + '/object_manager_attributes', to: 'object_manager_attributes#create', via: :post
match api_path + '/object_manager_attributes/:id', to: 'object_manager_attributes#update', via: :put match api_path + '/object_manager_attributes/:id', to: 'object_manager_attributes#update', via: :put
match api_path + '/object_manager_attributes/:id', to: 'object_manager_attributes#destroy', via: :delete match api_path + '/object_manager_attributes/:id', to: 'object_manager_attributes#destroy', via: :delete
match api_path + '/object_manager_attributes_discard_changes', to: 'object_manager_attributes#discard_changes', via: :post
match api_path + '/object_manager_attributes_execute_migrations', to: 'object_manager_attributes#execute_migrations', via: :post
end end

View file

@ -447,7 +447,9 @@ class CreateBase < ActiveRecord::Migration
t.column :editable, :boolean, null: false, default: true t.column :editable, :boolean, null: false, default: true
t.column :active, :boolean, null: false, default: true t.column :active, :boolean, null: false, default: true
t.column :screens, :string, limit: 2000, null: true t.column :screens, :string, limit: 2000, null: true
t.column :pending_migration, :boolean, null: false, default: true t.column :to_create, :boolean, null: false, default: true
t.column :to_migrate, :boolean, null: false, default: true
t.column :to_delete, :boolean, null: false, default: false
t.column :position, :integer, null: false t.column :position, :integer, null: false
t.column :created_by_id, :integer, null: false t.column :created_by_id, :integer, null: false
t.column :updated_by_id, :integer, null: false t.column :updated_by_id, :integer, null: false

View file

@ -2,6 +2,7 @@ class UpdateObjectManager3 < ActiveRecord::Migration
def up def up
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'User', object: 'User',
name: 'organization_id', name: 'organization_id',
display: 'Organization', display: 'Organization',
@ -29,13 +30,16 @@ class UpdateObjectManager3 < ActiveRecord::Migration
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 900, position: 900,
created_by_id: 1, created_by_id: 1,
updated_by_id: 1, updated_by_id: 1,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'Ticket', object: 'Ticket',
name: 'customer_id', name: 'customer_id',
display: 'Customer', display: 'Customer',
@ -60,7 +64,9 @@ class UpdateObjectManager3 < ActiveRecord::Migration
}, },
edit: {}, edit: {},
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 10, position: 10,
created_by_id: 1, created_by_id: 1,
updated_by_id: 1, updated_by_id: 1,

View file

@ -223,6 +223,7 @@ class UpdateOverview4 < ActiveRecord::Migration
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'Ticket', object: 'Ticket',
name: 'title', name: 'title',
display: 'Title', display: 'Title',
@ -243,11 +244,14 @@ class UpdateOverview4 < ActiveRecord::Migration
}, },
edit: {}, edit: {},
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 15, position: 15,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'Ticket', object: 'Ticket',
name: 'group_id', name: 'group_id',
display: 'Group', display: 'Group',
@ -275,7 +279,9 @@ class UpdateOverview4 < ActiveRecord::Migration
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 25, position: 25,
) )

View file

@ -2,6 +2,7 @@ class ObjectManagerUpdateUser < ActiveRecord::Migration
def up def up
UserInfo.current_user_id = 1 UserInfo.current_user_id = 1
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'User', object: 'User',
name: 'login', name: 'login',
display: 'Login', display: 'Login',
@ -26,11 +27,14 @@ class ObjectManagerUpdateUser < ActiveRecord::Migration
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 100, position: 100,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'User', object: 'User',
name: 'firstname', name: 'firstname',
display: 'Firstname', display: 'Firstname',
@ -70,11 +74,14 @@ class ObjectManagerUpdateUser < ActiveRecord::Migration
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 200, position: 200,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'User', object: 'User',
name: 'lastname', name: 'lastname',
display: 'Lastname', display: 'Lastname',
@ -114,11 +121,14 @@ class ObjectManagerUpdateUser < ActiveRecord::Migration
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 300, position: 300,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'User', object: 'User',
name: 'email', name: 'email',
display: 'Email', display: 'Email',
@ -158,11 +168,14 @@ class ObjectManagerUpdateUser < ActiveRecord::Migration
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 400, position: 400,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'User', object: 'User',
name: 'web', name: 'web',
display: 'Web', display: 'Web',
@ -190,11 +203,14 @@ class ObjectManagerUpdateUser < ActiveRecord::Migration
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 500, position: 500,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'User', object: 'User',
name: 'phone', name: 'phone',
display: 'Phone', display: 'Phone',
@ -222,11 +238,14 @@ class ObjectManagerUpdateUser < ActiveRecord::Migration
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 600, position: 600,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'User', object: 'User',
name: 'mobile', name: 'mobile',
display: 'Mobile', display: 'Mobile',
@ -254,11 +273,14 @@ class ObjectManagerUpdateUser < ActiveRecord::Migration
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 700, position: 700,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'User', object: 'User',
name: 'fax', name: 'fax',
display: 'Fax', display: 'Fax',
@ -286,11 +308,14 @@ class ObjectManagerUpdateUser < ActiveRecord::Migration
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 800, position: 800,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'User', object: 'User',
name: 'organization_id', name: 'organization_id',
display: 'Organization', display: 'Organization',
@ -323,11 +348,14 @@ class ObjectManagerUpdateUser < ActiveRecord::Migration
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 900, position: 900,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'User', object: 'User',
name: 'department', name: 'department',
display: 'Department', display: 'Department',
@ -355,11 +383,14 @@ class ObjectManagerUpdateUser < ActiveRecord::Migration
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 1000, position: 1000,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'User', object: 'User',
name: 'street', name: 'street',
display: 'Street', display: 'Street',
@ -386,11 +417,14 @@ class ObjectManagerUpdateUser < ActiveRecord::Migration
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 1100, position: 1100,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'User', object: 'User',
name: 'zip', name: 'zip',
display: 'Zip', display: 'Zip',
@ -418,11 +452,14 @@ class ObjectManagerUpdateUser < ActiveRecord::Migration
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 1200, position: 1200,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'User', object: 'User',
name: 'city', name: 'city',
display: 'City', display: 'City',
@ -450,11 +487,14 @@ class ObjectManagerUpdateUser < ActiveRecord::Migration
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 1300, position: 1300,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'User', object: 'User',
name: 'address', name: 'address',
display: 'Address', display: 'Address',
@ -482,11 +522,14 @@ class ObjectManagerUpdateUser < ActiveRecord::Migration
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 1350, position: 1350,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'User', object: 'User',
name: 'password', name: 'password',
display: 'Password', display: 'Password',
@ -515,11 +558,14 @@ class ObjectManagerUpdateUser < ActiveRecord::Migration
}, },
view: {} view: {}
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 1400, position: 1400,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'User', object: 'User',
name: 'vip', name: 'vip',
display: 'VIP', display: 'VIP',
@ -551,11 +597,14 @@ class ObjectManagerUpdateUser < ActiveRecord::Migration
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 1490, position: 1490,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'User', object: 'User',
name: 'note', name: 'note',
display: 'Note', display: 'Note',
@ -587,16 +636,20 @@ class ObjectManagerUpdateUser < ActiveRecord::Migration
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 1500, position: 1500,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'User', object: 'User',
name: 'role_ids', name: 'role_ids',
display: 'Roles', display: 'Roles',
data_type: 'checkbox', data_type: 'checkbox',
data_option: { data_option: {
default: '',
multiple: true, multiple: true,
null: false, null: false,
relation: 'Role', relation: 'Role',
@ -618,51 +671,20 @@ class ObjectManagerUpdateUser < ActiveRecord::Migration
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 1600, position: 1600,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
object: 'User', force: true,
name: 'group_ids',
display: 'Groups',
data_type: 'checkbox',
data_option: {
multiple: true,
null: true,
relation: 'Group',
},
editable: false,
active: true,
screens: {
signup: {},
invite_agent: {
'-all-' => {
null: false,
},
},
invite_customer: {},
edit: {
Admin: {
null: true,
},
},
view: {
'-all-' => {
shown: false,
},
},
},
pending_migration: false,
position: 1700,
)
ObjectManager::Attribute.add(
object: 'User', object: 'User',
name: 'active', name: 'active',
display: 'Active', display: 'Active',
data_type: 'active', data_type: 'active',
data_option: { data_option: {
null: true,
default: true, default: true,
}, },
editable: false, editable: false,
@ -682,7 +704,9 @@ class ObjectManagerUpdateUser < ActiveRecord::Migration
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 1800, position: 1800,
) )

View file

@ -2,6 +2,7 @@
class EmailTicketCc < ActiveRecord::Migration class EmailTicketCc < ActiveRecord::Migration
def up def up
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'Ticket', object: 'Ticket',
name: 'cc', name: 'cc',
display: 'Cc', display: 'Cc',
@ -22,7 +23,9 @@ class EmailTicketCc < ActiveRecord::Migration
create_middle: {}, create_middle: {},
edit: {} edit: {}
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 11, position: 11,
created_by_id: 1, created_by_id: 1,
updated_by_id: 1, updated_by_id: 1,

View file

@ -2,11 +2,13 @@
class OnlyOneGroup < ActiveRecord::Migration class OnlyOneGroup < ActiveRecord::Migration
def up def up
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'Ticket', object: 'Ticket',
name: 'group_id', name: 'group_id',
display: 'Group', display: 'Group',
data_type: 'select', data_type: 'select',
data_option: { data_option: {
default: '',
relation: 'Group', relation: 'Group',
relation_condition: { access: 'rw' }, relation_condition: { access: 'rw' },
nulloption: true, nulloption: true,
@ -30,17 +32,21 @@ class OnlyOneGroup < ActiveRecord::Migration
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 25, position: 25,
created_by_id: 1, created_by_id: 1,
updated_by_id: 1, updated_by_id: 1,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'User', object: 'User',
name: 'group_ids', name: 'group_ids',
display: 'Groups', display: 'Groups',
data_type: 'checkbox', data_type: 'checkbox',
data_option: { data_option: {
default: '',
multiple: true, multiple: true,
null: true, null: true,
relation: 'Group', relation: 'Group',
@ -67,12 +73,15 @@ class OnlyOneGroup < ActiveRecord::Migration
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 1700, position: 1700,
created_by_id: 1, created_by_id: 1,
updated_by_id: 1, updated_by_id: 1,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'User', object: 'User',
name: 'street', name: 'street',
display: 'Street', display: 'Street',
@ -99,13 +108,16 @@ class OnlyOneGroup < ActiveRecord::Migration
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 1100, position: 1100,
created_by_id: 1, created_by_id: 1,
updated_by_id: 1, updated_by_id: 1,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'User', object: 'User',
name: 'zip', name: 'zip',
display: 'Zip', display: 'Zip',
@ -133,13 +145,16 @@ class OnlyOneGroup < ActiveRecord::Migration
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 1200, position: 1200,
created_by_id: 1, created_by_id: 1,
updated_by_id: 1, updated_by_id: 1,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'User', object: 'User',
name: 'city', name: 'city',
display: 'City', display: 'City',
@ -167,13 +182,16 @@ class OnlyOneGroup < ActiveRecord::Migration
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 1300, position: 1300,
created_by_id: 1, created_by_id: 1,
updated_by_id: 1, updated_by_id: 1,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'User', object: 'User',
name: 'address', name: 'address',
display: 'Address', display: 'Address',
@ -201,7 +219,9 @@ class OnlyOneGroup < ActiveRecord::Migration
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 1350, position: 1350,
created_by_id: 1, created_by_id: 1,
updated_by_id: 1, updated_by_id: 1,

View file

@ -0,0 +1,60 @@
class RoleGroupRemove < ActiveRecord::Migration
def up
# return if it's a new setup
return if !Setting.find_by(name: 'system_init_done')
object_lookup_id = ObjectLookup.by_name('User')
record = ObjectManager::Attribute.find_by(
object_lookup_id: object_lookup_id,
name: 'role_ids',
)
record.destroy if record
record = ObjectManager::Attribute.find_by(
object_lookup_id: object_lookup_id,
name: 'group_ids',
)
record.destroy if record
ObjectManager::Attribute.add(
force: true,
object: 'User',
name: 'role_ids',
display: 'Permissions',
data_type: 'user_permission',
data_option: {
null: false,
item_class: 'checkbox',
},
editable: false,
active: true,
screens: {
signup: {},
invite_agent: {
'-all-' => {
null: false,
hideMode: {
rolesSelected: ['Agent'],
rolesNot: ['Customer'],
}
},
},
invite_customer: {},
edit: {
Admin: {
null: true,
},
},
view: {
'-all-' => {
shown: false,
},
},
},
to_create: false,
to_migrate: false,
to_delete: false,
position: 1600,
updated_by_id: 1,
created_by_id: 1,
)
end
end

View file

@ -0,0 +1,302 @@
class UpdateObjectManager < ActiveRecord::Migration
def up
# return if it's a new setup
return if !Setting.find_by(name: 'system_init_done')
add_column :object_manager_attributes, :to_create, :boolean, null: false, default: true
add_column :object_manager_attributes, :to_migrate, :boolean, null: false, default: true
add_column :object_manager_attributes, :to_delete, :boolean, null: false, default: false
ObjectManager::Attribute.add(
force: true,
object: 'Group',
name: 'name',
display: 'Name',
data_type: 'input',
data_option: {
type: 'text',
maxlength: 150,
null: false,
},
editable: false,
active: true,
screens: {
create: {
'-all-' => {
null: false,
},
},
edit: {
'-all-' => {
null: false,
},
},
view: {
'-all-' => {
shown: true,
},
},
},
to_create: false,
to_migrate: false,
to_delete: false,
position: 200,
created_by_id: 1,
updated_by_id: 1,
)
ObjectManager::Attribute.add(
force: true,
object: 'Group',
name: 'assignment_timeout',
display: 'Assignment Timeout',
data_type: 'integer',
data_option: {
maxlength: 150,
null: true,
note: 'Assignment timeout in minutes if assigned agent is not working on it. Ticket will be shown as unassigend.',
min: 0,
max: 999_999,
},
editable: false,
active: true,
screens: {
create: {
'-all-' => {
null: true,
},
},
edit: {
'-all-' => {
null: true,
},
},
},
to_create: false,
to_migrate: false,
to_delete: false,
position: 300,
created_by_id: 1,
updated_by_id: 1,
)
ObjectManager::Attribute.add(
force: true,
object: 'Group',
name: 'follow_up_possible',
display: 'Follow up possible',
data_type: 'select',
data_option: {
default: 'yes',
options: {
yes: 'yes',
reject: 'reject follow up/do not reopen Ticket',
new_ticket: 'do not reopen Ticket but create new Ticket'
},
null: false,
note: 'Follow up for closed ticket possible or not.',
translate: true
},
editable: false,
active: true,
screens: {
create: {
'-all-' => {
null: true,
},
},
edit: {
'-all-' => {
null: true,
},
},
},
to_create: false,
to_migrate: false,
to_delete: false,
position: 400,
created_by_id: 1,
updated_by_id: 1,
)
ObjectManager::Attribute.add(
force: true,
object: 'Group',
name: 'follow_up_assignment',
display: 'Assign Follow Ups',
data_type: 'select',
data_option: {
default: 'yes',
options: {
true: 'yes',
false: 'no',
},
null: false,
note: 'Assign follow up to latest agent again.',
translate: true
},
editable: false,
active: true,
screens: {
create: {
'-all-' => {
null: true,
},
},
edit: {
'-all-' => {
null: true,
},
},
},
to_create: false,
to_migrate: false,
to_delete: false,
position: 500,
created_by_id: 1,
updated_by_id: 1,
)
ObjectManager::Attribute.add(
force: true,
object: 'Group',
name: 'email_address_id',
display: 'Email',
data_type: 'select',
data_option: {
default: '',
multiple: false,
null: true,
relation: 'EmailAddress',
nulloption: true,
do_not_log: true,
},
editable: false,
active: true,
screens: {
create: {
'-all-' => {
null: true,
},
},
edit: {
'-all-' => {
null: true,
},
},
},
to_create: false,
to_migrate: false,
to_delete: false,
position: 600,
created_by_id: 1,
updated_by_id: 1,
)
ObjectManager::Attribute.add(
force: true,
object: 'Group',
name: 'signature_id',
display: 'Signature',
data_type: 'select',
data_option: {
default: '',
multiple: false,
null: true,
relation: 'Signature',
nulloption: true,
do_not_log: true,
},
editable: false,
active: true,
screens: {
create: {
'-all-' => {
null: true,
},
},
edit: {
'-all-' => {
null: true,
},
},
},
to_create: false,
to_migrate: false,
to_delete: false,
position: 600,
created_by_id: 1,
updated_by_id: 1,
)
ObjectManager::Attribute.add(
force: true,
object: 'Group',
name: 'note',
display: 'Note',
data_type: 'richtext',
data_option: {
type: 'text',
maxlength: 250,
null: true,
note: 'Notes are visible to agents only, never to customers.',
},
editable: false,
active: true,
screens: {
create: {
'-all-' => {
null: true,
},
},
edit: {
'-all-' => {
null: true,
},
},
view: {
'-all-' => {
shown: true,
},
},
},
to_create: false,
to_migrate: false,
to_delete: false,
position: 1500,
created_by_id: 1,
updated_by_id: 1,
)
ObjectManager::Attribute.add(
force: true,
object: 'Group',
name: 'active',
display: 'Active',
data_type: 'active',
data_option: {
null: true,
default: true,
},
editable: false,
active: true,
screens: {
create: {
'-all-' => {
null: true,
},
},
edit: {
Admin: {
null: false,
},
},
view: {
'-all-' => {
shown: false,
},
},
},
to_create: false,
to_migrate: false,
to_delete: false,
position: 1800,
created_by_id: 1,
updated_by_id: 1,
)
end
end

View file

@ -2481,6 +2481,7 @@ Network::Item::Comment.create(
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'Ticket', object: 'Ticket',
name: 'title', name: 'title',
display: 'Title', display: 'Title',
@ -2501,11 +2502,14 @@ ObjectManager::Attribute.add(
}, },
edit: {}, edit: {},
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 15, position: 15,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'Ticket', object: 'Ticket',
name: 'customer_id', name: 'customer_id',
display: 'Customer', display: 'Customer',
@ -2530,15 +2534,19 @@ ObjectManager::Attribute.add(
}, },
edit: {}, edit: {},
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 10, position: 10,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'Ticket', object: 'Ticket',
name: 'type', name: 'type',
display: 'Type', display: 'Type',
data_type: 'select', data_type: 'select',
data_option: { data_option: {
default: '',
options: { options: {
'Incident' => 'Incident', 'Incident' => 'Incident',
'Problem' => 'Problem', 'Problem' => 'Problem',
@ -2549,7 +2557,7 @@ ObjectManager::Attribute.add(
null: true, null: true,
translate: true, translate: true,
}, },
editable: false, editable: true,
active: false, active: false,
screens: { screens: {
create_middle: { create_middle: {
@ -2564,15 +2572,19 @@ ObjectManager::Attribute.add(
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 20, position: 20,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'Ticket', object: 'Ticket',
name: 'group_id', name: 'group_id',
display: 'Group', display: 'Group',
data_type: 'select', data_type: 'select',
data_option: { data_option: {
default: '',
relation: 'Group', relation: 'Group',
relation_condition: { access: 'rw' }, relation_condition: { access: 'rw' },
nulloption: true, nulloption: true,
@ -2596,15 +2608,19 @@ ObjectManager::Attribute.add(
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 25, position: 25,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'Ticket', object: 'Ticket',
name: 'owner_id', name: 'owner_id',
display: 'Owner', display: 'Owner',
data_type: 'select', data_type: 'select',
data_option: { data_option: {
default: '',
relation: 'User', relation: 'User',
relation_condition: { roles: 'Agent' }, relation_condition: { roles: 'Agent' },
nulloption: true, nulloption: true,
@ -2627,10 +2643,13 @@ ObjectManager::Attribute.add(
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 30, position: 30,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'Ticket', object: 'Ticket',
name: 'state_id', name: 'state_id',
display: 'State', display: 'State',
@ -2674,10 +2693,13 @@ ObjectManager::Attribute.add(
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 40, position: 40,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'Ticket', object: 'Ticket',
name: 'pending_time', name: 'pending_time',
display: 'Pending till', display: 'Pending till',
@ -2710,10 +2732,13 @@ ObjectManager::Attribute.add(
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 41, position: 41,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'Ticket', object: 'Ticket',
name: 'priority_id', name: 'priority_id',
display: 'Priority', display: 'Priority',
@ -2742,11 +2767,14 @@ ObjectManager::Attribute.add(
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 80, position: 80,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'Ticket', object: 'Ticket',
name: 'tags', name: 'tags',
display: 'Tags', display: 'Tags',
@ -2766,11 +2794,14 @@ ObjectManager::Attribute.add(
}, },
edit: {}, edit: {},
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 900, position: 900,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'TicketArticle', object: 'TicketArticle',
name: 'type_id', name: 'type_id',
display: 'Type', display: 'Type',
@ -2793,11 +2824,14 @@ ObjectManager::Attribute.add(
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 100, position: 100,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'TicketArticle', object: 'TicketArticle',
name: 'internal', name: 'internal',
display: 'Visibility', display: 'Visibility',
@ -2820,11 +2854,14 @@ ObjectManager::Attribute.add(
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 200, position: 200,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'TicketArticle', object: 'TicketArticle',
name: 'to', name: 'to',
display: 'To', display: 'To',
@ -2844,10 +2881,13 @@ ObjectManager::Attribute.add(
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 300, position: 300,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'TicketArticle', object: 'TicketArticle',
name: 'cc', name: 'cc',
display: 'Cc', display: 'Cc',
@ -2868,11 +2908,14 @@ ObjectManager::Attribute.add(
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 400, position: 400,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'TicketArticle', object: 'TicketArticle',
name: 'body', name: 'body',
display: 'Text', display: 'Text',
@ -2901,11 +2944,14 @@ ObjectManager::Attribute.add(
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 600, position: 600,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'User', object: 'User',
name: 'login', name: 'login',
display: 'Login', display: 'Login',
@ -2930,11 +2976,14 @@ ObjectManager::Attribute.add(
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 100, position: 100,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'User', object: 'User',
name: 'firstname', name: 'firstname',
display: 'Firstname', display: 'Firstname',
@ -2974,11 +3023,14 @@ ObjectManager::Attribute.add(
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 200, position: 200,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'User', object: 'User',
name: 'lastname', name: 'lastname',
display: 'Lastname', display: 'Lastname',
@ -3018,11 +3070,14 @@ ObjectManager::Attribute.add(
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 300, position: 300,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'User', object: 'User',
name: 'email', name: 'email',
display: 'Email', display: 'Email',
@ -3062,11 +3117,14 @@ ObjectManager::Attribute.add(
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 400, position: 400,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'User', object: 'User',
name: 'web', name: 'web',
display: 'Web', display: 'Web',
@ -3094,11 +3152,14 @@ ObjectManager::Attribute.add(
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 500, position: 500,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'User', object: 'User',
name: 'phone', name: 'phone',
display: 'Phone', display: 'Phone',
@ -3126,11 +3187,14 @@ ObjectManager::Attribute.add(
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 600, position: 600,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'User', object: 'User',
name: 'mobile', name: 'mobile',
display: 'Mobile', display: 'Mobile',
@ -3158,11 +3222,14 @@ ObjectManager::Attribute.add(
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 700, position: 700,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'User', object: 'User',
name: 'fax', name: 'fax',
display: 'Fax', display: 'Fax',
@ -3190,11 +3257,14 @@ ObjectManager::Attribute.add(
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 800, position: 800,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'User', object: 'User',
name: 'organization_id', name: 'organization_id',
display: 'Organization', display: 'Organization',
@ -3227,11 +3297,14 @@ ObjectManager::Attribute.add(
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 900, position: 900,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'User', object: 'User',
name: 'department', name: 'department',
display: 'Department', display: 'Department',
@ -3242,7 +3315,7 @@ ObjectManager::Attribute.add(
null: true, null: true,
item_class: 'formGroup--halfSize', item_class: 'formGroup--halfSize',
}, },
editable: false, editable: true,
active: true, active: true,
screens: { screens: {
signup: {}, signup: {},
@ -3259,11 +3332,14 @@ ObjectManager::Attribute.add(
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 1000, position: 1000,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'User', object: 'User',
name: 'street', name: 'street',
display: 'Street', display: 'Street',
@ -3290,11 +3366,14 @@ ObjectManager::Attribute.add(
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 1100, position: 1100,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'User', object: 'User',
name: 'zip', name: 'zip',
display: 'Zip', display: 'Zip',
@ -3322,11 +3401,14 @@ ObjectManager::Attribute.add(
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 1200, position: 1200,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'User', object: 'User',
name: 'city', name: 'city',
display: 'City', display: 'City',
@ -3354,11 +3436,14 @@ ObjectManager::Attribute.add(
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 1300, position: 1300,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'User', object: 'User',
name: 'address', name: 'address',
display: 'Address', display: 'Address',
@ -3386,11 +3471,14 @@ ObjectManager::Attribute.add(
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 1350, position: 1350,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'User', object: 'User',
name: 'password', name: 'password',
display: 'Password', display: 'Password',
@ -3419,11 +3507,14 @@ ObjectManager::Attribute.add(
}, },
view: {} view: {}
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 1400, position: 1400,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'User', object: 'User',
name: 'vip', name: 'vip',
display: 'VIP', display: 'VIP',
@ -3455,11 +3546,14 @@ ObjectManager::Attribute.add(
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 1490, position: 1490,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'User', object: 'User',
name: 'note', name: 'note',
display: 'Note', display: 'Note',
@ -3491,50 +3585,21 @@ ObjectManager::Attribute.add(
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 1500, position: 1500,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'User', object: 'User',
name: 'role_ids', name: 'role_ids',
display: 'Roles', display: 'Permissions',
data_type: 'checkbox', data_type: 'user_permission',
data_option: { data_option: {
multiple: true,
null: false, null: false,
relation: 'Role', item_class: 'checkbox',
},
editable: false,
active: true,
screens: {
signup: {},
invite_agent: {},
invite_customer: {},
edit: {
Admin: {
null: false,
},
},
view: {
'-all-' => {
shown: false,
},
},
},
pending_migration: false,
position: 1600,
)
ObjectManager::Attribute.add(
object: 'User',
name: 'group_ids',
display: 'Groups',
data_type: 'checkbox',
data_option: {
multiple: true,
null: true,
relation: 'Group',
}, },
editable: false, editable: false,
active: true, active: true,
@ -3543,7 +3608,10 @@ ObjectManager::Attribute.add(
invite_agent: { invite_agent: {
'-all-' => { '-all-' => {
null: false, null: false,
only_shown_if_selectable: true, hideMode: {
rolesSelected: ['Agent'],
rolesNot: ['Customer'],
}
}, },
}, },
invite_customer: {}, invite_customer: {},
@ -3558,16 +3626,20 @@ ObjectManager::Attribute.add(
}, },
}, },
}, },
pending_migration: false, to_create: false,
position: 1700, to_migrate: false,
to_delete: false,
position: 1600,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'User', object: 'User',
name: 'active', name: 'active',
display: 'Active', display: 'Active',
data_type: 'active', data_type: 'active',
data_option: { data_option: {
null: true,
default: true, default: true,
}, },
editable: false, editable: false,
@ -3587,11 +3659,14 @@ ObjectManager::Attribute.add(
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 1800, position: 1800,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'Organization', object: 'Organization',
name: 'name', name: 'name',
display: 'Name', display: 'Name',
@ -3616,17 +3691,19 @@ ObjectManager::Attribute.add(
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 200, position: 200,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'Organization', object: 'Organization',
name: 'shared', name: 'shared',
display: 'Shared organization', display: 'Shared organization',
data_type: 'boolean', data_type: 'boolean',
data_option: { data_option: {
maxlength: 250,
null: true, null: true,
default: true, default: true,
note: 'Customers in the organization can view each other items.', note: 'Customers in the organization can view each other items.',
@ -3650,11 +3727,14 @@ ObjectManager::Attribute.add(
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 1400, position: 1400,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'Organization', object: 'Organization',
name: 'note', name: 'note',
display: 'Note', display: 'Note',
@ -3679,16 +3759,20 @@ ObjectManager::Attribute.add(
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 1500, position: 1500,
) )
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
force: true,
object: 'Organization', object: 'Organization',
name: 'active', name: 'active',
display: 'Active', display: 'Active',
data_type: 'active', data_type: 'active',
data_option: { data_option: {
null: true,
default: true, default: true,
}, },
editable: false, editable: false,
@ -3705,7 +3789,291 @@ ObjectManager::Attribute.add(
}, },
}, },
}, },
pending_migration: false, to_create: false,
to_migrate: false,
to_delete: false,
position: 1800,
)
ObjectManager::Attribute.add(
force: true,
object: 'Group',
name: 'name',
display: 'Name',
data_type: 'input',
data_option: {
type: 'text',
maxlength: 150,
null: false,
},
editable: false,
active: true,
screens: {
create: {
'-all-' => {
null: false,
},
},
edit: {
'-all-' => {
null: false,
},
},
view: {
'-all-' => {
shown: true,
},
},
},
to_create: false,
to_migrate: false,
to_delete: false,
position: 200,
)
ObjectManager::Attribute.add(
force: true,
object: 'Group',
name: 'assignment_timeout',
display: 'Assignment Timeout',
data_type: 'integer',
data_option: {
maxlength: 150,
null: true,
note: 'Assignment timeout in minutes if assigned agent is not working on it. Ticket will be shown as unassigend.',
min: 0,
max: 999_999,
},
editable: false,
active: true,
screens: {
create: {
'-all-' => {
null: true,
},
},
edit: {
'-all-' => {
null: true,
},
},
},
to_create: false,
to_migrate: false,
to_delete: false,
position: 300,
)
ObjectManager::Attribute.add(
force: true,
object: 'Group',
name: 'follow_up_possible',
display: 'Follow up possible',
data_type: 'select',
data_option: {
default: 'yes',
options: {
yes: 'yes',
reject: 'reject follow up/do not reopen Ticket',
new_ticket: 'do not reopen Ticket but create new Ticket'
},
null: false,
note: 'Follow up for closed ticket possible or not.',
translate: true
},
editable: false,
active: true,
screens: {
create: {
'-all-' => {
null: true,
},
},
edit: {
'-all-' => {
null: true,
},
},
},
to_create: false,
to_migrate: false,
to_delete: false,
position: 400,
)
ObjectManager::Attribute.add(
force: true,
object: 'Group',
name: 'follow_up_assignment',
display: 'Assign Follow Ups',
data_type: 'select',
data_option: {
default: 'yes',
options: {
true: 'yes',
false: 'no',
},
null: false,
note: 'Assign follow up to latest agent again.',
translate: true
},
editable: false,
active: true,
screens: {
create: {
'-all-' => {
null: true,
},
},
edit: {
'-all-' => {
null: true,
},
},
},
to_create: false,
to_migrate: false,
to_delete: false,
position: 500,
)
ObjectManager::Attribute.add(
force: true,
object: 'Group',
name: 'email_address_id',
display: 'Email',
data_type: 'select',
data_option: {
default: '',
multiple: false,
null: true,
relation: 'EmailAddress',
nulloption: true,
do_not_log: true,
},
editable: false,
active: true,
screens: {
create: {
'-all-' => {
null: true,
},
},
edit: {
'-all-' => {
null: true,
},
},
},
to_create: false,
to_migrate: false,
to_delete: false,
position: 600,
)
ObjectManager::Attribute.add(
force: true,
object: 'Group',
name: 'signature_id',
display: 'Signature',
data_type: 'select',
data_option: {
default: '',
multiple: false,
null: true,
relation: 'Signature',
nulloption: true,
do_not_log: true,
},
editable: false,
active: true,
screens: {
create: {
'-all-' => {
null: true,
},
},
edit: {
'-all-' => {
null: true,
},
},
},
to_create: false,
to_migrate: false,
to_delete: false,
position: 600,
)
ObjectManager::Attribute.add(
force: true,
object: 'Group',
name: 'note',
display: 'Note',
data_type: 'richtext',
data_option: {
type: 'text',
maxlength: 250,
null: true,
note: 'Notes are visible to agents only, never to customers.',
},
editable: false,
active: true,
screens: {
create: {
'-all-' => {
null: true,
},
},
edit: {
'-all-' => {
null: true,
},
},
view: {
'-all-' => {
shown: true,
},
},
},
to_create: false,
to_migrate: false,
to_delete: false,
position: 1500,
)
ObjectManager::Attribute.add(
force: true,
object: 'Group',
name: 'active',
display: 'Active',
data_type: 'active',
data_option: {
null: true,
default: true,
},
editable: false,
active: true,
screens: {
create: {
'-all-' => {
null: true,
},
},
edit: {
Admin: {
null: false,
},
},
view: {
'-all-' => {
shown: false,
},
},
},
to_create: false,
to_migrate: false,
to_delete: false,
position: 1800, position: 1800,
) )
@ -3858,15 +4226,15 @@ Trigger.create_or_update(
}, },
perform: { perform: {
'notification.email' => { 'notification.email' => {
'body' => '<p>Your request (#{config.ticket_hook}#{ticket.number}) has been received and will be reviewed by our support staff.<p> 'body' => '<div>Your request (#{config.ticket_hook}#{ticket.number}) has been received and will be reviewed by our support staff.</div>
<br/> <br/>
<p>To provide additional information, please reply to this email or click on the following link: <div>To provide additional information, please reply to this email or click on the following link:
<a href="#{config.http_type}://#{config.fqdn}/#ticket/zoom/#{ticket.id}">#{config.http_type}://#{config.fqdn}/#ticket/zoom/#{ticket.id}</a> <a href="#{config.http_type}://#{config.fqdn}/#ticket/zoom/#{ticket.id}">#{config.http_type}://#{config.fqdn}/#ticket/zoom/#{ticket.id}</a>
</p> </div>
<br/> <br/>
<p>Your #{config.product_name} Team</p> <div>Your #{config.product_name} Team</div>
<br/> <br/>
<p><i><a href="http://zammad.com">Zammad</a>, your customer support system</i></p>', <div><i><a href="http://zammad.com">Zammad</a>, your customer support system</i></div>',
'recipient' => 'ticket_customer', 'recipient' => 'ticket_customer',
'subject' => 'Thanks for your inquiry (#{ticket.title})', 'subject' => 'Thanks for your inquiry (#{ticket.title})',
}, },
@ -3897,15 +4265,15 @@ Trigger.create_or_update(
}, },
perform: { perform: {
'notification.email' => { 'notification.email' => {
'body' => '<p>Your follow up for (#{config.ticket_hook}#{ticket.number}) has been received and will be reviewed by our support staff.<p> 'body' => '<div>Your follow up for (#{config.ticket_hook}#{ticket.number}) has been received and will be reviewed by our support staff.</div>
<br/> <br/>
<p>To provide additional information, please reply to this email or click on the following link: <div>To provide additional information, please reply to this email or click on the following link:
<a href="#{config.http_type}://#{config.fqdn}/#ticket/zoom/#{ticket.id}">#{config.http_type}://#{config.fqdn}/#ticket/zoom/#{ticket.id}</a> <a href="#{config.http_type}://#{config.fqdn}/#ticket/zoom/#{ticket.id}">#{config.http_type}://#{config.fqdn}/#ticket/zoom/#{ticket.id}</a>
</p> </div>
<br/> <br/>
<p>Your #{config.product_name} Team</p> <div>Your #{config.product_name} Team</div>
<br/> <br/>
<p><i><a href="http://zammad.com">Zammad</a>, your customer support system</i></p>', <div><i><a href="http://zammad.com">Zammad</a>, your customer support system</i></div>',
'recipient' => 'ticket_customer', 'recipient' => 'ticket_customer',
'subject' => 'Thanks for your follow up (#{ticket.title})', 'subject' => 'Thanks for your follow up (#{ticket.title})',
}, },

View file

@ -0,0 +1,471 @@
# encoding: utf-8
require 'test_helper'
class ObjectManagerTest < ActiveSupport::TestCase
test 'a object manager' do
list_objects = ObjectManager.list_objects
assert_equal(%w(Ticket TicketArticle User Organization Group), list_objects)
list_objects = ObjectManager.list_frontend_objects
assert_equal(%w(Ticket User Organization Group), list_objects)
# create simple attribute
attribute1 = ObjectManager::Attribute.add(
object: 'Ticket',
name: 'test1',
display: 'Test 1',
data_type: 'input',
data_option: {
maxlength: 200,
type: 'text',
null: false,
},
active: true,
screens: {},
position: 20,
created_by_id: 1,
updated_by_id: 1,
editable: false,
to_migrate: false,
)
assert(attribute1)
assert_equal('test1', attribute1.name)
assert_equal(true, attribute1.editable)
assert_equal(true, attribute1.to_create)
assert_equal(true, attribute1.to_migrate)
assert_equal(false, attribute1.to_delete)
assert_equal(true, ObjectManager::Attribute.pending_migration?)
attribute1 = ObjectManager::Attribute.get(
object: 'Ticket',
name: 'test1',
)
assert(attribute1)
assert_equal('test1', attribute1.name)
assert_equal(true, attribute1.editable)
assert_equal(true, attribute1.to_create)
assert_equal(true, attribute1.to_migrate)
assert_equal(false, attribute1.to_delete)
assert_equal(true, ObjectManager::Attribute.pending_migration?)
# delete attribute without execute migrations
ObjectManager::Attribute.remove(
object: 'Ticket',
name: 'test1',
)
assert_equal(false, ObjectManager::Attribute.pending_migration?)
assert(ObjectManager::Attribute.migration_execute)
attribute1 = ObjectManager::Attribute.get(
object: 'Ticket',
name: 'test1',
)
assert_not(attribute1)
# create invalid attributes
assert_raises(RuntimeError) {
attribute2 = ObjectManager::Attribute.add(
object: 'Ticket',
name: 'test2_id',
display: 'Test 2 with id',
data_type: 'input',
data_option: {
maxlength: 200,
type: 'text',
null: false,
},
active: true,
screens: {},
position: 20,
created_by_id: 1,
updated_by_id: 1,
)
}
assert_raises(RuntimeError) {
attribute3 = ObjectManager::Attribute.add(
object: 'Ticket',
name: 'test3_ids',
display: 'Test 3 with id',
data_type: 'input',
data_option: {
maxlength: 200,
type: 'text',
null: false,
},
active: true,
screens: {},
position: 20,
created_by_id: 1,
updated_by_id: 1,
)
}
assert_raises(RuntimeError) {
attribute4 = ObjectManager::Attribute.add(
object: 'Ticket',
name: 'test4',
display: 'Test 4 with missing data_option[:type]',
data_type: 'input',
data_option: {
maxlength: 200,
null: false,
},
active: true,
screens: {},
position: 20,
created_by_id: 1,
updated_by_id: 1,
)
}
attribute5 = ObjectManager::Attribute.add(
object: 'Ticket',
name: 'test5',
display: 'Test 5',
data_type: 'boolean',
data_option: {
default: true,
options: {
true: 'Yes',
false: 'No',
},
null: false,
},
active: true,
screens: {},
position: 20,
created_by_id: 1,
updated_by_id: 1,
)
assert(attribute5)
assert_equal('test5', attribute5.name)
ObjectManager::Attribute.remove(
object: 'Ticket',
name: 'test5',
)
assert_raises(RuntimeError) {
attribute6 = ObjectManager::Attribute.add(
object: 'Ticket',
name: 'test6',
display: 'Test 6',
data_type: 'boolean',
data_option: {
options: {
true: 'Yes',
false: 'No',
},
null: false,
},
active: true,
screens: {},
position: 20,
created_by_id: 1,
updated_by_id: 1,
)
}
attribute7 = ObjectManager::Attribute.add(
object: 'Ticket',
name: 'test7',
display: 'Test 7',
data_type: 'select',
data_option: {
default: 1,
options: {
'1' => 'aa',
'2' => 'bb',
},
null: false,
},
active: true,
screens: {},
position: 20,
created_by_id: 1,
updated_by_id: 1,
)
assert(attribute7)
assert_equal('test7', attribute7.name)
ObjectManager::Attribute.remove(
object: 'Ticket',
name: 'test7',
)
assert_raises(RuntimeError) {
attribute8 = ObjectManager::Attribute.add(
object: 'Ticket',
name: 'test8',
display: 'Test 8',
data_type: 'select',
data_option: {
default: 1,
null: false,
},
active: true,
screens: {},
position: 20,
created_by_id: 1,
updated_by_id: 1,
)
}
attribute9 = ObjectManager::Attribute.add(
object: 'Ticket',
name: 'test9',
display: 'Test 9',
data_type: 'datetime',
data_option: {
future: true,
past: false,
diff: 24,
null: true,
},
active: true,
screens: {},
position: 20,
created_by_id: 1,
updated_by_id: 1,
)
assert(attribute9)
assert_equal('test9', attribute9.name)
ObjectManager::Attribute.remove(
object: 'Ticket',
name: 'test9',
)
assert_raises(RuntimeError) {
attribute10 = ObjectManager::Attribute.add(
object: 'Ticket',
name: 'test10',
display: 'Test 10',
data_type: 'datetime',
data_option: {
past: false,
diff: 24,
null: true,
},
active: true,
screens: {},
position: 20,
created_by_id: 1,
updated_by_id: 1,
)
}
attribute11 = ObjectManager::Attribute.add(
object: 'Ticket',
name: 'test11',
display: 'Test 11',
data_type: 'date',
data_option: {
future: true,
past: false,
diff: 24,
null: true,
},
active: true,
screens: {},
position: 20,
created_by_id: 1,
updated_by_id: 1,
)
assert(attribute11)
assert_equal('test11', attribute11.name)
ObjectManager::Attribute.remove(
object: 'Ticket',
name: 'test11',
)
assert_raises(RuntimeError) {
attribute12 = ObjectManager::Attribute.add(
object: 'Ticket',
name: 'test12',
display: 'Test 12',
data_type: 'date',
data_option: {
past: false,
diff: 24,
null: true,
},
active: true,
screens: {},
position: 20,
created_by_id: 1,
updated_by_id: 1,
)
}
assert_equal(false, ObjectManager::Attribute.pending_migration?)
end
test 'b object manager attribute' do
assert_equal(false, ObjectManager::Attribute.pending_migration?)
assert_equal(0, ObjectManager::Attribute.where(to_migrate: true).count)
assert_equal(0, ObjectManager::Attribute.migrations.count)
attribute1 = ObjectManager::Attribute.add(
object: 'Ticket',
name: 'attribute1',
display: 'Attribute 1',
data_type: 'input',
data_option: {
maxlength: 200,
type: 'text',
null: true,
},
active: true,
screens: {},
position: 20,
created_by_id: 1,
updated_by_id: 1,
)
assert(attribute1)
assert_equal(true, ObjectManager::Attribute.pending_migration?)
assert_equal(1, ObjectManager::Attribute.where(to_migrate: true).count)
assert_equal(1, ObjectManager::Attribute.migrations.count)
# execute migrations
assert(ObjectManager::Attribute.migration_execute)
assert_equal(false, ObjectManager::Attribute.pending_migration?)
assert_equal(0, ObjectManager::Attribute.where(to_migrate: true).count)
assert_equal(0, ObjectManager::Attribute.migrations.count)
# create example ticket
ticket1 = Ticket.create(
title: 'some attribute test1',
group: Group.lookup(name: 'Users'),
customer_id: 2,
state: Ticket::State.lookup(name: 'new'),
priority: Ticket::Priority.lookup(name: '2 normal'),
attribute1: 'some attribute text',
updated_by_id: 1,
created_by_id: 1,
)
assert('ticket1 created', ticket1)
assert_equal('some attribute test1', ticket1.title)
assert_equal('Users', ticket1.group.name)
assert_equal('new', ticket1.state.name)
assert_equal('some attribute text', ticket1.attribute1)
# add additional attributes
attribute2 = ObjectManager::Attribute.add(
object: 'Ticket',
name: 'attribute2',
display: 'Attribute 2',
data_type: 'select',
data_option: {
default: '2',
options: {
'1' => 'aa',
'2' => 'bb',
},
null: true,
},
active: true,
screens: {},
position: 20,
created_by_id: 1,
updated_by_id: 1,
)
attribute3 = ObjectManager::Attribute.add(
object: 'Ticket',
name: 'attribute3',
display: 'Attribute 3',
data_type: 'datetime',
data_option: {
future: true,
past: false,
diff: 24,
null: true,
},
active: true,
screens: {},
position: 20,
created_by_id: 1,
updated_by_id: 1,
)
attribute4 = ObjectManager::Attribute.add(
object: 'Ticket',
name: 'attribute4',
display: 'Attribute 4',
data_type: 'datetime',
data_option: {
future: true,
past: false,
diff: 24,
null: true,
},
active: true,
screens: {},
position: 20,
created_by_id: 1,
updated_by_id: 1,
)
# execute migrations
assert_equal(true, ObjectManager::Attribute.pending_migration?)
assert(ObjectManager::Attribute.migration_execute)
assert_equal(false, ObjectManager::Attribute.pending_migration?)
# create example ticket
ticket2 = Ticket.create(
title: 'some attribute test2',
group: Group.lookup(name: 'Users'),
customer_id: 2,
state: Ticket::State.lookup(name: 'new'),
priority: Ticket::Priority.lookup(name: '2 normal'),
attribute1: 'some attribute text',
attribute2: '1',
attribute3: Time.zone.parse('2016-05-12 00:59:59 UTC'),
attribute4: Date.parse('2016-05-11'),
updated_by_id: 1,
created_by_id: 1,
)
assert('ticket2 created', ticket2)
assert_equal('some attribute test2', ticket2.title)
assert_equal('Users', ticket2.group.name)
assert_equal('new', ticket2.state.name)
assert_equal('some attribute text', ticket2.attribute1)
assert_equal('1', ticket2.attribute2)
assert_equal(Time.zone.parse('2016-05-12 00:59:59 UTC'), ticket2.attribute3)
assert_equal(Date.parse('2016-05-11'), ticket2.attribute4)
# remove attribute
ObjectManager::Attribute.remove(
object: 'Ticket',
name: 'attribute1',
)
ObjectManager::Attribute.remove(
object: 'Ticket',
name: 'attribute2',
)
ObjectManager::Attribute.remove(
object: 'Ticket',
name: 'attribute3',
)
ObjectManager::Attribute.remove(
object: 'Ticket',
name: 'attribute4',
)
assert(ObjectManager::Attribute.migration_execute)
ticket2 = Ticket.find(ticket2.id)
assert('ticket2 created', ticket2)
assert_equal('some attribute test2', ticket2.title)
assert_equal('Users', ticket2.group.name)
assert_equal('new', ticket2.state.name)
assert_equal(nil, ticket2[:attribute1])
assert_equal(nil, ticket2[:attribute2])
assert_equal(nil, ticket2[:attribute3])
assert_equal(nil, ticket2[:attribute4])
end
end