diff --git a/app/assets/javascripts/app/controllers/_application_controller_form.coffee b/app/assets/javascripts/app/controllers/_application_controller_form.coffee index b083280c8..46ec95120 100644 --- a/app/assets/javascripts/app/controllers/_application_controller_form.coffee +++ b/app/assets/javascripts/app/controllers/_application_controller_form.coffee @@ -204,6 +204,8 @@ class App.ControllerForm extends App.Controller attribute.autofocus = 'autofocus' # set required option + if attribute.required is true + attribute.null = false if !attribute.null attribute.required = 'required' else @@ -231,7 +233,7 @@ class App.ControllerForm extends App.Controller # check if we have a references parts = attribute.name.split '::' 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] ] # set params value to default @@ -480,15 +482,22 @@ class App.ControllerForm extends App.Controller for key of param parts = key.split '::' if parts[0] && parts[1] - if !(parts[0] of inputSelectObject) + if parts[1] && !inputSelectObject[ parts[0] ] inputSelectObject[ parts[0] ] = {} - if !parts[2] - inputSelectObject[ parts[0] ][ parts[1] ] = param[ key ] - else - if !(parts[1] of inputSelectObject[ parts[0] ]) - inputSelectObject[ parts[0] ][ parts[1] ] = {} + if parts[2] && !inputSelectObject[ parts[0] ][ parts[1] ] + inputSelectObject[ parts[0] ][ parts[1] ] = {} + if parts[3] && !inputSelectObject[ parts[0] ][ parts[1] ][ parts[2] ] + inputSelectObject[ parts[0] ][ parts[1] ][ parts[2] ] = {} + + 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 ] - delete param[ key ] + delete param[ key ] + else if parts[1] + inputSelectObject[ parts[0] ][ parts[1] ] = param[ key ] + delete param[ key ] # set new object params for key of inputSelectObject diff --git a/app/assets/javascripts/app/controllers/_dashboard/first_steps.coffee b/app/assets/javascripts/app/controllers/_dashboard/first_steps.coffee index 2267d36e4..21de356f3 100644 --- a/app/assets/javascripts/app/controllers/_dashboard/first_steps.coffee +++ b/app/assets/javascripts/app/controllers/_dashboard/first_steps.coffee @@ -40,7 +40,6 @@ class App.DashboardFirstSteps extends App.Controller #container: @el.closest('.content') head: 'Invite Colleagues' screen: 'invite_agent' - role: 'Agent' ) inviteCustomer: (e) -> diff --git a/app/assets/javascripts/app/controllers/_ui_element/active.coffee b/app/assets/javascripts/app/controllers/_ui_element/active.coffee index 56d0b363e..1cb8b20cb 100644 --- a/app/assets/javascripts/app/controllers/_ui_element/active.coffee +++ b/app/assets/javascripts/app/controllers/_ui_element/active.coffee @@ -4,7 +4,7 @@ class App.UiElement.active extends App.UiElement.ApplicationUiElement # set attributes attribute.null = false - attribute.translation = true + attribute.translate = true # build options list attribute.options = [ diff --git a/app/assets/javascripts/app/controllers/_ui_element/boolean.coffee b/app/assets/javascripts/app/controllers/_ui_element/boolean.coffee index 04951ae39..5855903c7 100644 --- a/app/assets/javascripts/app/controllers/_ui_element/boolean.coffee +++ b/app/assets/javascripts/app/controllers/_ui_element/boolean.coffee @@ -8,6 +8,7 @@ class App.UiElement.boolean extends App.UiElement.ApplicationUiElement { name: 'yes', value: true } { name: 'no', value: false } ] + attribute.translate = true # set data type if attribute.name diff --git a/app/assets/javascripts/app/controllers/_ui_element/object_manager_attribute.coffee b/app/assets/javascripts/app/controllers/_ui_element/object_manager_attribute.coffee new file mode 100644 index 000000000..02d9758b3 --- /dev/null +++ b/app/assets/javascripts/app/controllers/_ui_element/object_manager_attribute.coffee @@ -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) diff --git a/app/assets/javascripts/app/controllers/_ui_element/user_permission.coffee b/app/assets/javascripts/app/controllers/_ui_element/user_permission.coffee new file mode 100644 index 000000000..e8fb263a2 --- /dev/null +++ b/app/assets/javascripts/app/controllers/_ui_element/user_permission.coffee @@ -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 diff --git a/app/assets/javascripts/app/controllers/object_manager.coffee b/app/assets/javascripts/app/controllers/object_manager.coffee index 96541febf..a400bbf1c 100644 --- a/app/assets/javascripts/app/controllers/object_manager.coffee +++ b/app/assets/javascripts/app/controllers/object_manager.coffee @@ -14,7 +14,7 @@ class Index extends App.ControllerTabs @ajax( id: 'object_manager_attributes_list' type: 'GET' - url: @apiPath + '/object_manager_attributes_list' + url: "#{@apiPath}/object_manager_attributes_list" processData: true success: (data, status, xhr) => @stopLoading() @@ -36,22 +36,21 @@ class Index extends App.ControllerTabs class Items extends App.ControllerContent events: - 'click [data-type="delete"]': 'destroy' - 'click .js-up': 'move' - 'click .js-down': 'move' - 'click .js-new': 'new' - 'click .js-edit': 'edit' + 'click .js-delete': 'destroy' + 'click .js-new': 'new' + 'click .js-edit': 'edit' + 'click .js-discard': 'discard' + 'click .js-execute': 'execute' constructor: -> super + # check authentication return if !@authenticate() @subscribeId = App.ObjectManagerAttribute.subscribe(@render) App.ObjectManagerAttribute.fetch() - # ajax call - release: => if @subscribeId App.ObjectManagerAttribute.unsubscribe(@subscribeId) @@ -63,63 +62,22 @@ class Items extends App.ControllerContent 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')( - head: @object - items: items + head: @object + 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) => e.preventDefault() - new App.ControllerGenericNew( + new New( pageData: + head: @object title: 'Attribute' home: 'object_manager' object: 'ObjectManagerAttribute' @@ -127,6 +85,8 @@ class Items extends App.ControllerContent navupdate: '#object_manager' genericObject: 'ObjectManagerAttribute' container: @el.closest('.content') + item: + object: @object ) edit: (e) => @@ -134,136 +94,135 @@ class Items extends App.ControllerContent id = $( e.target ).closest('tr').data('id') new Edit( pageData: - object: 'ObjectManagerAttribute' + head: @object + title: 'Attribute' + home: 'object_manager' + object: 'ObjectManagerAttribute' + objects: 'ObjectManagerAttributes' + navupdate: '#object_manager' genericObject: 'ObjectManagerAttribute' + container: @el.closest('.content') callback: @render id: id - container: @el.closest('.content') ) destroy: (e) -> + e.stopPropagation() e.preventDefault() - sessionId = $( e.target ).data('session-id') + id = $(e.target).closest('tr').data('id') + item = App.ObjectManagerAttribute.find(id) @ajax( - id: 'sessions/' + sessionId + id: "object_manager_attributes/#{id}" type: 'DELETE' - url: @apiPath + '/sessions/' + sessionId + url: "#{@apiPath}/object_manager_attributes/#{id}" success: (data) => - @load() + @render() ) -class Edit extends App.ControllerModal - buttonClose: true - buttonCancel: true - buttonSubmit: true - head: 'Edit' + discard: (e) -> + e.preventDefault() + @ajax( + id: 'object_manager_attributes_discard_changes' + 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 = $( App.view('object_manager/edit')( - head: @object - items: [] - ) ) + @item = App[ @genericObject ].find( @id ) + @head = @pageData.head || @pageData.object - 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 = - input: 'Text (normal - one line)' - select: 'Selection' - datetime: 'Datetime' - date: 'Date' - 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') + @controller = new App.ControllerForm( + model: + configure_attributes: configure_attributes + params: @item + screen: @screen || 'edit' autofocus: true ) + @controller.form - ### - :options => { - 'Incident' => 'Incident', - 'Problem' => 'Problem', - 'Request for Change' => 'Request for Change', - }, - ### + onSubmit: (e) => + params = @formParam(e.target) + params.object = @pageData.head + @item.load(params) - content.find('[name=data_type]').on( - 'change', - (e) -> - dataType = $( e.target ).val() - content.find('.js-middle > div').addClass('hide') - content.find(".js-#{dataType}").removeClass('hide') + # validate + errors = @item.validate() + if errors + @log 'error', errors + @formValidate( form: e.target, errors: errors ) + 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' ) diff --git a/app/assets/javascripts/app/controllers/widget/invite_user.coffee b/app/assets/javascripts/app/controllers/widget/invite_user.coffee index e1fafc792..3d6ebb686 100644 --- a/app/assets/javascripts/app/controllers/widget/invite_user.coffee +++ b/app/assets/javascripts/app/controllers/widget/invite_user.coffee @@ -43,16 +43,16 @@ class App.InviteUser extends App.WizardModal e.preventDefault() @showSlide('js-waiting') @formDisable(e) - @params = @formParam(e.target) - @params.role_ids = [0] + @params = @formParam(e.target) # set invite flag @params.invite = true # find agent role - role = App.Role.findByAttribute('name', @role) - if role - @params.role_ids = role.id + if @role + role = App.Role.findByAttribute('name', @role) + if role + @params.role_ids = role.id user = new App.User user.load(@params) @@ -62,7 +62,7 @@ class App.InviteUser extends App.WizardModal ) if errors @log 'error new', errors - @formValidate( form: e.target, errors: errors ) + @formValidate(form: e.target, errors: errors) @formEnable(e) @showSlide('js-user') return false @@ -77,4 +77,4 @@ class App.InviteUser extends App.WizardModal @formEnable(e) @showSlide('js-user') @showAlert('js-user', details.error_human || details.error) - ) \ No newline at end of file + ) diff --git a/app/assets/javascripts/app/models/_application_model.coffee b/app/assets/javascripts/app/models/_application_model.coffee index 777a1b456..9c8cc2fc5 100644 --- a/app/assets/javascripts/app/models/_application_model.coffee +++ b/app/assets/javascripts/app/models/_application_model.coffee @@ -597,33 +597,33 @@ class App.Model extends Spine.Model all_complied = [] if !params for item in all - item_new = @find( item.id ) + item_new = @find(item.id) all_complied.push @_fillUp(item_new) return all_complied for item in all - item_new = @find( item.id ) + item_new = @find(item.id) all_complied.push @_fillUp(item_new) # filter search if params.filter - all_complied = @_filter( all_complied, params.filter ) + all_complied = @_filter(all_complied, params.filter) # use extend filter search if params.filterExtended - all_complied = @_filterExtended( all_complied, params.filterExtended ) + all_complied = @_filterExtended(all_complied, params.filterExtended) # sort by if params.sortBy != null - all_complied = @_sortBy( all_complied, params.sortBy ) + all_complied = @_sortBy(all_complied, params.sortBy) # order if params.order - all_complied = @_order( all_complied, params.order ) + all_complied = @_order(all_complied, params.order) all_complied - @_sortBy: ( collection, attribute ) -> - _.sortBy( collection, (item) -> + @_sortBy: (collection, attribute) -> + _.sortBy(collection, (item) -> # set displayName as default sort attribute if !attribute @@ -646,20 +646,20 @@ class App.Model extends Spine.Model item[ attribute ] ) - @_order: ( collection, attribute ) -> + @_order: (collection, attribute) -> if attribute is 'DESC' return collection.reverse() collection - @_filter: ( collection, filter ) -> + @_filter: (collection, filter) -> for key, value of filter - collection = _.filter( collection, (item) -> + collection = _.filter(collection, (item) -> if item[key] is value return item ) collection - @_filterExtended: ( collection, filters ) -> + @_filterExtended: (collection, filters) -> collection = _.filter( collection, (item) -> # check all filters diff --git a/app/assets/javascripts/app/models/group.coffee b/app/assets/javascripts/app/models/group.coffee index b2bcad9a0..da4114b61 100644 --- a/app/assets/javascripts/app/models/group.coffee +++ b/app/assets/javascripts/app/models/group.coffee @@ -6,8 +6,8 @@ class App.Group extends App.Model @configure_attributes = [ { 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: '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_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_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.', translate: 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: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, null: true }, diff --git a/app/assets/javascripts/app/models/object_manager_attribute.coffee b/app/assets/javascripts/app/models/object_manager_attribute.coffee index 048808183..431fea039 100644 --- a/app/assets/javascripts/app/models/object_manager_attribute.coffee +++ b/app/assets/javascripts/app/models/object_manager_attribute.coffee @@ -1,20 +1,13 @@ 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 @url: @apiPath + '/object_manager_attributes' @configure_attributes = [ - { 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: 'object', display: 'Object', tag: 'input', readonly: 1 }, - { name: 'position', display: 'Position', tag: 'input', readonly: 1 }, - { name: 'active', display: 'Active', tag: 'active', default: true }, - { name: 'data_type', display: 'Format', tag: 'input', type: 'text', limit: 100, null: false }, - { name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 }, + { 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: 'object', display: 'Object', tag: 'input', readonly: 1 }, + { name: 'position', display: 'Position', tag: 'input', readonly: 1 }, + { name: 'active', display: 'Active', tag: 'active', default: true }, + { name: 'data_type', display: 'Format', tag: 'object_manager_attribute', null: false }, + { name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 }, ] - @configure_overview = [ - #'name', - 'display', - 'position', - 'data_type', - ] - @configure_delete = true \ No newline at end of file diff --git a/app/assets/javascripts/app/models/user.coffee b/app/assets/javascripts/app/models/user.coffee index b26cc7e8f..499baf2e6 100644 --- a/app/assets/javascripts/app/models/user.coffee +++ b/app/assets/javascripts/app/models/user.coffee @@ -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: '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: 'role_ids', display: 'Roles', tag: 'checkbox', multiple: true, null: false, relation: 'Role' }, - { name: 'group_ids', display: 'Groups', tag: 'checkbox', multiple: true, null: true, relation: 'Group', invite_agent: true, invite_customer: true }, + { name: 'role_ids', display: 'Permissions', tag: 'user_permission', null: false, invite_agent: true, invite_customer: true, item_class: 'checkbox' }, { name: 'active', display: 'Active', tag: 'active', default: true }, { name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 }, { name: 'created_at', display: 'Created at', tag: 'datetime', readonly: 1 }, diff --git a/app/assets/javascripts/app/views/generic/input.jst.eco b/app/assets/javascripts/app/views/generic/input.jst.eco index af5458069..ee309c9a8 100644 --- a/app/assets/javascripts/app/views/generic/input.jst.eco +++ b/app/assets/javascripts/app/views/generic/input.jst.eco @@ -1 +1,4 @@ -placeholder="<%- @Ti(@attribute.placeholder) %>"<% end %> <%= @attribute.required %> <%= @attribute.autofocus %> <%- @attribute.autocapitalize %> <%- @attribute.autocomplete %>/> +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: %> + +<% end %> \ No newline at end of file diff --git a/app/assets/javascripts/app/views/generic/select.jst.eco b/app/assets/javascripts/app/views/generic/select.jst.eco index 173712069..710756869 100644 --- a/app/assets/javascripts/app/views/generic/select.jst.eco +++ b/app/assets/javascripts/app/views/generic/select.jst.eco @@ -1,5 +1,5 @@
- " name="<%= @attribute.name %>" <%= @attribute.multiple %> <%= @attribute.required %> <%= @attribute.autofocus %> <% if @attribute.disabled: %> disabled<% end %>> <% if @attribute.options: %> <% for row in @attribute.options: %> @@ -9,4 +9,13 @@ <% if not @attribute.multiple: %> <%- @Icon('arrow-down') %> <% end %> +<% if @attribute.disabled: %> +<% if @attribute.options: %> +<% for row in @attribute.options: %> + <% if row.selected: %> + + <% end %> +<% end %> +<% end %> +<% end %>
diff --git a/app/assets/javascripts/app/views/generic/user_permission.jst.eco b/app/assets/javascripts/app/views/generic/user_permission.jst.eco new file mode 100644 index 000000000..102de2943 --- /dev/null +++ b/app/assets/javascripts/app/views/generic/user_permission.jst.eco @@ -0,0 +1,22 @@ +
+<% for role in @roles: %> + + <% if role.name is 'Agent': %> +
+ <% for group in @groups: %> + + <% end %> +
+ <% end %> +<% end %> +
\ No newline at end of file diff --git a/app/assets/javascripts/app/views/object_manager/attribute.jst.eco b/app/assets/javascripts/app/views/object_manager/attribute.jst.eco new file mode 100644 index 000000000..0d8c8a92b --- /dev/null +++ b/app/assets/javascripts/app/views/object_manager/attribute.jst.eco @@ -0,0 +1,5 @@ +
+
+
+
+
diff --git a/app/assets/javascripts/app/views/object_manager/attribute/autocompletion.jst.eco b/app/assets/javascripts/app/views/object_manager/attribute/autocompletion.jst.eco new file mode 100644 index 000000000..11b95c52f --- /dev/null +++ b/app/assets/javascripts/app/views/object_manager/attribute/autocompletion.jst.eco @@ -0,0 +1,6 @@ +
+Auto-Vervollständigung +
+
+
+
\ No newline at end of file diff --git a/app/assets/javascripts/app/views/object_manager/attribute/boolean.jst.eco b/app/assets/javascripts/app/views/object_manager/attribute/boolean.jst.eco new file mode 100644 index 000000000..d24c01284 --- /dev/null +++ b/app/assets/javascripts/app/views/object_manager/attribute/boolean.jst.eco @@ -0,0 +1,29 @@ +
+
+
+ + + + + + + + + +
<%- @T('Key') %> + <%- @T('Display') %> + <%- @T('Default') %> +
+ true + + + + +
+ false + + + + +
+
diff --git a/app/assets/javascripts/app/views/object_manager/attribute/date.jst.eco b/app/assets/javascripts/app/views/object_manager/attribute/date.jst.eco new file mode 100644 index 000000000..023d9338d --- /dev/null +++ b/app/assets/javascripts/app/views/object_manager/attribute/date.jst.eco @@ -0,0 +1,5 @@ +
+
+
+
+
\ No newline at end of file diff --git a/app/assets/javascripts/app/views/object_manager/attribute/datetime.jst.eco b/app/assets/javascripts/app/views/object_manager/attribute/datetime.jst.eco new file mode 100644 index 000000000..290a6a32b --- /dev/null +++ b/app/assets/javascripts/app/views/object_manager/attribute/datetime.jst.eco @@ -0,0 +1,5 @@ +
+
+
+
+
\ No newline at end of file diff --git a/app/assets/javascripts/app/views/object_manager/attribute/input.jst.eco b/app/assets/javascripts/app/views/object_manager/attribute/input.jst.eco new file mode 100644 index 000000000..fcf25f609 --- /dev/null +++ b/app/assets/javascripts/app/views/object_manager/attribute/input.jst.eco @@ -0,0 +1,5 @@ +
+
+
+
+
\ No newline at end of file diff --git a/app/assets/javascripts/app/views/object_manager/attribute/integer.jst.eco b/app/assets/javascripts/app/views/object_manager/attribute/integer.jst.eco new file mode 100644 index 000000000..fceb40612 --- /dev/null +++ b/app/assets/javascripts/app/views/object_manager/attribute/integer.jst.eco @@ -0,0 +1,5 @@ +
+
+
+
+
\ No newline at end of file diff --git a/app/assets/javascripts/app/views/object_manager/attribute/select.jst.eco b/app/assets/javascripts/app/views/object_manager/attribute/select.jst.eco new file mode 100644 index 000000000..600b2908f --- /dev/null +++ b/app/assets/javascripts/app/views/object_manager/attribute/select.jst.eco @@ -0,0 +1,41 @@ +
+
+
+ + + + + + + <% for key, display of @values: %> + + + +
<%- @T('Key') %> + <%- @T('Display') %> + <%- @T('Default') %> + <%- @T('Action') %> +
+ + + + + + +
+ <%- @Icon('trash') %> <%- @T('Remove') %> +
+ <% end %> + +
+ + + + + + +
+ <%- @Icon('plus-small') %> <%- @T('Add') %> +
+
+
\ No newline at end of file diff --git a/app/assets/javascripts/app/views/object_manager/index.jst.eco b/app/assets/javascripts/app/views/object_manager/index.jst.eco index 6d0e45a53..c740f905c 100644 --- a/app/assets/javascripts/app/views/object_manager/index.jst.eco +++ b/app/assets/javascripts/app/views/object_manager/index.jst.eco @@ -1,21 +1,42 @@
+ <% if !_.isEmpty(@itemsToChange): %>

