Browse Source

Fixes #1573 - Assign user to multiple organizations.

Rolf Schmidt 2 years ago
parent
commit
84f6ee0a7c

+ 24 - 0
app/assets/javascripts/app/controllers/_ui_element/autocompletion_ajax_customer_organization.coffee

@@ -0,0 +1,24 @@
+# coffeelint: disable=camel_case_classes
+class App.UiElement.autocompletion_ajax_customer_organization
+  @render: (attributeConfig, params = {}, form) ->
+    attribute = $.extend(true, {}, attributeConfig)
+
+    if params[attribute.name] || attribute.value
+      object = App[attribute.relation].find(params[attribute.name] || attribute.value)
+      valueName = object.displayName() if object
+
+    # selectable search
+    searchableAjaxSelectObject = new App.CustomerOrganizationAjaxSelect(
+      delegate:      form
+      attribute:
+        value:       params[attribute.name] || attribute.value
+        valueName:   valueName
+        name:        attribute.name
+        id:          params.organization_id || attribute.id
+        placeholder: App.i18n.translateInline('Search…')
+        limit:       40
+        relation:    attribute.relation
+        ajax:        true
+        multiple:    attribute.multiple
+    )
+    searchableAjaxSelectObject.element()

+ 27 - 13
app/assets/javascripts/app/controllers/agent_ticket_create.coffee

@@ -397,6 +397,7 @@ class App.TicketCreate extends App.Controller
       screen:         'create_top'
       events:
         'change [name=customer_id]': @localUserInfo
+        'change [data-attribute-name=organization_id] .js-input': @localUserInfo
       handlersConfig: handlersTunnel
       autofocus:      true
       params:         params
@@ -456,10 +457,8 @@ class App.TicketCreate extends App.Controller
       query:        @query
     )
 
-    if @formDefault.customer_id
-      callback = (customer) =>
-        @localUserInfoCallback(@formDefault, customer)
-      App.User.full(@formDefault.customer_id, callback)
+    if @formDefault.customer_id || @formDefault.organization_id
+      @localUserInfo(undefined, @formDefault)
 
     # update taskbar with new meta data
     App.TaskManager.touch(@taskKey)
@@ -472,18 +471,33 @@ class App.TicketCreate extends App.Controller
   tokanice: ->
     App.Utils.tokanice('.content.active input[name=cc]', 'email')
 
-  localUserInfo: (e) =>
+  localUserInfo: (e, params = {}) =>
     return if !@sidebarWidget
-    params = App.ControllerForm.params($(e.target).closest('form'))
 
-    if params.customer_id
-      callback = (customer) =>
-        @localUserInfoCallback(params, customer)
-      App.User.full(params.customer_id, callback)
-      return
-    @localUserInfoCallback(params)
+    # get params by event if given
+    params = App.ControllerForm.params($(e.target).closest('form')) if e
+
+    return if @localUserInfoCustomerId is params.customer_id && @localUserInfoOrganizationId is params.organization_id
+    @localUserInfoCustomerId     = params.customer_id
+    @localUserInfoOrganizationId = params.organization_id
+
+    callbackOrganization = =>
+      if params.organization_id
+        App.Organization.full(params.organization_id, => @localUserInfoCallback(params))
+      else
+        @localUserInfoCallback(params)
+
+    callbackUser = ->
+      if params.customer_id
+        App.User.full(params.customer_id, callbackOrganization)
+      else
+        callbackOrganization()
+
+    callbackUser()
+
+  localUserInfoCallback: (params) =>
+    customer = App.User.find(params.customer_id) || {}
 
