Browse Source

Fixed issue#677 - admin interface -> time accounting setting will not be saved.

Rolf Schmidt 8 years ago
parent
commit
9577d7aa05

+ 6 - 3
app/assets/javascripts/app/controllers/ticket_zoom.coffee

@@ -663,7 +663,10 @@ class App.TicketZoom extends App.Controller
     ticketParams = @formParam(@$('.edit'))
 
     # validate ticket
-    ticket = App.Ticket.find(@ticket_id)
+    # we need to use the full ticket because
+    # the time accouting needs all attributes
+    # for condition check
+    ticket = App.Ticket.fullLocal(@ticket_id)
 
     # reset article - should not be resubmited on next ticket update
     ticket.article = undefined
@@ -740,9 +743,9 @@ class App.TicketZoom extends App.Controller
       @submitPost(e, ticket)
       return
 
-
     # verify if time accounting is active for ticket
-    if false
+    time_accounting_selector = @Config.get('time_accounting_selector')
+    if !App.Ticket.selector(ticket, time_accounting_selector['condition'])
       @submitPost(e, ticket)
       return
 

+ 20 - 1
app/assets/javascripts/app/controllers/time_accounting.coffee

@@ -5,6 +5,8 @@ class Index extends App.ControllerSubContent
     'change .js-timeAccountingSetting input': 'setTimeAccounting'
     'click .js-timePickerYear': 'setYear'
     'click .js-timePickerMonth': 'setMonth'
+    'click .js-timeAccountingFilter': 'setFilter'
+    'click .js-timeAccountingFilterReset': 'resetFilter'
 
   elements:
     '.js-timeAccountingSetting input': 'timeAccountingSetting'
@@ -103,10 +105,12 @@ class Index extends App.ControllerSubContent
       { name: 'condition',  display: 'Conditions for effected objects', tag: 'ticket_selector', null: false, preview: false, action: false, hasChanged: false },
     ]
 
-    new App.ControllerForm(
+    filter_params = App.Setting.get('time_accounting_selector')
+    @filter = new App.ControllerForm(
       el: @$('.js-selector')
       model:
         configure_attributes: configure_attributes,
+      params: filter_params
       autofocus: true
     )
 
@@ -128,6 +132,21 @@ class Index extends App.ControllerSubContent
       month: @month
     )
 
+  setFilter: (e) =>
+    e.preventDefault()
+
+    # get form data
+    params = @formParam(@filter.form)
+
+    # save filter settings
+    App.Setting.set('time_accounting_selector', params, notify: true)
+
+  resetFilter: (e) ->
+    e.preventDefault()
+
+    # save filter settings
+    App.Setting.set('time_accounting_selector', {}, notify: true)
+
   setTimeAccounting: (e) =>
     value = @timeAccountingSetting.prop('checked')
     App.Setting.set('time_accounting', value)

+ 3 - 0
app/assets/javascripts/app/lib/app_post/utils.coffee

@@ -99,6 +99,9 @@ class App.Utils
         else
           '>'
 
+  @escapeRegExp: (str) ->
+    return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&")
+
   # htmlEscaped = App.Utils.htmlEscape(rawText)
   @htmlEscape: (ascii) ->
     return ascii if !ascii

+ 100 - 0
app/assets/javascripts/app/models/ticket.coffee

@@ -124,3 +124,103 @@ class App.Ticket extends App.Model
         # apply direct value changes
         else
           params.ticket[attributes[1]] = content.value