Database Update required

-

<%- @T( 'Changes were made that require a database update. This might take some time.' ) %>

+

+ <%- @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.') %> +

+

+ <%- @T('Changes') %>: +

+ <% end %> + - + + <% for item in @items: %> - + + <% end %> diff --git a/app/assets/javascripts/app/views/object_manager/screens.jst.eco b/app/assets/javascripts/app/views/object_manager/screens.jst.eco new file mode 100644 index 000000000..6f4e58d7d --- /dev/null +++ b/app/assets/javascripts/app/views/object_manager/screens.jst.eco @@ -0,0 +1,30 @@ +
+
<%- @T('Display') %> <%- @T('Name') %><%- @T('Type') %><%- @T('Type') %><%- @T('Action') %>
<%= item.display %> <%= item.name %> <%= item.data_type %> + <% 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: %> + <%- @Icon('trash') %> + <% end %> +
+ + + + + <% for role, screenOptions of @data: %> + + + +
<%- @T('Role') %> + <%- @T('Screen') %> + <%- @T('Options') %> +
+ <%= role %> + + + <% for screen, options of screenOptions: %> +
+ + <%= screen %> + + <% for key, defaultValue of options: %> + <%= @T(key) %>: checked<% end %> value="true"> + <% end %> + <% end %> + <% end %> + +
+
\ No newline at end of file diff --git a/app/assets/stylesheets/zammad.scss b/app/assets/stylesheets/zammad.scss index 857adbb2b..fe0d67b7e 100644 --- a/app/assets/stylesheets/zammad.scss +++ b/app/assets/stylesheets/zammad.scss @@ -329,7 +329,7 @@ pre code.hljs { text-align: center; white-space: nowrap; vertical-align: middle; - + .icon { vertical-align: middle; margin-top: -3px; @@ -339,7 +339,7 @@ pre code.hljs { &.btn--icon--last .icon { margin-left: 5px; // so far only used in ticket_zoom secondaryAction dropup } - + &:focus { box-shadow: 0 0 0 3px hsl(201,62%,90%); } @@ -348,7 +348,7 @@ pre code.hljs { padding-top: 5px; padding-bottom: 4px; } - + &.btn--slim { padding-left: 12px; padding-right: 12px; @@ -365,7 +365,7 @@ pre code.hljs { border: none; margin: 5px 6px 0; vertical-align: baseline; /* calendar_subscriptions.jst.eco */ - + .icon { vertical-align: middle; margin-right: 5px; @@ -393,7 +393,7 @@ pre code.hljs { padding: 2px 11px 0 !important; display: inline-flex; align-items: center; - + .icon { margin: -2px 5px 0 -2px; fill: hsl(0,0%,60%); @@ -402,11 +402,11 @@ pre code.hljs { .icon:only-child { margin: 0; } - + &.btn--slim { padding-left: 7px !important; padding-right: 7px !important; - + &.btn--small { padding-left: 5px !important; padding-right: 5px !important; @@ -425,7 +425,7 @@ pre code.hljs { &.btn--onDark { background: none; color: white; - + svg { fill: currentColor; opacity: 1; @@ -453,7 +453,7 @@ pre code.hljs { &.btn--success { color: white; background: hsl(145,51%,45%); - + &:active { background: hsl(145,51%,35%); } @@ -461,7 +461,7 @@ pre code.hljs { &.btn--secondary { background: white; color: hsl(145,51%,45%); - + &:active { background: hsl(0,0%,98%); } @@ -486,7 +486,7 @@ pre code.hljs { &.btn--secondary { background: white; color: hsl(0,65%,55%); - + &:active { background: hsl(0,0%,98%); } @@ -502,12 +502,12 @@ pre code.hljs { background: none; vertical-align: baseline; text-align: left; - + .icon { fill: currentColor; opacity: 1; } - + &:active { color: hsl(203,65%,40%); background: none; @@ -516,7 +516,7 @@ pre code.hljs { &.btn--secondary { color: hsl(0,0%,68%); text-decoration: underline; - + &:active { color: hsl(0,0%,53%); } @@ -524,7 +524,7 @@ pre code.hljs { &.btn--positive { color: hsl(145,51%,45%); - + &:active { color: hsl(145,51%,30%); background: none; @@ -533,7 +533,7 @@ pre code.hljs { &.btn--danger { color: hsl(0,65%,55%); - + &:active { color: hsl(0,65%,40%); background: none; @@ -543,7 +543,7 @@ pre code.hljs { &.btn--subtle { text-decoration: underline; color: hsl(0,0%,85%); - + &:active { color: hsl(0,0%,75%); } @@ -560,7 +560,7 @@ pre code.hljs { &.btn--quad { padding: 10px 12px 9px; - + .icon { margin-top: -1px; } @@ -581,7 +581,7 @@ pre code.hljs { &.btn--dropdown { position: relative; - + select { opacity: 0; width: 100%; @@ -627,13 +627,13 @@ pre code.hljs { .visibility-change { /* - + Interactive Visibility Change Classes: - +
- + Important: HTML Order active > hover > normal */ @@ -645,7 +645,7 @@ pre code.hljs { &.is-active [data-visible=active] { display: block; - + & ~ [data-visible=normal] { display: none } @@ -653,7 +653,7 @@ pre code.hljs { &:hover [data-visible=hover] { display: block; - + & ~ [data-visible=normal] { display: none } @@ -663,7 +663,7 @@ pre code.hljs { .btn-group { display: inline-flex; flex-wrap: wrap; - + &.btn-group--full { display: flex; } @@ -671,7 +671,7 @@ pre code.hljs { & + .btn-group { margin-top: 10px; padding-top: 10px; - border-top: 1px solid hsl(240,2%,92%); + border-top: 1px solid hsl(240,2%,92%); } .btn + .btn { @@ -682,7 +682,7 @@ pre code.hljs { padding: 6px 10px 5px; /* reporting main.eco */ display: inline-block; border-radius: 3px; - + &.is-selected { background: hsl(203,65%,55%); color: white; @@ -695,11 +695,11 @@ pre code.hljs { align-items: center; position: relative; user-select: none; - + .dropdown-menu { margin-bottom: 0; } - + &.is-open .dropdown-menu { display: block; } @@ -721,7 +721,7 @@ pre code.hljs { height: 34px; align-items: center; line-height: 35px; - + &.is-clickable { background: hsl(203,65%,55%); color: white; @@ -734,15 +734,15 @@ pre code.hljs { &.is-blinking { animation: pulsate 667ms ease-in-out infinite alternate; } - + &:not(:last-child):not(:only-child) { border-right: none; } - + &:first-child { border-radius: 5px 0 0 5px; } - + &:last-child { border-radius: 0 5px 5px 0; } @@ -775,9 +775,9 @@ pre code.hljs { line-height: 12px; opacity: 0.5; 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 */ &:after { @@ -852,7 +852,7 @@ table { .table { display: table; - + small { color: inherit; } @@ -876,7 +876,7 @@ table { } td { height: 40px; - } + } } .table th:not(.noTruncate) .table-column-title, @@ -924,7 +924,7 @@ th.align-right { .table-hover > tbody > tr:hover > td { background: white; } - + .table-hover > tbody > tr:hover > th { background: rgba(0,8,14,.015); } @@ -938,7 +938,7 @@ th.align-right { padding: 10px; margin-right: -10px; z-index: 1; - + &:after { content: ""; display: block; @@ -957,7 +957,7 @@ th.align-right { .table tr.is-inactive { opacity: 0.5; text-decoration: line-through; - + a { color: #bbb; } @@ -965,7 +965,7 @@ th.align-right { .table tr.is-grayed-out { color: hsl(120,1%,77%); - + .icon { opacity: 0.33; } @@ -1210,7 +1210,7 @@ h3 { margin: 20px 0 8px; color: hsl(207,7%,29%); font-weight: normal; - + .subtitle { display: inline; font-size: 12px; @@ -1318,7 +1318,7 @@ fieldset > .form-group { .form-group { margin-bottom: 16px; - + &.form-group--inactive { opacity: 0.5; } @@ -1335,7 +1335,7 @@ fieldset > .form-group { .merge-group { display: flex; align-items: stretch; - + &.merge-group--inactive { } @@ -1353,7 +1353,7 @@ fieldset > .form-group { border-bottom: 1px solid #eee; } } - + .merge-target, .merge-source { flex: 1; @@ -1372,7 +1372,7 @@ fieldset > .form-group { &:first-of-type { margin-top: 6px; - + .merge-source, .merge-target { border-top: 1px solid #eee; @@ -1382,7 +1382,7 @@ fieldset > .form-group { &:last-of-type { margin-bottom: 6px; - + .merge-source, .merge-target { border-bottom: 1px solid #eee; @@ -1393,7 +1393,7 @@ fieldset > .form-group { .merge-value { margin-bottom: 3px; } - + .form-group { padding: 0; } @@ -1405,7 +1405,7 @@ fieldset > .form-group { &.merge-group--multi { .merge-value + .merge-value { margin-top: 12px; - } + } } } @@ -1413,7 +1413,7 @@ fieldset > .form-group { flex: 1; align-self: flex-end; } - + .merge-control { margin-bottom: 5px; height: 31px; @@ -1434,7 +1434,7 @@ fieldset > .form-group { display: flex; align-items: center; justify-content: center; - + .line-arrow { fill: #e6e6e6; } @@ -1446,7 +1446,7 @@ fieldset > .form-group { position: relative; display: flex; align-items: center; - + label { margin: 0; } @@ -1488,7 +1488,7 @@ fieldset > .form-group { top: -2px; position: relative; margin-left: auto; - + .icon-help { display: block; } @@ -1501,7 +1501,7 @@ fieldset > .form-group { .form-group.formGroup--halfSize { width: 50%; float: left; - + .form-control { min-width: initial; } @@ -1520,7 +1520,7 @@ fieldset > .form-group { flex-shrink: 0; } -input[type="radio"], +input[type="radio"], input[type="checkbox"] { margin: 0; } @@ -1553,7 +1553,7 @@ textarea, padding: 5px 8px 4px; height: 31px; line-height: 20px; - + &.form-control--multiline { min-height: 31px; } @@ -1598,7 +1598,7 @@ input.time { padding: 0 6px; line-height: 42px; flex-shrink: 0; - + &.form-control--small { line-height: 31px; } @@ -1657,7 +1657,7 @@ select.form-control:not([multiple]) { padding: 0; line-height: inherit; height: auto; - + &:focus { box-shadow: none; } @@ -1736,22 +1736,22 @@ input.has-error { .controls--button { display: flex; - + input, .form-control { flex: 1; border-right: none; border-top-right-radius: 0; border-bottom-right-radius: 0; - + &:focus + .controls-button { .controls-button-inner { border-color: hsl(200,71%,59%); } - - /* - fake the form-control outline + /* + + fake the form-control outline */ &:before { @@ -1852,7 +1852,7 @@ input.has-error { align-items: center; justify-content: center; @extend .zIndex-8; - + .modal-backdrop { bottom: 0; width: 200%; @@ -1915,7 +1915,7 @@ input.has-error { .modal-control { padding-left: 14px; padding-right: 14px; - + .btn.is-disabled { opacity: 1; color: hsl(240,5%,83%); @@ -1958,7 +1958,7 @@ kbd { display: flex; } -.pagination > li > a, +.pagination > li > a, .pagination > li > span { padding: 0; width: 31px; @@ -1966,11 +1966,11 @@ kbd { border-color: #e5e5e5; } -.pagination > .active > a, -.pagination > .active > span, -.pagination > .active > a:hover, -.pagination > .active > span:hover, -.pagination > .active > a:focus, +.pagination > .active > a, +.pagination > .active > span, +.pagination > .active > a:hover, +.pagination > .active > span:hover, +.pagination > .active > a:focus, .pagination > .active > span:focus { background: #0F94D6; border-color: #0F94D6; @@ -2001,7 +2001,7 @@ kbd { .page-header-title { display: flex; align-items: center; - + .zammad-switch { margin-right: 9px; } @@ -2017,7 +2017,7 @@ kbd { justify-self: center; padding-left: 9px; margin: 0 auto; - + & + .page-header-meta { margin-left: 0; flex: none; @@ -2031,7 +2031,7 @@ kbd { justify-content: flex-end; flex: 1; min-width: 0; /* firefox flexbug */ - + .btn { overflow: hidden; text-overflow: ellipsis; @@ -2081,7 +2081,7 @@ kbd { align-items: center; justify-content: center; } - + .page-loading-label { margin-left: 10px; margin-top: 1px; @@ -2096,7 +2096,7 @@ kbd { margin: 0; color: #bcbcbc; font-size: 12px; - + &.help-block--center { text-align: center; } @@ -2126,7 +2126,7 @@ kbd { box-shadow: 0 8px 17px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); - + label { 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 82.11% 97.15%, #5c404e, transparent 100%), radial-gradient(circle at 50% 50%, #8b6b76, #8b6b76 100%); - + a { color: white; } .hero-unit { - box-shadow: + box-shadow: 0 8px 17px 0 rgba(0, 0, 0, 0.1), 0 6px 20px 0 rgba(0, 0, 0, 0.09); } @@ -2227,7 +2227,7 @@ kbd { bottom: 0; left: 0; right: 0; - + .icon-logo { margin-right: 8px; margin-top: -11px; @@ -2299,12 +2299,12 @@ ol.tabs li { flex: 1 1 auto; @extend .u-clickable; white-space: nowrap; - + &.active { color: white; background: #444a4f; box-shadow: none; - + .tab-badge { color: hsl(204,3%,65%); } @@ -2316,19 +2316,19 @@ ol.tabs li { display: flex; align-items: center; justify-content: center; - + .arrow { margin-left: 10px; opacity: 0.75; } - + .icon { fill: hsl(0,0%,70%); } - + &.active { background: white; - + .icon { fill: #444a4f; opacity: 1; @@ -2359,7 +2359,7 @@ ol.tabs li { display: inline-flex; margin-left: 0; margin-right: 0; - + .tab { flex: none; } @@ -2369,11 +2369,11 @@ ol.tabs li { margin: 28px auto; font-size: 14px; border-radius: 8px; - + .tab { height: auto; padding: 10px 23px 9px; - + &:first-child { border-radius: 8px 0 0 8px; } @@ -2443,7 +2443,7 @@ ol.tabs li { align-items: center; text-decoration: none; width: calc(33.33% - 6px); - + &.auth-provider--wide { padding-right: 25px; } @@ -2538,11 +2538,11 @@ ol.tabs li { } @keyframes rotateplane { - 0% { + 0% { transform: perspective(120px) rotateX(0deg) rotateY(0deg); - } 50% { + } 50% { transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg); - } 100% { + } 100% { transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); } } @@ -2551,7 +2551,7 @@ ol.tabs li { padding: 2px; margin: -2px 0; cursor: pointer; - + /* :after technique for bigger click area */ &:after { content: ""; @@ -2565,12 +2565,12 @@ ol.tabs li { .icon-status { fill: $ok-color; - + &.inline { margin-top: -3px; vertical-align: middle; } - + &.inactive { fill: hsl(198,18%,86%); } @@ -2611,7 +2611,7 @@ ol.tabs li { */ form { margin: 0; - + &.form--flexibleWidth .controls { display: table; } @@ -2622,7 +2622,7 @@ form { display: flex; align-items: center; margin-top: 10px; - + .btn + .btn:not(.align-right) { margin-left: 20px; } @@ -2688,13 +2688,13 @@ footer { height: 41px; display: none; align-items: center; - + .tabsHolder { flex: 1; margin-right: 20px; min-width: 0; /* Firefox bug fix */ } - + .tabs { margin: 0; position: relative; @@ -2726,11 +2726,11 @@ footer { min-width: $minWidth - $sidebarWidth - $navigationWidth; background: white; z-index: 1; - box-shadow: + box-shadow: 0 -1px rgba(0,0,0,.05), 0 -2px rgba(0,0,0,.03), 0 -3px rgba(0,0,0,.01); - + @media only screen and (max-width: $largeScreenBreakpoint) { left: $navigationWidth; min-width: $minWidth - $sidebarWidth; @@ -2855,7 +2855,7 @@ footer { justify-content: center; font-size: 16px; color: hsl(0,0%,45%); - + .icon { margin-right: 10px; filter: grayscale(90%); @@ -2931,7 +2931,7 @@ footer { input:not(:checked) + label { // switch background background: hsl(202,68%,43%); } - + label:after { background: white; } @@ -5150,7 +5150,7 @@ footer { border-radius: 3px; color: white; border: none; - + &.alert--info { background: hsl(203,65%,55%); } diff --git a/app/controllers/object_manager_attributes_controller.rb b/app/controllers/object_manager_attributes_controller.rb index 6a42b73fd..8412fcd98 100644 --- a/app/controllers/object_manager_attributes_controller.rb +++ b/app/controllers/object_manager_attributes_controller.rb @@ -9,14 +9,12 @@ class ObjectManagerAttributesController < ApplicationController render json: { objects: ObjectManager.list_frontend_objects, } - #model_index_render(ObjectManager::Attribute, params) end # GET /object_manager_attributes def index return if deny_if_not_role(Z_ROLENAME_ADMIN) render json: ObjectManager::Attribute.list_full - #model_index_render(ObjectManager::Attribute, params) end # GET /object_manager_attributes/1 @@ -28,18 +26,68 @@ class ObjectManagerAttributesController < ApplicationController # POST /object_manager_attributes def create 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 # PUT /object_manager_attributes/1 def update 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 # DELETE /object_manager_attributes/1 def destroy 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 diff --git a/app/models/link.rb b/app/models/link.rb index e45b1253f..78beb34ba 100644 --- a/app/models/link.rb +++ b/app/models/link.rb @@ -13,8 +13,8 @@ class Link < ApplicationModel =begin links = Link.list( - :link_object => 'Ticket', - :link_object_value => 1 + link_object: 'Ticket', + link_object_value: 1 ) =end @@ -55,19 +55,19 @@ class Link < ApplicationModel =begin Link.add( - :link_type => 'normal', - :link_object_source => 'Ticket', - :link_object_source_value => 6, - :link_object_target => 'Ticket', - :link_object_target_value => 31 + link_type: 'normal', + link_object_source: 'Ticket', + link_object_source_value: 6, + link_object_target: 'Ticket', + link_object_target_value: 31 ) Link.add( - :link_types_id => 12, - :link_object_source_id => 1, - :link_object_source_value => 1, - :link_object_target_id => 1, - :link_object_target_value => 1 + link_types_id: 12, + link_object_source_id: 1, + link_object_source_value: 1, + link_object_target_id: 1, + link_object_target_value: 1 ) =end @@ -98,11 +98,11 @@ class Link < ApplicationModel =begin Link.remove( - :link_type => 'normal', - :link_object_source => 'Ticket', - :link_object_source_value => 6, - :link_object_target => 'Ticket', - :link_object_target_value => 31 + link_type: 'normal', + link_object_source: 'Ticket', + link_object_source_value: 6, + link_object_target: 'Ticket', + link_object_target_value: 31 ) =end diff --git a/app/models/object_manager.rb b/app/models/object_manager.rb index 187f3f3e6..9470acec5 100644 --- a/app/models/object_manager.rb +++ b/app/models/object_manager.rb @@ -6,7 +6,7 @@ class ObjectManager list all backend managed object - ObjectManager.list_objects() + ObjectManager.list_objects =end @@ -18,251 +18,12 @@ list all backend managed object list all frontend managed object - ObjectManager.list_frontend_objects() + ObjectManager.list_frontend_objects =end def self.list_frontend_objects - %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 + %w(Ticket User Organization Group) end end diff --git a/app/models/ticket.rb b/app/models/ticket.rb index 190e0a087..9d449ab8a 100644 --- a/app/models/ticket.rb +++ b/app/models/ticket.rb @@ -180,12 +180,18 @@ returns tickets.each { |ticket| + article_id = nil + article = Ticket::Article.last_customer_agent_article(ticket.id) + if article + article_id = article.id + end + # send notification Transaction::BackgroundJob.run( object: 'Ticket', type: 'reminder_reached', object_id: ticket.id, - article_id: ticket.articles.last.id, + article_id: article_id, user_id: 1, ) @@ -220,13 +226,19 @@ returns # 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 if ticket.escalation_time < Time.zone.now Transaction::BackgroundJob.run( object: 'Ticket', type: 'escalation', object_id: ticket.id, - article_id: ticket.articles.last.id, + article_id: article_id, user_id: 1, ) result.push ticket @@ -238,7 +250,7 @@ returns object: 'Ticket', type: 'escalation_warning', object_id: ticket.id, - article_id: ticket.articles.last.id, + article_id: article_id, user_id: 1, ) result.push ticket diff --git a/app/models/ticket/article.rb b/app/models/ticket/article.rb index 11ee02369..e28ac941c 100644 --- a/app/models/ticket/article.rb +++ b/app/models/ticket/article.rb @@ -61,6 +61,11 @@ class Ticket::Article < ApplicationModel article 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 # strip not wanted chars diff --git a/config/routes/object_manager_attribute.rb b/config/routes/object_manager_attribute.rb index 54c3bcd57..391140b39 100644 --- a/config/routes/object_manager_attribute.rb +++ b/config/routes/object_manager_attribute.rb @@ -2,11 +2,13 @@ Zammad::Application.routes.draw do api_path = Rails.configuration.api_path # object_manager - 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/: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/: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_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/: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/: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_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 diff --git a/db/migrate/20120101000001_create_base.rb b/db/migrate/20120101000001_create_base.rb index a4af1b1ff..dde5f8445 100644 --- a/db/migrate/20120101000001_create_base.rb +++ b/db/migrate/20120101000001_create_base.rb @@ -447,7 +447,9 @@ class CreateBase < ActiveRecord::Migration t.column :editable, :boolean, null: false, default: true t.column :active, :boolean, null: false, default: 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 :created_by_id, :integer, null: false t.column :updated_by_id, :integer, null: false diff --git a/db/migrate/20150977000001_update_object_manager3.rb b/db/migrate/20150977000001_update_object_manager3.rb index eb8757de4..1a5c030bf 100644 --- a/db/migrate/20150977000001_update_object_manager3.rb +++ b/db/migrate/20150977000001_update_object_manager3.rb @@ -2,6 +2,7 @@ class UpdateObjectManager3 < ActiveRecord::Migration def up ObjectManager::Attribute.add( + force: true, object: 'User', name: 'organization_id', display: 'Organization', @@ -29,13 +30,16 @@ class UpdateObjectManager3 < ActiveRecord::Migration }, }, }, - pending_migration: false, + to_create: false, + to_migrate: false, + to_delete: false, position: 900, created_by_id: 1, updated_by_id: 1, ) ObjectManager::Attribute.add( + force: true, object: 'Ticket', name: 'customer_id', display: 'Customer', @@ -60,7 +64,9 @@ class UpdateObjectManager3 < ActiveRecord::Migration }, edit: {}, }, - pending_migration: false, + to_create: false, + to_migrate: false, + to_delete: false, position: 10, created_by_id: 1, updated_by_id: 1, diff --git a/db/migrate/20151012000001_update_overview4.rb b/db/migrate/20151012000001_update_overview4.rb index 200174d27..5ebfa515b 100644 --- a/db/migrate/20151012000001_update_overview4.rb +++ b/db/migrate/20151012000001_update_overview4.rb @@ -223,6 +223,7 @@ class UpdateOverview4 < ActiveRecord::Migration ) ObjectManager::Attribute.add( + force: true, object: 'Ticket', name: 'title', display: 'Title', @@ -243,11 +244,14 @@ class UpdateOverview4 < ActiveRecord::Migration }, edit: {}, }, - pending_migration: false, + to_create: false, + to_migrate: false, + to_delete: false, position: 15, ) ObjectManager::Attribute.add( + force: true, object: 'Ticket', name: 'group_id', display: 'Group', @@ -275,7 +279,9 @@ class UpdateOverview4 < ActiveRecord::Migration }, }, }, - pending_migration: false, + to_create: false, + to_migrate: false, + to_delete: false, position: 25, ) diff --git a/db/migrate/20160217000001_object_manager_update_user.rb b/db/migrate/20160217000001_object_manager_update_user.rb index e739db608..e15dac3d4 100644 --- a/db/migrate/20160217000001_object_manager_update_user.rb +++ b/db/migrate/20160217000001_object_manager_update_user.rb @@ -2,6 +2,7 @@ class ObjectManagerUpdateUser < ActiveRecord::Migration def up UserInfo.current_user_id = 1 ObjectManager::Attribute.add( + force: true, object: 'User', name: '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, ) ObjectManager::Attribute.add( + force: true, object: 'User', name: '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, ) ObjectManager::Attribute.add( + force: true, object: 'User', name: '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, ) ObjectManager::Attribute.add( + force: true, object: 'User', name: '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, ) ObjectManager::Attribute.add( + force: true, object: 'User', name: '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, ) ObjectManager::Attribute.add( + force: true, object: 'User', name: '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, ) ObjectManager::Attribute.add( + force: true, object: 'User', name: '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, ) ObjectManager::Attribute.add( + force: true, object: 'User', name: '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, ) ObjectManager::Attribute.add( + force: true, object: 'User', name: 'organization_id', display: 'Organization', @@ -323,11 +348,14 @@ class ObjectManagerUpdateUser < ActiveRecord::Migration }, }, }, - pending_migration: false, + to_create: false, + to_migrate: false, + to_delete: false, position: 900, ) ObjectManager::Attribute.add( + force: true, object: 'User', name: '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, ) ObjectManager::Attribute.add( + force: true, object: 'User', name: '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, ) ObjectManager::Attribute.add( + force: true, object: 'User', name: '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, ) ObjectManager::Attribute.add( + force: true, object: 'User', name: '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, ) ObjectManager::Attribute.add( + force: true, object: 'User', name: '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, ) ObjectManager::Attribute.add( + force: true, object: 'User', name: 'password', display: 'Password', @@ -515,11 +558,14 @@ class ObjectManagerUpdateUser < ActiveRecord::Migration }, view: {} }, - pending_migration: false, + to_create: false, + to_migrate: false, + to_delete: false, position: 1400, ) ObjectManager::Attribute.add( + force: true, object: 'User', name: '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, ) ObjectManager::Attribute.add( + force: true, object: 'User', name: '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, ) ObjectManager::Attribute.add( + force: true, object: 'User', name: 'role_ids', display: 'Roles', data_type: 'checkbox', data_option: { + default: '', multiple: true, null: false, relation: 'Role', @@ -618,51 +671,20 @@ class ObjectManagerUpdateUser < ActiveRecord::Migration }, }, }, - pending_migration: false, + to_create: false, + to_migrate: false, + to_delete: 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, - 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( + force: true, object: 'User', name: 'active', display: 'Active', data_type: 'active', data_option: { + null: true, default: true, }, editable: false, @@ -682,7 +704,9 @@ class ObjectManagerUpdateUser < ActiveRecord::Migration }, }, }, - pending_migration: false, + to_create: false, + to_migrate: false, + to_delete: false, position: 1800, ) diff --git a/db/migrate/20160303000001_email_ticket_cc.rb b/db/migrate/20160303000001_email_ticket_cc.rb index d1ff3bd8b..bfb6e11d9 100644 --- a/db/migrate/20160303000001_email_ticket_cc.rb +++ b/db/migrate/20160303000001_email_ticket_cc.rb @@ -2,6 +2,7 @@ class EmailTicketCc < ActiveRecord::Migration def up ObjectManager::Attribute.add( + force: true, object: 'Ticket', name: 'cc', display: 'Cc', @@ -22,7 +23,9 @@ class EmailTicketCc < ActiveRecord::Migration create_middle: {}, edit: {} }, - pending_migration: false, + to_create: false, + to_migrate: false, + to_delete: false, position: 11, created_by_id: 1, updated_by_id: 1, diff --git a/db/migrate/20160307000001_only_one_group.rb b/db/migrate/20160307000001_only_one_group.rb index 569f4ebe3..f55a5f344 100644 --- a/db/migrate/20160307000001_only_one_group.rb +++ b/db/migrate/20160307000001_only_one_group.rb @@ -2,11 +2,13 @@ class OnlyOneGroup < ActiveRecord::Migration def up ObjectManager::Attribute.add( + force: true, object: 'Ticket', name: 'group_id', display: 'Group', data_type: 'select', data_option: { + default: '', relation: 'Group', relation_condition: { access: 'rw' }, nulloption: true, @@ -30,17 +32,21 @@ class OnlyOneGroup < ActiveRecord::Migration }, }, }, - pending_migration: false, + to_create: false, + to_migrate: false, + to_delete: false, position: 25, created_by_id: 1, updated_by_id: 1, ) ObjectManager::Attribute.add( + force: true, object: 'User', name: 'group_ids', display: 'Groups', data_type: 'checkbox', data_option: { + default: '', multiple: true, null: true, relation: 'Group', @@ -67,12 +73,15 @@ class OnlyOneGroup < ActiveRecord::Migration }, }, }, - pending_migration: false, + to_create: false, + to_migrate: false, + to_delete: false, position: 1700, created_by_id: 1, updated_by_id: 1, ) ObjectManager::Attribute.add( + force: true, object: 'User', name: '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, created_by_id: 1, updated_by_id: 1, ) ObjectManager::Attribute.add( + force: true, object: 'User', name: '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, created_by_id: 1, updated_by_id: 1, ) ObjectManager::Attribute.add( + force: true, object: 'User', name: '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, created_by_id: 1, updated_by_id: 1, ) ObjectManager::Attribute.add( + force: true, object: 'User', name: '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, created_by_id: 1, updated_by_id: 1, diff --git a/db/migrate/20160506000003_role_group_remove.rb b/db/migrate/20160506000003_role_group_remove.rb new file mode 100644 index 000000000..2a69acea6 --- /dev/null +++ b/db/migrate/20160506000003_role_group_remove.rb @@ -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 diff --git a/db/migrate/20160512000001_update_object_manager.rb b/db/migrate/20160512000001_update_object_manager.rb new file mode 100644 index 000000000..476d9d660 --- /dev/null +++ b/db/migrate/20160512000001_update_object_manager.rb @@ -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 diff --git a/db/seeds.rb b/db/seeds.rb index 806c18abb..f74c00f23 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -2481,6 +2481,7 @@ Network::Item::Comment.create( ) ObjectManager::Attribute.add( + force: true, object: 'Ticket', name: 'title', display: 'Title', @@ -2501,11 +2502,14 @@ ObjectManager::Attribute.add( }, edit: {}, }, - pending_migration: false, + to_create: false, + to_migrate: false, + to_delete: false, position: 15, ) ObjectManager::Attribute.add( + force: true, object: 'Ticket', name: 'customer_id', display: 'Customer', @@ -2530,15 +2534,19 @@ ObjectManager::Attribute.add( }, edit: {}, }, - pending_migration: false, + to_create: false, + to_migrate: false, + to_delete: false, position: 10, ) ObjectManager::Attribute.add( + force: true, object: 'Ticket', name: 'type', display: 'Type', data_type: 'select', data_option: { + default: '', options: { 'Incident' => 'Incident', 'Problem' => 'Problem', @@ -2549,7 +2557,7 @@ ObjectManager::Attribute.add( null: true, translate: true, }, - editable: false, + editable: true, active: false, screens: { create_middle: { @@ -2564,15 +2572,19 @@ ObjectManager::Attribute.add( }, }, }, - pending_migration: false, + to_create: false, + to_migrate: false, + to_delete: false, position: 20, ) ObjectManager::Attribute.add( + force: true, object: 'Ticket', name: 'group_id', display: 'Group', data_type: 'select', data_option: { + default: '', relation: 'Group', relation_condition: { access: 'rw' }, nulloption: true, @@ -2596,15 +2608,19 @@ ObjectManager::Attribute.add( }, }, }, - pending_migration: false, + to_create: false, + to_migrate: false, + to_delete: false, position: 25, ) ObjectManager::Attribute.add( + force: true, object: 'Ticket', name: 'owner_id', display: 'Owner', data_type: 'select', data_option: { + default: '', relation: 'User', relation_condition: { roles: 'Agent' }, nulloption: true, @@ -2627,10 +2643,13 @@ ObjectManager::Attribute.add( }, }, }, - pending_migration: false, + to_create: false, + to_migrate: false, + to_delete: false, position: 30, ) ObjectManager::Attribute.add( + force: true, object: 'Ticket', name: 'state_id', display: 'State', @@ -2674,10 +2693,13 @@ ObjectManager::Attribute.add( }, }, }, - pending_migration: false, + to_create: false, + to_migrate: false, + to_delete: false, position: 40, ) ObjectManager::Attribute.add( + force: true, object: 'Ticket', name: 'pending_time', display: 'Pending till', @@ -2710,10 +2732,13 @@ ObjectManager::Attribute.add( }, }, }, - pending_migration: false, + to_create: false, + to_migrate: false, + to_delete: false, position: 41, ) ObjectManager::Attribute.add( + force: true, object: 'Ticket', name: 'priority_id', display: 'Priority', @@ -2742,11 +2767,14 @@ ObjectManager::Attribute.add( }, }, }, - pending_migration: false, + to_create: false, + to_migrate: false, + to_delete: false, position: 80, ) ObjectManager::Attribute.add( + force: true, object: 'Ticket', name: 'tags', display: 'Tags', @@ -2766,11 +2794,14 @@ ObjectManager::Attribute.add( }, edit: {}, }, - pending_migration: false, + to_create: false, + to_migrate: false, + to_delete: false, position: 900, ) ObjectManager::Attribute.add( + force: true, object: 'TicketArticle', name: 'type_id', display: 'Type', @@ -2793,11 +2824,14 @@ ObjectManager::Attribute.add( }, }, }, - pending_migration: false, + to_create: false, + to_migrate: false, + to_delete: false, position: 100, ) ObjectManager::Attribute.add( + force: true, object: 'TicketArticle', name: 'internal', display: 'Visibility', @@ -2820,11 +2854,14 @@ ObjectManager::Attribute.add( }, }, }, - pending_migration: false, + to_create: false, + to_migrate: false, + to_delete: false, position: 200, ) ObjectManager::Attribute.add( + force: true, object: 'TicketArticle', name: 'to', display: 'To', @@ -2844,10 +2881,13 @@ ObjectManager::Attribute.add( }, }, }, - pending_migration: false, + to_create: false, + to_migrate: false, + to_delete: false, position: 300, ) ObjectManager::Attribute.add( + force: true, object: 'TicketArticle', name: 'cc', display: 'Cc', @@ -2868,11 +2908,14 @@ ObjectManager::Attribute.add( }, }, }, - pending_migration: false, + to_create: false, + to_migrate: false, + to_delete: false, position: 400, ) ObjectManager::Attribute.add( + force: true, object: 'TicketArticle', name: 'body', display: 'Text', @@ -2901,11 +2944,14 @@ ObjectManager::Attribute.add( }, }, }, - pending_migration: false, + to_create: false, + to_migrate: false, + to_delete: false, position: 600, ) ObjectManager::Attribute.add( + force: true, object: 'User', name: 'login', display: 'Login', @@ -2930,11 +2976,14 @@ ObjectManager::Attribute.add( }, }, }, - pending_migration: false, + to_create: false, + to_migrate: false, + to_delete: false, position: 100, ) ObjectManager::Attribute.add( + force: true, object: 'User', name: 'firstname', display: 'Firstname', @@ -2974,11 +3023,14 @@ ObjectManager::Attribute.add( }, }, }, - pending_migration: false, + to_create: false, + to_migrate: false, + to_delete: false, position: 200, ) ObjectManager::Attribute.add( + force: true, object: 'User', name: 'lastname', display: 'Lastname', @@ -3018,11 +3070,14 @@ ObjectManager::Attribute.add( }, }, }, - pending_migration: false, + to_create: false, + to_migrate: false, + to_delete: false, position: 300, ) ObjectManager::Attribute.add( + force: true, object: 'User', name: 'email', display: 'Email', @@ -3062,11 +3117,14 @@ ObjectManager::Attribute.add( }, }, }, - pending_migration: false, + to_create: false, + to_migrate: false, + to_delete: false, position: 400, ) ObjectManager::Attribute.add( + force: true, object: 'User', name: 'web', display: 'Web', @@ -3094,11 +3152,14 @@ ObjectManager::Attribute.add( }, }, }, - pending_migration: false, + to_create: false, + to_migrate: false, + to_delete: false, position: 500, ) ObjectManager::Attribute.add( + force: true, object: 'User', name: 'phone', display: 'Phone', @@ -3126,11 +3187,14 @@ ObjectManager::Attribute.add( }, }, }, - pending_migration: false, + to_create: false, + to_migrate: false, + to_delete: false, position: 600, ) ObjectManager::Attribute.add( + force: true, object: 'User', name: 'mobile', display: 'Mobile', @@ -3158,11 +3222,14 @@ ObjectManager::Attribute.add( }, }, }, - pending_migration: false, + to_create: false, + to_migrate: false, + to_delete: false, position: 700, ) ObjectManager::Attribute.add( + force: true, object: 'User', name: 'fax', display: 'Fax', @@ -3190,11 +3257,14 @@ ObjectManager::Attribute.add( }, }, }, - pending_migration: false, + to_create: false, + to_migrate: false, + to_delete: false, position: 800, ) ObjectManager::Attribute.add( + force: true, object: 'User', name: 'organization_id', display: 'Organization', @@ -3227,11 +3297,14 @@ ObjectManager::Attribute.add( }, }, }, - pending_migration: false, + to_create: false, + to_migrate: false, + to_delete: false, position: 900, ) ObjectManager::Attribute.add( + force: true, object: 'User', name: 'department', display: 'Department', @@ -3242,7 +3315,7 @@ ObjectManager::Attribute.add( null: true, item_class: 'formGroup--halfSize', }, - editable: false, + editable: true, active: true, screens: { signup: {}, @@ -3259,11 +3332,14 @@ ObjectManager::Attribute.add( }, }, }, - pending_migration: false, + to_create: false, + to_migrate: false, + to_delete: false, position: 1000, ) ObjectManager::Attribute.add( + force: true, object: 'User', name: 'street', display: 'Street', @@ -3290,11 +3366,14 @@ ObjectManager::Attribute.add( }, }, }, - pending_migration: false, + to_create: false, + to_migrate: false, + to_delete: false, position: 1100, ) ObjectManager::Attribute.add( + force: true, object: 'User', name: 'zip', display: 'Zip', @@ -3322,11 +3401,14 @@ ObjectManager::Attribute.add( }, }, }, - pending_migration: false, + to_create: false, + to_migrate: false, + to_delete: false, position: 1200, ) ObjectManager::Attribute.add( + force: true, object: 'User', name: 'city', display: 'City', @@ -3354,11 +3436,14 @@ ObjectManager::Attribute.add( }, }, }, - pending_migration: false, + to_create: false, + to_migrate: false, + to_delete: false, position: 1300, ) ObjectManager::Attribute.add( + force: true, object: 'User', name: 'address', display: 'Address', @@ -3386,11 +3471,14 @@ ObjectManager::Attribute.add( }, }, }, - pending_migration: false, + to_create: false, + to_migrate: false, + to_delete: false, position: 1350, ) ObjectManager::Attribute.add( + force: true, object: 'User', name: 'password', display: 'Password', @@ -3419,11 +3507,14 @@ ObjectManager::Attribute.add( }, view: {} }, - pending_migration: false, + to_create: false, + to_migrate: false, + to_delete: false, position: 1400, ) ObjectManager::Attribute.add( + force: true, object: 'User', name: 'vip', display: 'VIP', @@ -3455,11 +3546,14 @@ ObjectManager::Attribute.add( }, }, }, - pending_migration: false, + to_create: false, + to_migrate: false, + to_delete: false, position: 1490, ) ObjectManager::Attribute.add( + force: true, object: 'User', name: 'note', display: 'Note', @@ -3491,50 +3585,21 @@ ObjectManager::Attribute.add( }, }, }, - pending_migration: false, + to_create: false, + to_migrate: false, + to_delete: false, position: 1500, ) ObjectManager::Attribute.add( + force: true, object: 'User', name: 'role_ids', - display: 'Roles', - data_type: 'checkbox', + display: 'Permissions', + data_type: 'user_permission', data_option: { - multiple: true, null: false, - relation: 'Role', - }, - 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', + item_class: 'checkbox', }, editable: false, active: true, @@ -3543,7 +3608,10 @@ ObjectManager::Attribute.add( invite_agent: { '-all-' => { null: false, - only_shown_if_selectable: true, + hideMode: { + rolesSelected: ['Agent'], + rolesNot: ['Customer'], + } }, }, invite_customer: {}, @@ -3558,16 +3626,20 @@ ObjectManager::Attribute.add( }, }, }, - pending_migration: false, - position: 1700, + to_create: false, + to_migrate: false, + to_delete: false, + position: 1600, ) ObjectManager::Attribute.add( + force: true, object: 'User', name: 'active', display: 'Active', data_type: 'active', data_option: { + null: true, default: true, }, editable: false, @@ -3587,11 +3659,14 @@ ObjectManager::Attribute.add( }, }, }, - pending_migration: false, + to_create: false, + to_migrate: false, + to_delete: false, position: 1800, ) ObjectManager::Attribute.add( + force: true, object: 'Organization', name: 'name', display: 'Name', @@ -3616,17 +3691,19 @@ ObjectManager::Attribute.add( }, }, }, - pending_migration: false, + to_create: false, + to_migrate: false, + to_delete: false, position: 200, ) ObjectManager::Attribute.add( + force: true, object: 'Organization', name: 'shared', display: 'Shared organization', data_type: 'boolean', data_option: { - maxlength: 250, null: true, default: true, 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, ) ObjectManager::Attribute.add( + force: true, object: 'Organization', name: 'note', display: 'Note', @@ -3679,16 +3759,20 @@ ObjectManager::Attribute.add( }, }, }, - pending_migration: false, + to_create: false, + to_migrate: false, + to_delete: false, position: 1500, ) ObjectManager::Attribute.add( + force: true, object: 'Organization', name: 'active', display: 'Active', data_type: 'active', data_option: { + null: true, default: true, }, 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, ) @@ -3858,15 +4226,15 @@ Trigger.create_or_update( }, perform: { 'notification.email' => { - 'body' => '

Your request (#{config.ticket_hook}#{ticket.number}) has been received and will be reviewed by our support staff.

+ 'body' => '

Your request (#{config.ticket_hook}#{ticket.number}) has been received and will be reviewed by our support staff.

-

To provide additional information, please reply to this email or click on the following link: +

To provide additional information, please reply to this email or click on the following link: #{config.http_type}://#{config.fqdn}/#ticket/zoom/#{ticket.id} -

+

-

Your #{config.product_name} Team

+
Your #{config.product_name} Team

-

Zammad, your customer support system

', +
Zammad, your customer support system
', 'recipient' => 'ticket_customer', 'subject' => 'Thanks for your inquiry (#{ticket.title})', }, @@ -3897,15 +4265,15 @@ Trigger.create_or_update( }, perform: { 'notification.email' => { - 'body' => '

Your follow up for (#{config.ticket_hook}#{ticket.number}) has been received and will be reviewed by our support staff.

+ 'body' => '

Your follow up for (#{config.ticket_hook}#{ticket.number}) has been received and will be reviewed by our support staff.

-

To provide additional information, please reply to this email or click on the following link: +

To provide additional information, please reply to this email or click on the following link: #{config.http_type}://#{config.fqdn}/#ticket/zoom/#{ticket.id} -

+

-

Your #{config.product_name} Team

+
Your #{config.product_name} Team

-

Zammad, your customer support system

', +
Zammad, your customer support system
', 'recipient' => 'ticket_customer', 'subject' => 'Thanks for your follow up (#{ticket.title})', }, diff --git a/test/unit/object_manager_test.rb b/test/unit/object_manager_test.rb new file mode 100644 index 000000000..a71f852d0 --- /dev/null +++ b/test/unit/object_manager_test.rb @@ -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