-  localUserInfoCallback: (params, customer = {}) =>
     @sidebarWidget.render(params)
     @textModule.reload(
       config: App.Config.all()

+ 4 - 4
app/assets/javascripts/app/controllers/agent_ticket_create/sidebar_organization.coffee

@@ -1,11 +1,11 @@
 class SidebarOrganization extends App.Controller
   sidebarItem: =>
     return if !@permissionCheck('ticket.agent')
-    return if !@params.customer_id
-    return if !App.User.exists(@params.customer_id)
-    customer = App.User.find(@params.customer_id)
-    @organization_id = customer.organization_id
+
+    # use ticket organization id or customer user organization id as fallback
+    @organization_id = @params.organization_id || App.User.find(@params.customer_id)?.organization_id
     return if !@organization_id
+
     @item = {
       name: 'organization'
       badgeIcon: 'group'

+ 11 - 9
app/assets/javascripts/app/controllers/knowledge_base/form_controller.coffee

@@ -19,17 +19,19 @@ class App.KnowledgeBaseFormController extends App.ControllerForm
   getAjaxAttributes: (field, attributes) ->
     @apiPath = App.Config.get('api_path')
 
-    attributes.type = 'POST'
     attributes.url  =  "#{@apiPath}/knowledge_bases/search"
 
-    attributes.data.flavor            = 'agent'
-    attributes.data.knowledge_base_id = @object.knowledge_base().id
-    attributes.data.exclude_ids       = [@object.translation(@kb_locale.id)?.id]
-    attributes.data.index             = 'KnowledgeBase::Answer::Translation'
-    attributes.data.locale            = @kb_locale.systemLocale().locale
-    attributes.data.highlight_enabled = false
-
-    attributes.data = JSON.stringify(attributes.data)
+    data                   = {}
+    data.query             = field.input.val()
+    data.limit             = field.options.attribute.limit
+    data.flavor            = 'agent'
+    data.knowledge_base_id = @object.knowledge_base().id
+    data.exclude_ids       = [@object.translation(@kb_locale.id)?.id]
+    data.index             = 'KnowledgeBase::Answer::Translation'
+    data.locale            = @kb_locale.systemLocale().locale
+    data.highlight_enabled = false
+
+    attributes.data = JSON.stringify(data)
 
     attributes
 

+ 13 - 4
app/assets/javascripts/app/controllers/ticket_customer.coffee

@@ -7,19 +7,23 @@ class App.TicketCustomer extends App.ControllerModal
   content: ->
     configure_attributes = [
       { name: 'customer_id', display: __('Customer'), tag: 'user_autocompletion', null: false, placeholder: __('Enter Person or Organization/Company'), minLengt: 2, disableCreateObject: false },
+      { name: 'organization_id', display: __('Organization'), tag: 'autocompletion_ajax_customer_organization', multiple: false, null: false, relation: 'Organization', autocapitalize: false, translate: false },
     ]
     @controller = new App.ControllerForm(
       model:
         configure_attributes: configure_attributes,
-      autofocus: true
+      autofocus:      true,
+      handlersConfig: [App.TicketZoomFormHandlerMultiOrganization],
     )
     @controller.form
 
   onSubmit: (e) =>
     params = @formParam(e.target)
 
-    ticket = App.Ticket.find(@ticket_id)
-    ticket.customer_id = params['customer_id']
+    ticket                 = App.Ticket.find(@ticket_id)
+    ticket.customer_id     = params['customer_id']
+    ticket.organization_id = params['organization_id']
+
     errors = ticket.validate(
       controllerForm: @controller
     )
@@ -32,7 +36,9 @@ class App.TicketCustomer extends App.ControllerModal
       )
       return
 
-    @customer_id = params['customer_id']
+    @customer_id     = params['customer_id']
+    @organization_id = params['organization_id']
+
     callback = =>
 
       # close modal
@@ -43,7 +49,10 @@ class App.TicketCustomer extends App.ControllerModal
       ticket.article = undefined
       ticket.updateAttributes(
         customer_id: @customer_id
+        organization_id: @organization_id
       )
 
     # load user if not already exists
     App.User.full(@customer_id, callback)
+    if @organization_id
+      App.Organization.full(@organization_id, callback)

+ 33 - 0
app/assets/javascripts/app/controllers/ticket_zoom/form_handler_multi_organization.coffee

@@ -0,0 +1,33 @@
+# in future this can be hopefully done by core workflow
+# but for now as the ticket create form is split in different form objects
+# this will be done by a handler
+class App.TicketZoomFormHandlerMultiOrganization
+  @run: (params, attribute, attributes, classname, form, ui) ->
+
+    # for agents run handler on customer field
+    return if attribute.name isnt 'customer_id' && ui.permissionCheck('ticket.agent')
+
+    # for customers there is no customer field so run it on title field
+    return if attribute.name isnt 'title' && ui.permissionCheck('ticket.customer') && !ui.permissionCheck('ticket.agent')
+
+    organization_id = form.find('div[data-attribute-name=organization_id] .js-input')
+    return if !organization_id
+
+    if ui.permissionCheck('ticket.agent')
+      customer = App.User.find(params.customer_id)
+    else
+      customer = App.Session.get()
+
+    if customer && customer.organization_ids.length > 0
+      ui.show('organization_id')
+      ui.mandantory('organization_id')
+
+      if customer.organization_id
+        customer_organization = App.Organization.find(customer.organization_id)
+        if customer_organization
+          organization_id.get(0).selectValue(customer_organization.id, customer_organization.name)
+    else
+      ui.hide('organization_id', undefined, true)
+      ui.optional('organization_id')
+
+App.Config.set('200-MultiOrganization', App.TicketZoomFormHandlerMultiOrganization, 'TicketCreateFormHandler')

+ 11 - 7
app/assets/javascripts/app/controllers/ticket_zoom/sidebar_organization.coffee

@@ -1,19 +1,23 @@
 class SidebarOrganization extends App.Controller
   sidebarItem: =>
-    return if @ticket.currentView() isnt 'agent'
     return if !@ticket.organization_id
-    @item = {
-      name: 'organization'
-      badgeIcon: 'group'
-      sidebarHead: __('Organization')
-      sidebarCallback: @showOrganization
-      sidebarActions: [
+
+    actions = []
+    if @permissionCheck('ticket.agent')
+      actions = [
         {
           title:    __('Edit Organization')
           name:     'organization-edit'
           callback: @editOrganization
         },
       ]
+
+    @item = {
+      name: 'organization'
+      badgeIcon: 'group'
+      sidebarHead: __('Organization')
+      sidebarCallback: @showOrganization
+      sidebarActions: actions
     }
     @item
 

+ 13 - 12
app/assets/javascripts/app/controllers/user.coffee

@@ -83,20 +83,21 @@ class User extends App.ControllerSubContent
         App.Group.fetch()
         @renderResult(user_ids)
 
-      new App.ControllerGenericEdit(
-        id: item.id
-        pageData:
-          title:     __('Users')
-          home:      'users'
-          object:    __('User')
-          objects:   __('Users')
-          navupdate: '#users'
-        genericObject: 'User'
-        callback: rerender
-        container: @el.closest('.content')
+      item.secondaryOrganizations(0, 1000, =>
+        new App.ControllerGenericEdit(
+          id: item.id
+          pageData:
+            title:     __('Users')
+            home:      'users'
+            object:    __('User')
+            objects:   __('Users')
+            navupdate: '#users'
+          genericObject: 'User'
+          callback: rerender
+          container: @el.closest('.content')
+        )
       )
 
-
     callbackLoginAttribute = (value, object, attribute, attributes) ->
       attribute.prefixIcon = null
       attribute.title = null

+ 32 - 0
app/assets/javascripts/app/controllers/user_profile.coffee

@@ -165,6 +165,8 @@ class ActionRow extends App.ControllerObserverActionRow
     actions
 
 class Object extends App.ControllerObserver
+  organizationLimit: 3
+
   model: 'User'
   observeNot:
     cid: true
@@ -180,9 +182,12 @@ class Object extends App.ControllerObserver
     image_source: true
 
   events:
+    'click .js-showMoreOrganizations a': 'showMoreOrganizations'
     'focusout [contenteditable]': 'update'
 
   render: (user) =>
+    if user
+      @user = user
 
     # update taskbar with new meta data
     App.TaskManager.touch(@taskKey)
@@ -208,6 +213,7 @@ class Object extends App.ControllerObserver
       user:     user
       userData: userData
     )
+    @renderOrganizations()
 
     @$('[contenteditable]').ce({
       mode:      'textonly'
@@ -215,6 +221,32 @@ class Object extends App.ControllerObserver
       maxlength: 250
     })
 
+  showMoreOrganizations: (e) ->
+    @preventDefaultAndStopPropagation(e)
+    @organizationLimit = (parseInt(@organizationLimit / 100) + 1) * 100
+    @renderOrganizations()
+
+  renderOrganizations: ->
+    elLocal = @el
+    @user.secondaryOrganizations(0, @organizationLimit, (secondaryOrganizations) ->
+      organizations = []
+      for organization in secondaryOrganizations
+        el = $('<li></li>')
+        new Organization(
+          object_id: organization.id
+          el: el
+        )
+        organizations.push el
+
+      elLocal.find('.js-organizationList li').not('.js-showMoreOrganizations').remove()
+      elLocal.find('.js-organizationList').prepend(organizations)
+    )
+
+    if @user.organization_ids.length < @organizationLimit
+      @el.find('.js-showMoreOrganizations').addClass('hidden')
+    else
+      @el.find('.js-showMoreOrganizations').removeClass('hidden')
+
   update: (e) =>
     name  = $(e.target).attr('data-name')
     value = $(e.target).html()

+ 11 - 8
app/assets/javascripts/app/controllers/widget/link/kb_answer.coffee

@@ -14,14 +14,17 @@ class App.WidgetLinkKbAnswer extends App.WidgetLink
   getAjaxAttributes: (field, attributes) ->
     @apiPath = App.Config.get('api_path')
 
-    attributes.type                   = 'POST'
-    attributes.url                    =  "#{@apiPath}/knowledge_bases/search"
-    attributes.data.flavor            = 'agent'
-    attributes.data.include_locale    = true
-    attributes.data.index             = 'KnowledgeBase::Answer::Translation'
-    attributes.data.highlight_enabled = false
-
-    attributes.data = JSON.stringify(attributes.data)
+    attributes.url = "#{@apiPath}/knowledge_bases/search"
+
+    data                   = {}
+    data.query             = field.input.val()
+    data.limit             = field.options.attribute.limit
+    data.flavor            = 'agent'
+    data.include_locale    = true
+    data.index             = 'KnowledgeBase::Answer::Translation'
+    data.highlight_enabled = false
+
+    attributes.data = JSON.stringify(data)
 
     attributes
 

Some files were not shown because too many files changed in this diff