+
+  # check if selector is matching
+  @selector: (ticket, selector) ->
+    return true if _.isEmpty(selector)
+
+    for objectAttribute, condition of selector
+      [objectName, attributeName] = objectAttribute.split('.')
+
+      # there is no article.subject so we take the title instead
+      if objectAttribute == 'article.subject' && !ticket['article']['subject']
+        objectName    = 'ticket'
+        attributeName = 'title'
+
+      # for new articles there is no created_by_id so we set the current user
+      # if no id is given
+      if objectAttribute == 'article.created_by_id' && !ticket['article']['created_by_id']
+        ticket['article']['created_by_id'] = App.Session.get('id')
+
+      if objectName == 'ticket'
+        object = ticket
+      else
+        object = ticket[objectName] || {}
+
+      return false if !@_selectorMatch(object, objectName, attributeName, condition)
+
+    return true
+
+  @_selectorConditionDate: (condition, operator) ->
+    return if operator != '+' && operator != '-'
+
+    conditionValue = new Date()
+    if condition['range'] == 'minute'
+      conditionValue.setTime( eval( conditionValue.getTime() + operator + 60 * 1000 ) )
+    else if condition['range'] == 'hour'
+      conditionValue.setTime( eval( conditionValue.getTime() + operator + 60 * 60 * 1000 ) )
+    else if condition['range'] == 'day'
+      conditionValue.setTime( eval( conditionValue.getTime() + operator + 60 * 60 * 24 * 1000 ) )
+    else if condition['range'] == 'month'
+      conditionValue.setTime( eval( conditionValue.getTime() + operator + 60 * 60 * 30 * 1000 ) )
+    else if condition['range'] == 'year'
+      conditionValue.setTime( eval( conditionValue.getTime() + operator + 60 * 60 * 365 * 1000 ) )
+
+    conditionValue
+
+  @_selectorMatch: (object, objectName, attributeName, condition) ->
+    conditionValue = condition.value
+    conditionValue = '' if conditionValue == undefined
+    objectValue    = object[attributeName]
+    objectValue    = '' if objectValue == undefined
+
+    # take care about pre conditions
+    if condition['pre_condition']
+      [conditionType, conditionKey] = condition['pre_condition'].split('.')
+
+      if conditionType == 'current_user'
+        conditionValue = App.Session.get(conditionKey)
+      else if condition['pre_condition'] == 'not_set'
+        conditionValue = ''
+
+    # prepare regex for contains conditions
+    contains_regex = new RegExp(App.Utils.escapeRegExp(conditionValue.toString()), 'i')
+
+    # move value to array if it is not already
+    if !_.isArray(objectValue)
+      objectValue = [objectValue]
+    # move value to array if it is not already
+    if !_.isArray(conditionValue)
+      conditionValue = [conditionValue]
+
+    result = false
+    for loopObjectKey, loopObjectValue of objectValue
+      for loopConditionKey, loopConditionValue of conditionValue
+        if condition.operator == 'contains'
+          result = true if objectValue.toString().match(contains_regex)
+        else if condition.operator == 'contains not'
+          result = true if !objectValue.toString().match(contains_regex)
+        else if condition.operator == 'is'
+          result = true if objectValue.toString().trim().toLowerCase() is loopConditionValue.toString().trim().toLowerCase()
+        else if condition.operator == 'is not'
+          result = true if objectValue.toString().trim().toLowerCase() isnt loopConditionValue.toString().trim().toLowerCase()
+        else if condition.operator == 'after (absolute)'
+          result = true if new Date(objectValue.toString()) > new Date(loopConditionValue.toString())
+        else if condition.operator == 'before (absolute)'
+          result = true if new Date(objectValue.toString()) < new Date(loopConditionValue.toString())
+        else if condition.operator == 'before (relative)'
+          loopConditionValue = @_selectorConditionDate(condition, '-')
+          result = true if new Date(objectValue.toString()) < new Date(loopConditionValue.toString())
+        else if condition.operator == 'within last (relative)'
+          loopConditionValue = @_selectorConditionDate(condition, '-')
+          result = true if new Date(objectValue.toString()) < new Date() && new Date(objectValue.toString()) > new Date(loopConditionValue.toString())
+        else if condition.operator == 'after (relative)'
+          loopConditionValue = @_selectorConditionDate(condition, '+')
+          result = true if new Date(objectValue.toString()) > new Date(loopConditionValue.toString())
+        else if condition.operator == 'within next (relative)'
+          loopConditionValue = @_selectorConditionDate(condition, '+')
+          result = true if new Date(objectValue.toString()) > new Date() && new Date(objectValue.toString()) < new Date(loopConditionValue.toString())
+        else
+          throw "Unknown operator: #{condition.operator}"
+
+    result

+ 2 - 0
app/assets/javascripts/app/views/time_accounting/index.jst.eco

@@ -15,6 +15,8 @@
     </div>
     <p><%- @T('Enable time accounting for following matching tickets.') %></p>
     <div class="js-selector"></div>
+    <button type="submit" class="btn btn--primary js-timeAccountingFilter"><%- @T('Save') %></button>
+    <button type="submit" class="btn btn--danger js-timeAccountingFilterReset"><%- @T('Reset') %></button>
   </div>
 
   <div class="settings-entry">

+ 15 - 0
app/views/tests/ticket_selector.html.erb

@@ -0,0 +1,15 @@
+
+<link rel="stylesheet" href="/assets/tests/qunit-1.21.0.css">
+<script src="/assets/tests/qunit-1.21.0.js"></script>
+<script src="/assets/tests/ticket_selector.js"></script>
+
+<style type="text/css">
+body {
+  padding-top: 0px;
+}
+</style>
+
+<script type="text/javascript">
+</script>
+
+<div id="qunit" class="u-dontfold"></div>

+ 1 - 0
config/routes/test.rb

@@ -14,6 +14,7 @@ Zammad::Application.routes.draw do
   match '/tests_form_searchable_select',  to: 'tests#form_searchable_select',     via: :get
   match '/tests_table',                   to: 'tests#table',                      via: :get
   match '/tests_html_utils',              to: 'tests#html_utils',                 via: :get
+  match '/tests_ticket_selector',         to: 'tests#ticket_selector',            via: :get
   match '/tests_taskbar',                 to: 'tests#taskbar',                    via: :get
   match '/tests/wait/:sec',               to: 'tests#wait',                       via: :get
   match '/tests/unprocessable_entity',    to: 'tests#error_unprocessable_entity', via: :get

+ 962 - 0
public/assets/tests/ticket_selector.js

@@ -0,0 +1,962 @@
+window.onload = function() {
+
+  var ticketData = {
+      "number": "72008",
+      "title": "asdfasdf",
+      "group_id": 1,
+      "owner_id": 6,
+      "updated_by_id": 6,
+      "created_by_id": 6,
+      "customer_id": 6,
+      "state_id": 4,
+      "priority_id": 2,
+      "created_at": "2017-02-09T09:16:56.192Z",
+      "updated_at": "2017-02-09T09:16:56.192Z",
+      "pending_time": "2017-02-09T09:16:56.192Z",
+      "aaaaa": "1234568791",
+      "anrede": "Herr",
+      "asdf": "",
+      "organization_id": 6,
+      "organization": {
+        "name": "harald test gmbh",
+        "domain": "www.harald-ist-cool.de",
+        "shared": true,
+        "note": "<div>harald test gmbh</div>",
+        "member_ids": [
+            6,
+            2
+        ],
+        "active": true,
+        "created_at": "2017-02-09T09:16:56.192Z",
+        "updated_at": "2017-02-09T09:16:56.192Z",
+        "domain_assignment": false,
+        "updated_by_id": 6,
+        "created_by_id": 6,
+        "id": 6
+      },
+      "group": {
+        "name": "Users",
+        "assignment_timeout": null,
+        "follow_up_possible": "reject",
+        "follow_up_assignment": true,
+        "email_address_id": 1,
+        "signature_id": 1,
+        "note": "Standard Group/Pool for Tickets.",
+        "active": true,
+        "updated_at": "2017-01-18T13:45:30.528Z",
+        "id": 1
+      },
+      "owner": {
+        "login": "-",
+        "firstname": "-",
+        "lastname": "",
+        "email": "",
+        "web": "",
+        "password": "",
+        "phone": "",
+        "fax": "",
+        "mobile": "",
+        "street": "",
+        "zip": "",
+        "city": "",
+        "country": "",
+        "organization_id": null,
+        "department": "",
+        "note": "",
+        "role_ids": [],
+        "group_ids": [],
+        "active": false,
+        "updated_at": "2016-08-02T14:25:24.053Z",
+        "address": "",
+        "vip": false,
+        "anrede": null,
+        "asdf": null,
+        "id": 1
+      },
+      "state": {
+        "name": "closed",
+        "note": null,
+        "active": true,
+        "id": 4
+      },
+      "priority": {
+        "name": "2 normal",
+        "note": null,
+        "active": true,
+        "updated_at": "2016-08-02T14:25:24.677Z",
+        "id": 2
+      },
+      "article": {
+        "from": "Test Master Agent",
+        "to": "agent1@example.com",
+        "cc": "agent1+cc@example.com",
+        "body": "asdfasdfasdf<br><br><div data-signature=\"true\" data-signature-id=\"1\">  Test Master Agent<br><br>--<br> Super Support - Waterford Business Park<br> 5201 Blue Lagoon Drive - 8th Floor &amp; 9th Floor - Miami, 33126 USA<br> Email: hot@example.com - Web: http://www.example.com/<br>--</div>",
+        "content_type": "text/html",
+        "ticket_id": "2",
+        "type_id": 1,
+        "sender_id": 1,
+        "internal": false,
+        "in_reply_to": "<20170217100622.2.152971@zammad.example.com>",
+        "form_id": "326044216"
+      },
+      "customer": {
+        "login": "hc@zammad.com",
+        "firstname": "Harald",
+        "lastname": "Customer",
+        "email": "hc@zammad.com",
+        "web": "zammad.com",
+        "password": "",
+        "phone": "1234567894",
+        "fax": "",
+        "mobile": "",
+        "street": "",
+        "zip": "",
+        "city": "",
+        "country": "",
+        "organization_id": 6,
+        "created_by_id": 6,
+        "updated_by_id": 6,
+        "department": "",
+        "note": "",
+        "role_ids": [
+          3
+        ],
+        "group_ids": [],
+        "active": true,
+        "created_at": "2017-02-09T09:16:56.192Z",
+        "updated_at": "2017-02-09T09:16:56.192Z",
+        "address": "Walter-Gropius-Straße 17, 80807 München, Germany",
+        "web": "www.harald-ist-cool.de",
+        "vip": false,
+        "id": 434
+      },
+      "escalation_at": "2017-02-09T09:16:56.192Z",
+      "last_contact_agent_at": "2017-02-09T09:16:56.192Z",
+      "last_contact_agent_at": "2017-02-09T09:16:56.192Z",
+      "last_contact_at": "2017-02-09T09:16:56.192Z",
+      "last_contact_customer_at": "2017-02-09T09:16:56.192Z",
+      "first_response_at": "2017-02-09T09:16:56.192Z",
+      "close_at": "2017-02-09T09:16:56.192Z",
+      "id": 8
+  };
+
+  var sessionData = {
+    "login": "hh@zammad.com",
+    "firstname": "Harald",
+    "lastname": "Habebe",
+    "email": "hh@zammad.com",
+    "web": "",
+    "password": "",
+    "phone": "",
+    "fax": "",
+    "mobile": "",
+    "street": "",
+    "zip": "",
+    "city": "",
+    "country": "",
+    "organization_id": 6,
+    "department": "",
+    "note": "",
+    "role_ids": [
+      1,
+      2,
+      5,
+      6,
+      4
+    ],
+    "group_ids": [
+      1
+    ],
+    "active": true,
+    "updated_at": "2017-02-09T09:17:04.770Z",
+    "address": "",
+    "vip": false,
+    "anrede": "",
+    "asdf": "",
+    "id": 6
+  };
+
+  /*
+   * ------------------------------------------------------------------------
+   * Test functions
+   * ------------------------------------------------------------------------
+   */
+
+  var testContains = function (key, value, ticket) {
+    setting = {
+      "condition": {
+        [key]: {
+          "operator": "contains",
+          "value": value
+        },
+      }
+    };
+    result = App.Ticket.selector(ticket, setting['condition'] );
+    equal(result, true, result);
+
+    setting = {
+      "condition": {
+        [key]: {
+          "operator": "contains not",
+          "value": value
+        },
+      }
+    };
+    result = App.Ticket.selector(ticket, setting['condition'] );
+    equal(result, false, result);
+  };
+
+  var testIs = function (key, value, ticket) {
+    setting = {
+      "condition": {
+        [key]: {
+          "operator": "is",
+          "value": value
+        },
+      }
+    };
+    result = App.Ticket.selector(ticket, setting['condition'] );
+    equal(result, true, result);
+
+    setting = {
+      "condition": {
+        [key]: {
+          "operator": "is not",
+          "value": value
+        },
+      }
+    };
+    result = App.Ticket.selector(ticket, setting['condition'] );
+    equal(result, false, result);
+  };
+
+  var testPreConditionUser = function (key, specificValue, ticket, session) {
+    App.Session.set(sessionData);
+
+    setting = {
+      "condition": {
+        [key]: {
+          "operator": "is",
+          "pre_condition": "current_user.id",
+          "value": "",
+          "value_completion": ""
+        },
+      }
+    };
+    result = App.Ticket.selector(ticket, setting['condition'] );
+    equal(result, true, result);
+
+    setting = {
+      "condition": {
+        [key]: {
+          "operator": "is not",
+          "pre_condition": "current_user.id",
+          "value": "",
+          "value_completion": ""
+        },
+      }
+    };
+    result = App.Ticket.selector(ticket, setting['condition'] );
+    equal(result, false, result);
+
+    setting = {
+      "condition": {
+        [key]: {
+          "operator": "is",
+          "pre_condition": "specific",
+          "value": specificValue,
+          "value_completion": "Nicole Braun <nicole.braun@zammad.org>"
+        }
+      }
+    };
+
+    result = App.Ticket.selector(ticket, setting['condition'] );
+    equal(result, true, result);
+
+    setting = {
+      "condition": {
+        [key]: {
+          "operator": "is not",
+          "pre_condition": "specific",
+          "value": specificValue,
+          "value_completion": "Nicole Braun <nicole.braun@zammad.org>"
+        }
+      }
+    };
+    result = App.Ticket.selector(ticket, setting['condition'] );
+    equal(result, false, result);
+
+    setting = {
+      "condition": {
+        [key]: {
+          "operator": "is not",
+          "pre_condition": "specific",
+          "value": specificValue,
+          "value_completion": "Nicole Braun <nicole.braun@zammad.org>"
+        }
+      }
+    };
+    result = App.Ticket.selector(ticket, setting['condition'] );
+    equal(result, false, result);
+
+    setting = {
+      "condition": {
+        [key]: {
+          "operator": "is",
+          "pre_condition": "not_set",
+        }
+      }
+    };
+    result = App.Ticket.selector(ticket, setting['condition'] );
+    equal(result, false, result);
+
+    setting = {
+      "condition": {
+        [key]: {
+          "operator": "is not",
+          "pre_condition": "not_set",
+        }
+      }
+    };
+    result = App.Ticket.selector(ticket, setting['condition'] );
+    equal(result, true, result);
+  };
+
+  var testPreConditionOrganization = function (key, specificValue, ticket, session) {
+    App.Session.set(sessionData);
+
+    setting = {
+      "condition": {
+        [key]: {
+          "operator": "is",
+          "pre_condition": "current_user.organization_id",
+          "value": "",
+          "value_completion": ""
+        },
+      }
+    };
+    result = App.Ticket.selector(ticket, setting['condition'] );
+    equal(result, true, result);
+
+    setting = {
+      "condition": {
+        [key]: {
+          "operator": "is not",
+          "pre_condition": "current_user.organization_id",
+          "value": "",
+          "value_completion": ""
+        },
+      }
+    };
+    result = App.Ticket.selector(ticket, setting['condition'] );
+    equal(result, false, result);
+
+    setting = {
+      "condition": {
+        [key]: {
+          "operator": "is",
+          "pre_condition": "specific",
+          "value": specificValue,
+          "value_completion": "Nicole Braun <nicole.braun@zammad.org>"
+        }
+      }
+    };
+
+    result = App.Ticket.selector(ticket, setting['condition'] );
+    equal(result, true, result);
+
+    setting = {
+      "condition": {
+        [key]: {
+          "operator": "is not",
+          "pre_condition": "specific",
+          "value": specificValue,
+          "value_completion": "Nicole Braun <nicole.braun@zammad.org>"
+        }
+      }
+    };
+    result = App.Ticket.selector(ticket, setting['condition'] );
+    equal(result, false, result);
+
+    setting = {
+      "condition": {
+        [key]: {
+          "operator": "is not",
+          "pre_condition": "specific",
+          "value": specificValue,
+          "value_completion": "Nicole Braun <nicole.braun@zammad.org>"
+        }
+      }
+    };
+    result = App.Ticket.selector(ticket, setting['condition'] );
+    equal(result, false, result);
+
+    setting = {
+      "condition": {
+        [key]: {
+          "operator": "is",
+          "pre_condition": "not_set",
+        }
+      }
+    };
+    result = App.Ticket.selector(ticket, setting['condition'] );
+    equal(result, false, result);
+
+    setting = {
+      "condition": {
+        [key]: {
+          "operator": "is not",
+          "pre_condition": "not_set",
+        }
+      }
+    };
+    result = App.Ticket.selector(ticket, setting['condition'] );
+    equal(result, true, result);
+  };
+
+  var testTime = function (key, value, ticket) {
+    valueDate   = new Date(value);
+    compareDate = new Date( valueDate.setHours( valueDate.getHours() - 1 ) ).toISOString();
+    setting = {
+      "condition": {
+        [key]: {
+          "operator": "after (absolute)",
+          "value": compareDate
+        },
+      }
+    };
+    result = App.Ticket.selector(ticket, setting['condition'] );
+    equal(result, true, result);
+
+    valueDate   = new Date(value);
+    compareDate = new Date( valueDate.setHours( valueDate.getHours() + 1 ) ).toISOString();
+    setting = {
+      "condition": {
+        [key]: {
+          "operator": "after (absolute)",
+          "value": compareDate
+        },
+      }
+    };
+    result = App.Ticket.selector(ticket, setting['condition'] );
+    equal(result, false, result);
+
+    valueDate   = new Date(value);
+    compareDate = new Date( valueDate.setHours( valueDate.getHours() - 1 ) ).toISOString();
+    setting = {
+      "condition": {
+        [key]: {
+          "operator": "before (absolute)",
+          "value": compareDate
+        },
+      }
+    };
+    result = App.Ticket.selector(ticket, setting['condition'] );
+    equal(result, false, result);
+
+    valueDate   = new Date(value);
+    compareDate = new Date( valueDate.setHours( valueDate.getHours() + 1 ) ).toISOString();
+    setting = {
+      "condition": {
+        [key]: {
+          "operator": "before (absolute)",
+          "value": compareDate
+        },
+      }
+    };
+    result = App.Ticket.selector(ticket, setting['condition'] );
+    equal(result, true, result);
+
+    valueDate   = new Date(value);
+    compareDate = new Date( valueDate.setHours( valueDate.getHours() + 2 ) ).toISOString();
+    setting = {
+      "condition": {
+        [key]: {
+          "operator": "before (relative)",
+          "value": 1,
+          "range": "hour"
+        },
+      }
+    };
+    result = App.Ticket.selector(ticket, setting['condition'] );
+    equal(result, true, result);
+  };
+
+  var testTimeBeforeRelative = function (key, value, range, expectedResult, ticket) {
+    setting = {
+      "condition": {
+        [key]: {
+          "operator": "before (relative)",
+          "value": value,
+          "range": range
+        },
+      }
+    };
+    result = App.Ticket.selector(ticket, setting['condition'] );
+    equal(result, expectedResult, result);
+  };
+
+  var testTimeAfterRelative = function (key, value, range, expectedResult, ticket) {
+    setting = {
+      "condition": {
+        [key]: {
+          "operator": "after (relative)",
+          "value": value,
+          "range": range
+        },
+      }
+    };
+    result = App.Ticket.selector(ticket, setting['condition'] );
+    equal(result, expectedResult, result);
+  };
+
+  var testTimeWithinNextRelative = function (key, value, range, expectedResult, ticket) {
+    setting = {
+      "condition": {
+        [key]: {
+          "operator": "within next (relative)",
+          "value": value,
+          "range": range
+        },
+      }
+    };
+    result = App.Ticket.selector(ticket, setting['condition'] );
+    equal(result, expectedResult, result);
+  };
+
+  var testTimeWithinLastRelative = function (key, value, range, expectedResult, ticket) {
+    setting = {
+      "condition": {
+        [key]: {
+          "operator": "within last (relative)",
+          "value": value,
+          "range": range
+        },
+      }
+    };
+    result = App.Ticket.selector(ticket, setting['condition'] );
+    equal(result, expectedResult, result);
+  };
+
+  /*
+   * ------------------------------------------------------------------------
+   * Field tests
+   * ------------------------------------------------------------------------
+   */
+
+  test("ticket number", function() {
+    ticket = new App.Ticket();
+    ticket.load(ticketData);
+
+    testContains('ticket.number', '72', ticket);
+  });
+
+  test("ticket title", function() {
+    ticket = new App.Ticket();
+    ticket.load(ticketData);
+
+    testContains('ticket.title', 'asd', ticket);
+  });
+
+  test("ticket customer_id", function() {
+    ticket = new App.Ticket();
+    ticket.load(ticketData);
+
+    App.Session.set(sessionData);
+
+    testPreConditionUser('ticket.customer_id', '6', ticket, sessionData);
+  });
+
+  test("ticket organization_id", function() {
+    ticket = new App.Ticket();
+    ticket.load(ticketData);
+
+    testPreConditionUser('ticket.organization_id', '6', ticket, sessionData);
+  });
+
+  test("ticket group_id", function() {
+    ticket = new App.Ticket();
+    ticket.load(ticketData);
+
+    testIs('ticket.group_id', ['1'], ticket, sessionData);
+  });
+
+  test("ticket owner_id", function() {
+    ticket = new App.Ticket();
+    ticket.load(ticketData);
+
+    App.Session.set(sessionData);
+
+    testPreConditionUser('ticket.owner_id', '6', ticket, sessionData);
+  });
+
+  test("ticket state_id", function() {
+    ticket = new App.Ticket();
+    ticket.load(ticketData);
+
+    testIs('ticket.state_id', ['4'], ticket, sessionData);
+  });
+
+  test("ticket pending_time", function() {
+    ticket = new App.Ticket();
+    ticket.load(ticketData);
+
+    testTime('ticket.pending_time', ticket.pending_time, ticket);
+
+    // -------------------------
+    // BEFORE TIME
+    // -------------------------
+
+    // hour
+    ticket.pending_time = new Date().toISOString();
+    testTimeBeforeRelative('ticket.pending_time', 1, 'hour', false, ticket);
+
+    compareDate = new Date();
+    compareDate.setTime( compareDate.getTime() - 60 * 60 * 2 * 1000 );
+    ticket.pending_time = compareDate.toISOString();
+    testTimeBeforeRelative('ticket.pending_time', 1, 'hour', true, ticket);
+
+    // day
+    ticket.pending_time = new Date().toISOString();
+    testTimeBeforeRelative('ticket.pending_time', 1, 'day', false, ticket);
+
+    compareDate = new Date();
+    compareDate.setTime( compareDate.getTime() - 60 * 60 * 48 * 1000 );
+    ticket.pending_time = compareDate.toISOString();
+    testTimeBeforeRelative('ticket.pending_time', 1, 'day', true, ticket);
+
+    // year
+    ticket.pending_time = new Date().toISOString();
+    testTimeBeforeRelative('ticket.pending_time', 1, 'year', false, ticket);
+
+    compareDate = new Date();
+    compareDate.setTime( compareDate.getTime() - 60 * 60 * 365 * 2 * 1000 );
+    ticket.pending_time = compareDate.toISOString();
+    testTimeBeforeRelative('ticket.pending_time', 1, 'year', true, ticket);
+
+    // -------------------------
+    // AFTER TIME
+    // -------------------------
+
+    // hour
+    ticket.pending_time = new Date().toISOString();
+    testTimeAfterRelative('ticket.pending_time', 1, 'hour', false, ticket);
+
+    compareDate = new Date();
+    compareDate.setTime( compareDate.getTime() + 60 * 60 * 2 * 1000 );
+    ticket.pending_time = compareDate.toISOString();
+    testTimeAfterRelative('ticket.pending_time', 1, 'hour', true, ticket);
+
+    // day
+    ticket.pending_time = new Date().toISOString();
+    testTimeAfterRelative('ticket.pending_time', 1, 'day', false, ticket);
+
+    compareDate = new Date();
+    compareDate.setTime( compareDate.getTime() + 60 * 60 * 48 * 1000 );
+    ticket.pending_time = compareDate.toISOString();
+    testTimeAfterRelative('ticket.pending_time', 1, 'day', true, ticket);
+
+    // year
+    ticket.pending_time = new Date().toISOString();
+    testTimeAfterRelative('ticket.pending_time', 1, 'year', false, ticket);
+
+    compareDate = new Date();
+    compareDate.setTime( compareDate.getTime() + 60 * 60 * 365 * 2 * 1000 );
+    ticket.pending_time = compareDate.toISOString();
+    testTimeAfterRelative('ticket.pending_time', 1, 'year', true, ticket);
+
+
+    // -------------------------
+    // WITHIN LAST TIME
+    // -------------------------
+
+    // hour
+    compareDate = new Date();
+    compareDate.setTime( compareDate.getTime() - 60 * 60 * 0.5 * 1000 );
+    ticket.pending_time = compareDate.toISOString();
+    testTimeWithinLastRelative('ticket.pending_time', 1, 'hour', true, ticket);
+
+    compareDate = new Date();
+    compareDate.setTime( compareDate.getTime() - 60 * 60 * 2 * 1000 );
+    ticket.pending_time = compareDate.toISOString();
+    testTimeWithinLastRelative('ticket.pending_time', 1, 'hour', false, ticket);
+
+    // -------------------------
+    // WITHIN NEXT TIME
+    // -------------------------
+
+    // hour
+    compareDate = new Date();
+    compareDate.setTime( compareDate.getTime() + 60 * 60 * 0.5 * 1000 );
+    ticket.pending_time = compareDate.toISOString();
+    testTimeWithinNextRelative('ticket.pending_time', 1, 'hour', true, ticket);
+
+    compareDate = new Date();
+    compareDate.setTime( compareDate.getTime() + 60 * 60 * 2 * 1000 );
+    ticket.pending_time = compareDate.toISOString();
+    testTimeWithinNextRelative('ticket.pending_time', 1, 'hour', false, ticket);
+  });
+
+  test("ticket priority_id", function() {
+    ticket = new App.Ticket();
+    ticket.load(ticketData);
+
+    testIs('ticket.priority_id', ['2'], ticket, sessionData);
+  });
+
+  test("ticket escalation_at", function() {
+    ticket = new App.Ticket();
+    ticket.load(ticketData);
+
+    testTime('ticket.escalation_at', ticket.escalation_at, ticket);
+  });
+
+  test("ticket last_contact_agent_at", function() {
+    ticket = new App.Ticket();
+    ticket.load(ticketData);
+
+    testTime('ticket.last_contact_agent_at', ticket.last_contact_agent_at, ticket);
+  });
+
+  test("ticket last_contact_at", function() {
+    ticket = new App.Ticket();
+    ticket.load(ticketData);
+
+    testTime('ticket.last_contact_at', ticket.last_contact_at, ticket);
+  });
+
+  test("ticket last_contact_customer_at", function() {
+    ticket = new App.Ticket();
+    ticket.load(ticketData);
+
+    testTime('ticket.last_contact_customer_at', ticket.last_contact_customer_at, ticket);
+  });
+
+  test("ticket first_response_at", function() {
+    ticket = new App.Ticket();
+    ticket.load(ticketData);
+
+    testTime('ticket.first_response_at', ticket.first_response_at, ticket);
+  });
+
+  test("ticket close_at", function() {
+    ticket = new App.Ticket();
+    ticket.load(ticketData);
+
+    testTime('ticket.close_at', ticket.close_at, ticket);
+  });
+
+  test("ticket created_by_id", function() {
+    ticket = new App.Ticket();
+    ticket.load(ticketData);
+
+    App.Session.set(sessionData);
+
+    testPreConditionUser('ticket.created_by_id', '6', ticket, sessionData);
+  });
+
+  test("ticket created_at", function() {
+    ticket = new App.Ticket();
+    ticket.load(ticketData);
+
+    testTime('ticket.created_at', ticket.created_at, ticket);
+  });
+
+  test("ticket updated_at", function() {
+    ticket = new App.Ticket();
+    ticket.load(ticketData);
+
+    testTime('ticket.updated_at', ticket.updated_at, ticket);
+  });
+
+  test("ticket updated_by_id", function() {
+    ticket = new App.Ticket();
+    ticket.load(ticketData);
+
+    App.Session.set(sessionData);
+
+    testPreConditionUser('ticket.updated_by_id', '6', ticket, sessionData);
+  });
+
+  test("article from", function() {
+    ticket = new App.Ticket();
+    ticket.load(ticketData);
+
+    testContains('article.from', 'Master', ticket);
+  });
+
+  test("article to", function() {
+    ticket = new App.Ticket();
+    ticket.load(ticketData);
+
+    testContains('article.to', 'agent1', ticket);
+  });
+
+  test("article cc", function() {
+    ticket = new App.Ticket();
+    ticket.load(ticketData);
+
+    testContains('article.cc', 'agent1+cc', ticket);
+  });
+
+  test("article subject", function() {
+    ticket = new App.Ticket();
+    ticket.load(ticketData);
+
+    testContains('article.subject', 'asdf', ticket);
+  });
+
+  test("article type_id", function() {
+    ticket = new App.Ticket();
+    ticket.load(ticketData);
+
+    testIs('article.type_id', ['1'], ticket);
+  });
+
+  test("article sender_id", function() {
+    ticket = new App.Ticket();
+    ticket.load(ticketData);
+
+    testIs('article.sender_id', ['1'], ticket);
+  });
+
+  test("article internal", function() {
+    ticket = new App.Ticket();
+    ticket.load(ticketData);
+
+    testIs('article.internal', ['false'], ticket);
+  });
+
+  test("article created_by_id", function() {
+    ticket = new App.Ticket();
+    ticket.load(ticketData);
+
+    testPreConditionUser('article.created_by_id', '6', ticket, sessionData);
+  });
+
+  test("customer login", function() {
+    ticket = new App.Ticket();
+    ticket.load(ticketData);
+
+    testContains('customer.login', 'hc', ticket);
+  });
+
+  test("customer firstname", function() {
+    ticket = new App.Ticket();
+    ticket.load(ticketData);
+
+    testContains('customer.firstname', 'Harald', ticket);
+  });
+
+  test("customer lastname", function() {
+    ticket = new App.Ticket();
+    ticket.load(ticketData);
+
+    testContains('customer.lastname', 'Customer', ticket);
+  });
+
+  test("customer email", function() {
+    ticket = new App.Ticket();
+    ticket.load(ticketData);
+
+    testContains('customer.email', 'hc', ticket);
+  });
+
+  test("customer organization_id", function() {
+    ticket = new App.Ticket();
+    ticket.load(ticketData);
+
+    testPreConditionOrganization('customer.organization_id', '6', ticket, sessionData);
+  });
+
+  test("customer created_by_id", function() {
+    ticket = new App.Ticket();
+    ticket.load(ticketData);
+
+    testPreConditionUser('customer.created_by_id', '6', ticket, sessionData);
+  });
+
+  test("customer created_at", function() {
+    ticket = new App.Ticket();
+    ticket.load(ticketData);
+
+    testTime('customer.created_at', ticket.customer.created_at, ticket);
+  });
+
+  test("customer updated_by_id", function() {
+    ticket = new App.Ticket();
+    ticket.load(ticketData);
+
+    testPreConditionUser('customer.updated_by_id', '6', ticket, sessionData);
+  });
+
+  test("customer missing_field", function() {
+    ticket = new App.Ticket();
+    ticket.load(ticketData);
+
+    testContains('customer.missing_field', '', ticket);
+  });
+
+  test("customer web", function() {
+    ticket = new App.Ticket();
+    ticket.load(ticketData);
+
+    testContains('customer.web', 'cool', ticket);
+  });
+
+  test("organization name", function() {
+    ticket = new App.Ticket();
+    ticket.load(ticketData);
+
+    testContains('organization.name', 'gmbh', ticket);
+  });
+
+  test("organization shared", function() {
+    ticket = new App.Ticket();
+    ticket.load(ticketData);
+
+    testIs('organization.shared', true, ticket);
+  });
+
+  test("organization created_by_id", function() {
+    ticket = new App.Ticket();
+    ticket.load(ticketData);
+
+    testPreConditionUser('organization.created_by_id', 6, ticket);
+  });
+
+  test("organization updated_by_id", function() {
+    ticket = new App.Ticket();
+    ticket.load(ticketData);
+
+    testPreConditionUser('organization.updated_by_id', 6, ticket);
+  });
+
+  test("organization created_at", function() {
+    ticket = new App.Ticket();
+    ticket.load(ticketData);
+
+    testTime('organization.created_at', ticket.organization.created_at, ticket);
+  });
+
+  test("organization updated_at", function() {
+    ticket = new App.Ticket();
+    ticket.load(ticketData);
+
+    testTime('organization.updated_at', ticket.organization.updated_at, ticket);
+  });
+
+  test("organization domain_assignment", function() {
+    ticket = new App.Ticket();
+    ticket.load(ticketData);
+
+    testIs('organization.domain_assignment', false, ticket);
+  });
+
+  test("organization domain", function() {
+    ticket = new App.Ticket();
+    ticket.load(ticketData);
+
+    testContains('organization.domain', 'cool', ticket);
+  });
+}

+ 7 - 0
test/browser/aab_unit_test.rb

@@ -34,6 +34,13 @@ class AAbUnitTest < TestCase
       css: '.result .failed',
       value: '0',
     )
+
+    location(url: browser_url + '/tests_ticket_selector')
+    sleep 8
+    match(
+      css: '.result .failed',
+      value: '0',
+    )
   end
 
   def test_form