Merge branch 'develop' of github.com:martini/zammad into develop
This commit is contained in:
commit
591c537325
96 changed files with 2198 additions and 676 deletions
|
@ -206,14 +206,18 @@ class App.Controller extends Spine.Controller
|
|||
update = =>
|
||||
ui = @
|
||||
$('.humanTimeFromNow').each( ->
|
||||
item = $(this)
|
||||
# console.log('rewrite frontendTimeUpdate', this, $(this).hasClass('escalation'))
|
||||
timestamp = $(this).data('time')
|
||||
time = ui.humanTime( timestamp, $(this).hasClass('escalation') )
|
||||
$(this).attr( 'data-tooltip', App.i18n.translateTimestamp(timestamp) )
|
||||
$(this).html( time )
|
||||
ui.frontendTimeUpdateItem(item)
|
||||
)
|
||||
App.Interval.set( update, 30000, 'frontendTimeUpdate', 'ui' )
|
||||
|
||||
frontendTimeUpdateItem: (item) =>
|
||||
timestamp = item.data('time')
|
||||
time = @humanTime( timestamp, item.hasClass('escalation') )
|
||||
item.attr( 'data-tooltip', App.i18n.translateTimestamp(timestamp) )
|
||||
item.html( time )
|
||||
|
||||
ticketPopups: (position = 'right') ->
|
||||
|
||||
# open ticket in new task if curent user agent
|
||||
|
@ -243,10 +247,15 @@ class App.Controller extends Spine.Controller
|
|||
content: ->
|
||||
ticket_id = $(@).data('id')
|
||||
ticket = App.Ticket.fullLocal( ticket_id )
|
||||
ticket.humanTime = ui.humanTime(ticket.created_at)
|
||||
App.view('popover/ticket')(
|
||||
html = App.view('popover/ticket')(
|
||||
ticket: ticket
|
||||
)
|
||||
html = $( html )
|
||||
html.find('.humanTimeFromNow').each( ->
|
||||
item = $(this)
|
||||
ui.frontendTimeUpdateItem(item)
|
||||
)
|
||||
html
|
||||
)
|
||||
|
||||
ticketPopupsDestroy: =>
|
||||
|
@ -369,7 +378,7 @@ class App.Controller extends Spine.Controller
|
|||
|
||||
userTicketPopups: (params) ->
|
||||
|
||||
show = (data, tickets) =>
|
||||
show = (data, ticket_list) =>
|
||||
|
||||
if !data.position
|
||||
data.position = 'left'
|
||||
|
@ -377,7 +386,7 @@ class App.Controller extends Spine.Controller
|
|||
@userTicketPopupsDestroy()
|
||||
|
||||
# show user popup
|
||||
controller = @
|
||||
ui = @
|
||||
@userTicketPopupsList = @el.find(data.selector).popover(
|
||||
trigger: 'hover'
|
||||
container: 'body'
|
||||
|
@ -390,16 +399,21 @@ class App.Controller extends Spine.Controller
|
|||
|
||||
content: ->
|
||||
type = $(@).filter('[data-type]').data('type')
|
||||
data = tickets[type] || []
|
||||
|
||||
# set human time
|
||||
for ticket in data
|
||||
ticket.humanTime = controller.humanTime(ticket.created_at)
|
||||
tickets = []
|
||||
if ticket_list[type]
|
||||
for ticket_id in ticket_list[type]
|
||||
tickets.push App.Ticket.fullLocal( ticket_id )
|
||||
|
||||
# insert data
|
||||
App.view('popover/user_ticket_list')(
|
||||
tickets: data,
|
||||
html = App.view('popover/user_ticket_list')(
|
||||
tickets: tickets
|
||||
)
|
||||
html = $( html )
|
||||
html.find('.humanTimeFromNow').each( ->
|
||||
item = $(this)
|
||||
ui.frontendTimeUpdateItem(item)
|
||||
)
|
||||
html
|
||||
)
|
||||
|
||||
fetch = (params) =>
|
||||
|
@ -411,14 +425,18 @@ class App.Controller extends Spine.Controller
|
|||
}
|
||||
processData: true,
|
||||
success: (data, status, xhr) =>
|
||||
App.Store.write( "user-ticket-popover::#{params.user_id}", data.tickets )
|
||||
show( params, data.tickets )
|
||||
App.Store.write( "user-ticket-popover::#{params.user_id}", data )
|
||||
|
||||
# load assets
|
||||
App.Collection.loadAssets( data.assets )
|
||||
|
||||
show( params, { open: data.ticket_ids_open, closed: data.ticket_ids_closed } )
|
||||
)
|
||||
|
||||
# get data
|
||||
tickets = App.Store.get( "user-ticket-popover::#{params.user_id}" )
|
||||
if tickets
|
||||
show( params, tickets )
|
||||
data = App.Store.get( "user-ticket-popover::#{params.user_id}" )
|
||||
if data
|
||||
show( params, { open: data.ticket_ids_open, closed: data.ticket_ids_closed } )
|
||||
@delay(
|
||||
=>
|
||||
fetch(params)
|
||||
|
|
|
@ -175,7 +175,7 @@ class App.ControllerForm extends App.Controller
|
|||
###
|
||||
|
||||
formGenItem: (attribute_config, classname, form, attribute_count ) ->
|
||||
attribute = clone( attribute_config )
|
||||
attribute = clone( attribute_config, true )
|
||||
|
||||
# create item id
|
||||
attribute.id = classname + '_' + attribute.name
|
||||
|
@ -259,6 +259,29 @@ class App.ControllerForm extends App.Controller
|
|||
|
||||
# build options list
|
||||
if _.isEmpty(attribute.options)
|
||||
attribute.options = [
|
||||
{ name: 'yes', value: true }
|
||||
{ name: 'no', value: false }
|
||||
]
|
||||
|
||||
# set data type
|
||||
if attribute.name
|
||||
attribute.name = '{boolean}' + attribute.name
|
||||
|
||||
# finde selected item of list
|
||||
for record in attribute.options
|
||||
if record.value is attribute.value
|
||||
record.selected = 'selected'
|
||||
|
||||
# return item
|
||||
item = $( App.view('generic/select')( attribute: attribute ) )
|
||||
|
||||
else if attribute.tag is 'active'
|
||||
|
||||
# active attribute is always required
|
||||
attribute.null = false
|
||||
|
||||
# build options list
|
||||
attribute.options = [
|
||||
{ name: 'active', value: true }
|
||||
{ name: 'inactive', value: false }
|
||||
|
@ -320,12 +343,13 @@ class App.ControllerForm extends App.Controller
|
|||
number
|
||||
if !reset && (year isnt '' && month isnt '' && day isnt '')
|
||||
time = new Date( Date.parse( "#{year}-#{format(month)}-#{format(day)}T00:00:00Z" ) )
|
||||
time.setMinutes( time.getMinutes() + diff + time.getTimezoneOffset() )
|
||||
else
|
||||
time = new Date()
|
||||
#time.setMinutes( time.getMinutes() + diff + time.getTimezoneOffset() )
|
||||
item.closest('.form-group').find("[name=\"{date}#{name}___day\"]").val( time.getUTCDate() )
|
||||
item.closest('.form-group').find("[name=\"{date}#{name}___month\"]").val( time.getUTCMonth()+1 )
|
||||
item.closest('.form-group').find("[name=\"{date}#{name}___year\"]").val( time.getUTCFullYear() )
|
||||
time.setMinutes( time.getMinutes() + diff )
|
||||
item.closest('.form-group').find("[name=\"{date}#{name}___day\"]").val( time.getDate() )
|
||||
item.closest('.form-group').find("[name=\"{date}#{name}___month\"]").val( time.getMonth()+1 )
|
||||
item.closest('.form-group').find("[name=\"{date}#{name}___year\"]").val( time.getFullYear() )
|
||||
|
||||
item.find('.js-today').bind('click', (e) ->
|
||||
e.preventDefault()
|
||||
|
@ -462,9 +486,10 @@ class App.ControllerForm extends App.Controller
|
|||
number
|
||||
if !reset && (year isnt '' && month isnt '' && day isnt '' && hour isnt '' && day isnt '')
|
||||
time = new Date( Date.parse( "#{year}-#{format(month)}-#{format(day)}T#{format(hour)}:#{format(minute)}:00Z" ) )
|
||||
time.setMinutes( time.getMinutes() + diff + time.getTimezoneOffset() )
|
||||
else
|
||||
time = new Date()
|
||||
time.setMinutes( time.getMinutes() + diff + time.getTimezoneOffset() )
|
||||
time.setMinutes( time.getMinutes() + diff )
|
||||
#console.log('T', time, time.getHours(), time.getMinutes())
|
||||
item.closest('.form-group').find("[name=\"{datetime}#{name}___day\"]").val( time.getDate() )
|
||||
item.closest('.form-group').find("[name=\"{datetime}#{name}___month\"]").val( time.getMonth()+1 )
|
||||
|
|
|
@ -98,8 +98,8 @@ class App.ControllerGenericEdit extends App.ControllerModal
|
|||
|
||||
class App.ControllerGenericIndex extends App.Controller
|
||||
events:
|
||||
'click [data-type=edit]': 'edit'
|
||||
'click [data-type=new]': 'new'
|
||||
'click [data-type = edit]': 'edit'
|
||||
'click [data-type = new]': 'new'
|
||||
|
||||
constructor: ->
|
||||
super
|
||||
|
@ -160,6 +160,7 @@ class App.ControllerGenericIndex extends App.Controller
|
|||
bindRow:
|
||||
events:
|
||||
'click': @edit
|
||||
container: @container
|
||||
},
|
||||
@pageData.tableExtend
|
||||
)
|
||||
|
@ -177,6 +178,7 @@ class App.ControllerGenericIndex extends App.Controller
|
|||
id: item.id
|
||||
pageData: @pageData
|
||||
genericObject: @genericObject
|
||||
container: @container
|
||||
)
|
||||
|
||||
new: (e) ->
|
||||
|
@ -184,6 +186,7 @@ class App.ControllerGenericIndex extends App.Controller
|
|||
new App.ControllerGenericNew(
|
||||
pageData: @pageData
|
||||
genericObject: @genericObject
|
||||
container: @container
|
||||
)
|
||||
|
||||
class App.ControllerGenericDestroyConfirm extends App.ControllerModal
|
||||
|
|
|
@ -82,7 +82,7 @@ class App.ControllerTable extends App.Controller
|
|||
el: element
|
||||
overview: ['time', 'area', 'level', 'browser', 'location', 'data']
|
||||
attributes: [
|
||||
{ name: 'time', display: 'Time', type: 'time' },
|
||||
{ name: 'time', display: 'Time', tag: 'datetime' },
|
||||
{ name: 'area', display: 'Area', type: 'text' },
|
||||
{ name: 'level', display: 'Level', type: 'text' },
|
||||
{ name: 'browser', display: 'Browser', type: 'text' },
|
||||
|
@ -235,13 +235,14 @@ class App.ControllerTable extends App.Controller
|
|||
|
||||
# bind on delete dialog
|
||||
if data.model && destroy
|
||||
table.delegate('[data-type="destroy"]', 'click', (e) ->
|
||||
table.delegate('[data-type="destroy"]', 'click', (e) =>
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
itemId = $(e.target).parents('tr').data('id')
|
||||
item = data.model.find(itemId)
|
||||
new App.ControllerGenericDestroyConfirm(
|
||||
item: item
|
||||
container: @container
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
@ -64,11 +64,16 @@ class App.ChannelEmailFilter extends App.Controller
|
|||
|
||||
new: (e) =>
|
||||
e.preventDefault()
|
||||
new App.ChannelEmailFilterEdit( {} )
|
||||
new App.ChannelEmailFilterEdit(
|
||||
container: @el.closest('.content')
|
||||
)
|
||||
|
||||
edit: (id, e) =>
|
||||
e.preventDefault()
|
||||
new App.ChannelEmailFilterEdit( object: App.PostmasterFilter.find(id) )
|
||||
new App.ChannelEmailFilterEdit(
|
||||
object: App.PostmasterFilter.find(id)
|
||||
container: @el.closest('.content')
|
||||
)
|
||||
|
||||
class App.ChannelEmailFilterEdit extends App.ControllerModal
|
||||
constructor: ->
|
||||
|
@ -152,12 +157,17 @@ class App.ChannelEmailAddress extends App.Controller
|
|||
|
||||
new: (e) =>
|
||||
e.preventDefault()
|
||||
new App.ChannelEmailAddressEdit( {} )
|
||||
new App.ChannelEmailAddressEdit(
|
||||
container: @el.closest('.content')
|
||||
)
|
||||
|
||||
edit: (id, e) =>
|
||||
e.preventDefault()
|
||||
item = App.EmailAddress.find(id)
|
||||
new App.ChannelEmailAddressEdit( object: item )
|
||||
new App.ChannelEmailAddressEdit(
|
||||
object: item
|
||||
container: @el.closest('.content')
|
||||
)
|
||||
|
||||
class App.ChannelEmailAddressEdit extends App.ControllerModal
|
||||
constructor: ->
|
||||
|
@ -238,12 +248,17 @@ class App.ChannelEmailSignature extends App.Controller
|
|||
|
||||
new: (e) =>
|
||||
e.preventDefault()
|
||||
new App.ChannelEmailSignatureEdit( {} )
|
||||
new App.ChannelEmailSignatureEdit(
|
||||
container: @el.closest('.content')
|
||||
)
|
||||
|
||||
edit: (id, e) =>
|
||||
e.preventDefault()
|
||||
item = App.Signature.find(id)
|
||||
new App.ChannelEmailSignatureEdit( object: item )
|
||||
new App.ChannelEmailSignatureEdit(
|
||||
object: item
|
||||
container: @el.closest('.content')
|
||||
)
|
||||
|
||||
class App.ChannelEmailSignatureEdit extends App.ControllerModal
|
||||
constructor: ->
|
||||
|
@ -324,12 +339,17 @@ class App.ChannelEmailInbound extends App.Controller
|
|||
|
||||
new: (e) =>
|
||||
e.preventDefault()
|
||||
new App.ChannelEmailInboundEdit( {} )
|
||||
new App.ChannelEmailInboundEdit(
|
||||
container: @el.closest('.content')
|
||||
)
|
||||
|
||||
edit: (id, e) =>
|
||||
e.preventDefault()
|
||||
item = App.Channel.find(id)
|
||||
new App.ChannelEmailInboundEdit( object: item )
|
||||
new App.ChannelEmailInboundEdit(
|
||||
object: item
|
||||
container: @el.closest('.content')
|
||||
)
|
||||
|
||||
|
||||
class App.ChannelEmailInboundEdit extends App.ControllerModal
|
||||
|
|
|
@ -4,7 +4,6 @@ App.Config.set( 'User', {
|
|||
callback: ->
|
||||
item = {}
|
||||
item['name'] = App.Session.get( 'login' )
|
||||
item['image'] = App.Session.get( 'imageUrl' )
|
||||
if App.Session.get()
|
||||
item['avatar'] = App.Session.get().avatar()
|
||||
return item
|
||||
|
|
|
@ -486,6 +486,7 @@ class Sidebar extends App.Controller
|
|||
title: 'Users'
|
||||
object: 'User'
|
||||
objects: 'Users'
|
||||
container: @el.closest('.content')
|
||||
)
|
||||
items.push {
|
||||
head: 'Customer'
|
||||
|
@ -511,6 +512,7 @@ class Sidebar extends App.Controller
|
|||
title: 'Organizations'
|
||||
object: 'Organization'
|
||||
objects: 'Organizations'
|
||||
container: @el.closest('.content')
|
||||
)
|
||||
showOrganization = (el) =>
|
||||
new App.WidgetOrganization(
|
||||
|
@ -523,6 +525,7 @@ class Sidebar extends App.Controller
|
|||
icon: 'group'
|
||||
actions: [
|
||||
{
|
||||
title: 'Edit Organization'
|
||||
name: 'Edit Organization'
|
||||
class: 'glyphicon glyphicon-edit'
|
||||
callback: editOrganization
|
||||
|
|
|
@ -6,22 +6,22 @@ class Index extends App.ControllerContent
|
|||
return if !@authenticate()
|
||||
|
||||
new App.ControllerGenericIndex(
|
||||
el: @el,
|
||||
id: @id,
|
||||
genericObject: 'Group',
|
||||
pageData: {
|
||||
title: 'Groups',
|
||||
home: 'groups',
|
||||
object: 'Group',
|
||||
objects: 'Groups',
|
||||
navupdate: '#groups',
|
||||
el: @el
|
||||
id: @id
|
||||
genericObject: 'Group'
|
||||
pageData:
|
||||
title: 'Groups'
|
||||
home: 'groups'
|
||||
object: 'Group'
|
||||
objects: 'Groups'
|
||||
navupdate: '#groups'
|
||||
notes: [
|
||||
'Groups are ...'
|
||||
],
|
||||
]
|
||||
buttons: [
|
||||
{ name: 'New Group', 'data-type': 'new', class: 'btn--success' },
|
||||
],
|
||||
},
|
||||
]
|
||||
container: @el.closest('.content')
|
||||
)
|
||||
|
||||
App.Config.set( 'Group', { prio: 1500, name: 'Groups', parent: '#manage', target: '#manage/groups', controller: Index, role: ['Admin'] }, 'NavBarAdmin' )
|
|
@ -156,11 +156,8 @@ class App.Navigation extends App.Controller
|
|||
area.result = []
|
||||
for id in area.ids
|
||||
ticket = App.Ticket.find( id )
|
||||
ticket.humanTime = @humanTime(ticket.created_at)
|
||||
data =
|
||||
display: "##{ticket.number} - #{ticket.title}"
|
||||
createt_at: "#{ticket.created_at}"
|
||||
humanTime: "#{ticket.humanTime}"
|
||||
id: ticket.id
|
||||
class: "task level-1 ticket-popover"
|
||||
url: ticket.uiUrl()
|
||||
|
|
|
@ -7,10 +7,10 @@ class Index extends App.ControllerTabs
|
|||
|
||||
# get data
|
||||
@ajax(
|
||||
id: 'object_manager_attributes_list',
|
||||
type: 'GET',
|
||||
url: @apiPath + '/object_manager_attributes_list',
|
||||
processData: true,
|
||||
id: 'object_manager_attributes_list'
|
||||
type: 'GET'
|
||||
url: @apiPath + '/object_manager_attributes_list'
|
||||
processData: true
|
||||
success: (data, status, xhr) =>
|
||||
@build(data.objects)
|
||||
)
|
||||
|
@ -19,9 +19,9 @@ class Index extends App.ControllerTabs
|
|||
@tabs = []
|
||||
for object in objects
|
||||
item =
|
||||
name: object,
|
||||
target: "c-#{object}",
|
||||
controller: Items,
|
||||
name: object
|
||||
target: "c-#{object}"
|
||||
controller: Items
|
||||
params:
|
||||
object: object
|
||||
@tabs.push item
|
||||
|
@ -120,18 +120,19 @@ class Items extends App.ControllerContent
|
|||
objects: 'ObjectManagerAttributes'
|
||||
navupdate: '#object_manager'
|
||||
genericObject: 'ObjectManagerAttribute'
|
||||
container: @el.closest('.content')
|
||||
)
|
||||
|
||||
edit: (e) =>
|
||||
e.preventDefault()
|
||||
id = $( e.target ).closest('tr').data('id')
|
||||
new Edit(
|
||||
pageData: {
|
||||
pageData:
|
||||
object: 'ObjectManagerAttribute'
|
||||
},
|
||||
genericObject: 'ObjectManagerAttribute'
|
||||
callback: @render
|
||||
id: id
|
||||
container: @el.closest('.content')
|
||||
)
|
||||
|
||||
destroy: (e) ->
|
||||
|
@ -158,7 +159,6 @@ class Edit extends App.ControllerModal
|
|||
items: []
|
||||
) )
|
||||
|
||||
|
||||
item = App.ObjectManagerAttribute.find(@id)
|
||||
|
||||
options =
|
||||
|
@ -249,9 +249,8 @@ class Edit extends App.ControllerModal
|
|||
@content.find('[name=data_type]').trigger('change')
|
||||
|
||||
|
||||
|
||||
configureAttributesBottom = [
|
||||
{ name: 'active', display: 'Active', tag: 'boolean', 'default': true, 'null': false },
|
||||
{ name: 'active', display: 'Active', tag: 'active', default: true },
|
||||
]
|
||||
controller = new App.ControllerForm(
|
||||
model: { configure_attributes: configureAttributesBottom, className: '' },
|
||||
|
@ -263,8 +262,6 @@ class Edit extends App.ControllerModal
|
|||
#@content = controller.form
|
||||
|
||||
|
||||
|
||||
|
||||
#@show(content)
|
||||
@show()
|
||||
|
||||
|
|
|
@ -7,9 +7,9 @@ class App.OrganizationHistory extends App.GenericHistory
|
|||
|
||||
# get data
|
||||
@ajax(
|
||||
id: 'organization_history',
|
||||
type: 'GET',
|
||||
url: @apiPath + '/organizations/history/' + @organization_id,
|
||||
id: 'organization_history'
|
||||
type: 'GET'
|
||||
url: @apiPath + '/organizations/history/' + @organization_id
|
||||
success: (data, status, xhr) =>
|
||||
|
||||
# load assets
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
class App.OrganizationProfile extends App.Controller
|
||||
events:
|
||||
'focusout [contenteditable]': 'update'
|
||||
|
||||
constructor: (params) ->
|
||||
super
|
||||
|
||||
|
@ -12,11 +9,8 @@ class App.OrganizationProfile extends App.Controller
|
|||
|
||||
@navupdate '#'
|
||||
|
||||
# subscribe and reload data / fetch new data if triggered
|
||||
@subscribeId = App.Organization.full( @organization_id, @render, false, true )
|
||||
|
||||
release: =>
|
||||
App.Organization.unsubscribe(@subscribeId)
|
||||
# fetch new data if needed
|
||||
App.Organization.full( @organization_id, @render )
|
||||
|
||||
meta: =>
|
||||
meta =
|
||||
|
@ -48,6 +42,39 @@ class App.OrganizationProfile extends App.Controller
|
|||
@doNotLog = 1
|
||||
@recentView( 'Organization', @organization_id )
|
||||
|
||||
@html App.view('organization_profile/index')(
|
||||
organization: organization
|
||||
)
|
||||
|
||||
new Object(
|
||||
el: @$('.js-object-container')
|
||||
organization: organization
|
||||
)
|
||||
|
||||
new App.TicketStats(
|
||||
el: @$('.js-ticket-stats')
|
||||
organization: organization
|
||||
)
|
||||
|
||||
new App.UpdateTastbar(
|
||||
genericObject: organization
|
||||
)
|
||||
|
||||
class Object extends App.Controller
|
||||
events:
|
||||
'focusout [contenteditable]': 'update'
|
||||
|
||||
constructor: (params) ->
|
||||
super
|
||||
|
||||
# subscribe and reload data / fetch new data if triggered
|
||||
@subscribeId = App.Organization.full( @organization.id, @render, false, true )
|
||||
|
||||
release: =>
|
||||
App.Organization.unsubscribe(@subscribeId)
|
||||
|
||||
render: (organization) =>
|
||||
|
||||
# get display data
|
||||
organizationData = []
|
||||
for attributeName, attributeConfig of App.Organization.attributesGet('view')
|
||||
|
@ -65,7 +92,7 @@ class App.OrganizationProfile extends App.Controller
|
|||
if name isnt 'name'
|
||||
organizationData.push attributeConfig
|
||||
|
||||
@html App.view('organization_profile')(
|
||||
@html App.view('organization_profile/object')(
|
||||
organization: organization
|
||||
organizationData: organizationData
|
||||
)
|
||||
|
@ -76,15 +103,6 @@ class App.OrganizationProfile extends App.Controller
|
|||
maxlength: 250
|
||||
})
|
||||
|
||||
new App.TicketStats(
|
||||
el: @$('.js-ticket-stats')
|
||||
organization: organization
|
||||
)
|
||||
|
||||
new App.UpdateTastbar(
|
||||
genericObject: organization
|
||||
)
|
||||
|
||||
# start action controller
|
||||
showHistory = =>
|
||||
new App.OrganizationHistory( organization_id: organization.id )
|
||||
|
@ -97,6 +115,7 @@ class App.OrganizationProfile extends App.Controller
|
|||
title: 'Organizations'
|
||||
object: 'Organization'
|
||||
objects: 'Organizations'
|
||||
container: @el.closest('.content')
|
||||
)
|
||||
|
||||
actions = [
|
||||
|
@ -120,13 +139,14 @@ class App.OrganizationProfile extends App.Controller
|
|||
update: (e) =>
|
||||
name = $(e.target).attr('data-name')
|
||||
value = $(e.target).html()
|
||||
org = App.Organization.find( @organization_id )
|
||||
org = App.Organization.find( @organization.id )
|
||||
if org[name] isnt value
|
||||
data = {}
|
||||
data[name] = value
|
||||
org.updateAttributes( data )
|
||||
@log 'notice', 'update', name, value, org
|
||||
|
||||
|
||||
class Router extends App.ControllerPermanent
|
||||
constructor: (params) ->
|
||||
super
|
||||
|
|
|
@ -6,22 +6,22 @@ class Index extends App.ControllerContent
|
|||
return if !@authenticate()
|
||||
|
||||
new App.ControllerGenericIndex(
|
||||
el: @el,
|
||||
id: @id,
|
||||
genericObject: 'Organization',
|
||||
pageData: {
|
||||
title: 'Organizations',
|
||||
home: 'organizations',
|
||||
object: 'Organization',
|
||||
objects: 'Organizations',
|
||||
navupdate: '#organizations',
|
||||
el: @el
|
||||
id: @id
|
||||
genericObject: 'Organization'
|
||||
pageData:
|
||||
title: 'Organizations'
|
||||
home: 'organizations'
|
||||
object: 'Organization'
|
||||
objects: 'Organizations'
|
||||
navupdate: '#organizations'
|
||||
notes: [
|
||||
'Organizations are for any person in the system. Agents (Owners, Resposbiles, ...) and Customers.'
|
||||
],
|
||||
]
|
||||
buttons: [
|
||||
{ name: 'New Organization', 'data-type': 'new', class: 'btn--success' },
|
||||
],
|
||||
},
|
||||
{ name: 'New Organization', 'data-type': 'new', class: 'btn--success' }
|
||||
]
|
||||
container: @el.closest('.content')
|
||||
)
|
||||
|
||||
App.Config.set( 'Organization', { prio: 2000, name: 'Organizations', parent: '#manage', target: '#manage/organizations', controller: Index, role: ['Admin'] }, 'NavBarAdmin' )
|
|
@ -6,22 +6,22 @@ class Index extends App.ControllerContent
|
|||
return if !@authenticate()
|
||||
|
||||
new App.ControllerGenericIndex(
|
||||
el: @el,
|
||||
id: @id,
|
||||
genericObject: 'Overview',
|
||||
pageData: {
|
||||
title: 'Overviews',
|
||||
home: 'overviews',
|
||||
object: 'Overview',
|
||||
objects: 'Overviews',
|
||||
navupdate: '#overviews',
|
||||
el: @el
|
||||
id: @id
|
||||
genericObject: 'Overview'
|
||||
pageData:
|
||||
title: 'Overviews'
|
||||
home: 'overviews'
|
||||
object: 'Overview'
|
||||
objects: 'Overviews'
|
||||
navupdate: '#overviews'
|
||||
notes: [
|
||||
'Overview are ...'
|
||||
],
|
||||
]
|
||||
buttons: [
|
||||
{ name: 'New Overview', 'data-type': 'new', class: 'btn--success' },
|
||||
],
|
||||
},
|
||||
{ name: 'New Overview', 'data-type': 'new', class: 'btn--success' }
|
||||
]
|
||||
container: @el.closest('.content')
|
||||
)
|
||||
|
||||
App.Config.set( 'Overview', { prio: 2300, name: 'Overviews', parent: '#manage', target: '#manage/overviews', controller: Index, role: ['Admin'] }, 'NavBarAdmin' )
|
|
@ -6,22 +6,22 @@ class Index extends App.ControllerContent
|
|||
return if !@authenticate()
|
||||
|
||||
new App.ControllerGenericIndex(
|
||||
el: @el,
|
||||
id: @id,
|
||||
genericObject: 'Job',
|
||||
pageData: {
|
||||
title: 'Schedulers',
|
||||
home: 'schedulers',
|
||||
object: 'Scheduler',
|
||||
objects: 'Schedulers',
|
||||
navupdate: '#schedulers',
|
||||
el: @el
|
||||
id: @id
|
||||
genericObject: 'Job'
|
||||
pageData:
|
||||
title: 'Schedulers'
|
||||
home: 'schedulers'
|
||||
object: 'Scheduler'
|
||||
objects: 'Schedulers'
|
||||
navupdate: '#schedulers'
|
||||
notes: [
|
||||
'Scheduler are ...'
|
||||
],
|
||||
]
|
||||
buttons: [
|
||||
{ name: 'New Scheduler', 'data-type': 'new', class: 'btn--success' },
|
||||
],
|
||||
},
|
||||
{ name: 'New Scheduler', 'data-type': 'new', class: 'btn--success' }
|
||||
]
|
||||
container: @el.closest('.content')
|
||||
)
|
||||
|
||||
App.Config.set( 'Scheduler', { prio: 3000, name: 'Schedulers', parent: '#manage', target: '#manage/schedulers', controller: Index, role: ['Admin'] }, 'NavBarAdmin' )
|
|
@ -6,22 +6,22 @@ class Index extends App.ControllerContent
|
|||
return if !@authenticate()
|
||||
|
||||
new App.ControllerGenericIndex(
|
||||
el: @el,
|
||||
id: @id,
|
||||
genericObject: 'Sla',
|
||||
pageData: {
|
||||
title: 'SLA',
|
||||
home: 'slas',
|
||||
object: 'SLA',
|
||||
objects: 'SLAs',
|
||||
navupdate: '#slas',
|
||||
el: @el
|
||||
id: @id
|
||||
genericObject: 'Sla'
|
||||
pageData:
|
||||
title: 'SLA'
|
||||
home: 'slas'
|
||||
object: 'SLA'
|
||||
objects: 'SLAs'
|
||||
navupdate: '#slas'
|
||||
notes: [
|
||||
# 'SLA are ...'
|
||||
],
|
||||
]
|
||||
buttons: [
|
||||
{ name: 'New SLA', 'data-type': 'new', class: 'btn--success' },
|
||||
],
|
||||
},
|
||||
{ name: 'New SLA', 'data-type': 'new', class: 'btn--success' }
|
||||
]
|
||||
container: @el.closest('.content')
|
||||
)
|
||||
|
||||
App.Config.set( 'Sla', { prio: 2900, name: 'SLAs', parent: '#manage', target: '#manage/slas', controller: Index, role: ['Admin'] }, 'NavBarAdmin' )
|
|
@ -6,22 +6,22 @@ class Index extends App.ControllerContent
|
|||
return if !@authenticate()
|
||||
|
||||
new App.ControllerGenericIndex(
|
||||
el: @el,
|
||||
id: @id,
|
||||
genericObject: 'TextModule',
|
||||
pageData: {
|
||||
title: 'TextModules',
|
||||
home: 'text_modules',
|
||||
object: 'TextModule',
|
||||
objects: 'TextModules',
|
||||
navupdate: '#text_modules',
|
||||
el: @el
|
||||
id: @id
|
||||
genericObject: 'TextModule'
|
||||
pageData:
|
||||
title: 'TextModules'
|
||||
home: 'text_modules'
|
||||
object: 'TextModule'
|
||||
objects: 'TextModules'
|
||||
navupdate: '#text_modules'
|
||||
notes: [
|
||||
'TextModules are ...'
|
||||
],
|
||||
]
|
||||
buttons: [
|
||||
{ name: 'New TextModule', 'data-type': 'new', class: 'btn--success' },
|
||||
],
|
||||
},
|
||||
{ name: 'New TextModule', 'data-type': 'new', class: 'btn--success' }
|
||||
]
|
||||
container: @el.closest('.content')
|
||||
)
|
||||
|
||||
App.Config.set( 'TextModule', { prio: 2300, name: 'TextModules', parent: '#manage', target: '#manage/text_modules', controller: Index, role: ['Admin'] }, 'NavBarAdmin' )
|
|
@ -482,7 +482,7 @@ class Table extends App.ControllerContent
|
|||
new App.OverviewSettings(
|
||||
overview_id: @overview.id
|
||||
view_mode: @view_mode
|
||||
container: @el
|
||||
container: @el.closest('.content')
|
||||
)
|
||||
|
||||
class App.OverviewSettings extends App.ControllerModal
|
||||
|
|
|
@ -124,17 +124,27 @@ class App.TicketZoom extends App.Controller
|
|||
@doNotLog = 1
|
||||
@recentView( 'Ticket', ticket_id )
|
||||
|
||||
error: (xhr, status, error) =>
|
||||
error: (xhr) =>
|
||||
statusText = xhr.statusText
|
||||
status = xhr.status
|
||||
detail = xhr.responseText
|
||||
#console.log('error', status, statusText)
|
||||
|
||||
# do not close window if request is aborted
|
||||
return if status is 'abort'
|
||||
# ignore if request is aborted
|
||||
if statusText is 'abort'
|
||||
return
|
||||
|
||||
# if ticket is already loaded, ignore status "0" - network issues e. g. temp. not connection
|
||||
if @ticketUpdatedAtLastCall && status is 0
|
||||
console.log('network issues e. g. temp. not connection', status, statusText, detail)
|
||||
return
|
||||
|
||||
# show error message
|
||||
if xhr.status is 401 || error is 'Unauthorized'
|
||||
if status is 401 || statusText is 'Unauthorized'
|
||||
@taskHead = '» ' + App.i18n.translateInline('Unauthorized') + ' «'
|
||||
@taskIconClass = 'error'
|
||||
@html App.view('generic/error/unauthorized')( objectName: 'Ticket' )
|
||||
else if xhr.status is 404 || error is 'Not Found'
|
||||
else if status is 404 || statusText is 'Not Found'
|
||||
@taskHead = '» ' + App.i18n.translateInline('Not Found') + ' «'
|
||||
@taskIconClass = 'error'
|
||||
@html App.view('generic/error/not_found')( objectName: 'Ticket' )
|
||||
|
@ -142,9 +152,7 @@ class App.TicketZoom extends App.Controller
|
|||
@taskHead = '» ' + App.i18n.translateInline('Error') + ' «'
|
||||
@taskIconClass = 'error'
|
||||
|
||||
status = xhr.status
|
||||
detail = xhr.responseText
|
||||
if !status && !detail
|
||||
if !detail
|
||||
detail = 'General communication error, maybe internet is not available!'
|
||||
@html App.view('generic/error/generic')(
|
||||
status: status
|
||||
|
@ -321,15 +329,20 @@ class App.TicketZoom extends App.Controller
|
|||
)
|
||||
|
||||
showTicketHistory = =>
|
||||
new App.TicketHistory( ticket_id: @ticket.id )
|
||||
new App.TicketHistory(
|
||||
ticket_id: @ticket.id
|
||||
container: @el.closest('.content')
|
||||
)
|
||||
showTicketMerge = =>
|
||||
new App.TicketMerge
|
||||
new App.TicketMerge(
|
||||
ticket: @ticket
|
||||
task_key: @task_key
|
||||
container: @el
|
||||
container: @el.closest('.content')
|
||||
)
|
||||
changeCustomer = (e, el) =>
|
||||
new App.TicketCustomer(
|
||||
ticket: @ticket
|
||||
container: @el.closest('.content')
|
||||
)
|
||||
items = [
|
||||
{
|
||||
|
@ -367,6 +380,7 @@ class App.TicketZoom extends App.Controller
|
|||
title: 'Users'
|
||||
object: 'User'
|
||||
objects: 'Users'
|
||||
container: @el.closest('.content')
|
||||
)
|
||||
showCustomer = (el) =>
|
||||
new App.WidgetUser(
|
||||
|
@ -400,6 +414,7 @@ class App.TicketZoom extends App.Controller
|
|||
title: 'Organizations'
|
||||
object: 'Organization'
|
||||
objects: 'Organizations'
|
||||
container: @el.closest('.content')
|
||||
)
|
||||
showOrganization = (el) =>
|
||||
new App.WidgetOrganization(
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
class App.UserProfile extends App.Controller
|
||||
events:
|
||||
'focusout [contenteditable]': 'update'
|
||||
|
||||
constructor: (params) ->
|
||||
super
|
||||
|
||||
|
@ -12,8 +9,8 @@ class App.UserProfile extends App.Controller
|
|||
|
||||
@navupdate '#'
|
||||
|
||||
# subscribe and reload data / fetch new data if triggered
|
||||
@subscribeId = App.User.full( @user_id, @render, false, true )
|
||||
# fetch new data if needed
|
||||
@subscribeId = App.User.full( @user_id, @render )
|
||||
|
||||
release: =>
|
||||
App.User.unsubscribe(@subscribeId)
|
||||
|
@ -47,6 +44,40 @@ class App.UserProfile extends App.Controller
|
|||
@doNotLog = 1
|
||||
@recentView( 'User', @user_id )
|
||||
|
||||
@html App.view('user_profile/index')(
|
||||
user: user
|
||||
)
|
||||
|
||||
new Object(
|
||||
el: @$('.js-object-container')
|
||||
user: user
|
||||
)
|
||||
|
||||
new App.TicketStats(
|
||||
el: @$('.js-ticket-stats')
|
||||
user: user
|
||||
)
|
||||
|
||||
new App.UpdateTastbar(
|
||||
genericObject: user
|
||||
)
|
||||
|
||||
|
||||
class Object extends App.Controller
|
||||
events:
|
||||
'focusout [contenteditable]': 'update'
|
||||
|
||||
constructor: (params) ->
|
||||
super
|
||||
|
||||
# subscribe and reload data / fetch new data if triggered
|
||||
@subscribeId = App.User.full( @user.id, @render, false, true )
|
||||
|
||||
release: =>
|
||||
App.User.unsubscribe(@subscribeId)
|
||||
|
||||
render: (user) =>
|
||||
|
||||
# get display data
|
||||
userData = []
|
||||
for attributeName, attributeConfig of App.User.attributesGet('view')
|
||||
|
@ -64,7 +95,7 @@ class App.UserProfile extends App.Controller
|
|||
if name isnt 'firstname' && name isnt 'lastname' && name isnt 'organization'
|
||||
userData.push attributeConfig
|
||||
|
||||
@html App.view('user_profile')(
|
||||
@html App.view('user_profile/object')(
|
||||
user: user
|
||||
userData: userData
|
||||
)
|
||||
|
@ -75,15 +106,6 @@ class App.UserProfile extends App.Controller
|
|||
maxlength: 250
|
||||
})
|
||||
|
||||
new App.TicketStats(
|
||||
el: @$('.js-ticket-stats')
|
||||
user: user
|
||||
)
|
||||
|
||||
new App.UpdateTastbar(
|
||||
genericObject: user
|
||||
)
|
||||
|
||||
# start action controller
|
||||
showHistory = =>
|
||||
new App.UserHistory( user_id: user.id )
|
||||
|
@ -97,6 +119,7 @@ class App.UserProfile extends App.Controller
|
|||
title: 'Users'
|
||||
object: 'User'
|
||||
objects: 'Users'
|
||||
container: @el.closest('.content')
|
||||
)
|
||||
|
||||
actions = [
|
||||
|
@ -120,7 +143,7 @@ class App.UserProfile extends App.Controller
|
|||
update: (e) =>
|
||||
name = $(e.target).attr('data-name')
|
||||
value = $(e.target).html()
|
||||
user = App.User.find( @user_id )
|
||||
user = App.User.find( @user.id )
|
||||
if user[name] isnt value
|
||||
data = {}
|
||||
data[name] = value
|
||||
|
|
|
@ -10,29 +10,117 @@
|
|||
#= require_tree ./lib/app_post
|
||||
|
||||
class App extends Spine.Controller
|
||||
@viewPrint: (object, attribute_name) ->
|
||||
attributes = {}
|
||||
if object.constructor.attributesGet
|
||||
attributes = object.constructor.attributesGet()
|
||||
attribute_config = attributes[attribute_name]
|
||||
value = object[attribute_name]
|
||||
valueRef = undefined
|
||||
|
||||
# check if relation is requested
|
||||
if !attribute_config
|
||||
attribute_name_new = "#{attribute_name}_id"
|
||||
attribute_config = attributes[attribute_name_new]
|
||||
if attribute_config
|
||||
attribute_name = attribute_name_new
|
||||
if object[attribute_name]
|
||||
valueRef = value
|
||||
value = object[attribute_name]
|
||||
|
||||
# in case of :: key, get the sub value
|
||||
if !value
|
||||
parts = attribute_name.split('::')
|
||||
if parts[0] && parts[1] && object[ parts[0] ]
|
||||
value = object[ parts[0] ][ parts[1] ]
|
||||
|
||||
#console.log('Pa', attribute_name, object, attribute_config, object[attribute_name], valueRef, value)
|
||||
|
||||
# if we have no config, get output this way
|
||||
if !attribute_config
|
||||
return @viewPrintItem( value )
|
||||
|
||||
# check if valueRef already exists, no lookup needed later
|
||||
if !valueRef
|
||||
attribute_name_without_ref = attribute_name.substr(attribute_name.length-3, attribute_name.length)
|
||||
if attribute_name_without_ref is '_id'
|
||||
attribute_name_without_ref = attribute_name.substr(0, attribute_name.length-3)
|
||||
if object[attribute_name_without_ref]
|
||||
valueRef = object[attribute_name_without_ref]
|
||||
|
||||
return @viewPrintItem( value, attribute_config, valueRef )
|
||||
|
||||
# define print name helper
|
||||
@viewPrintItem: ( item, attribute_config = {}, valueRef ) ->
|
||||
return '-' if item is undefined
|
||||
return '-' if item is ''
|
||||
return item if !item
|
||||
result = item
|
||||
|
||||
# lookup relation
|
||||
if attribute_config.relation || valueRef
|
||||
if valueRef
|
||||
item = valueRef
|
||||
else
|
||||
item = App[attribute_config.relation].find(item)
|
||||
|
||||
# if date is a object, get name of the object
|
||||
isObject = false
|
||||
if typeof item is 'object'
|
||||
isObject = true
|
||||
if item.displayNameLong
|
||||
result = item.displayNameLong()
|
||||
else if item.displayName
|
||||
result = item.displayName()
|
||||
else
|
||||
result = item.name
|
||||
|
||||
# execute callback on content
|
||||
if attribute_config.callback
|
||||
result = attribute_config.callback( result, attribute_config )
|
||||
|
||||
# text2html in textarea view
|
||||
isHtmlEscape = false
|
||||
if attribute_config.tag is 'textarea'
|
||||
isHtmlEscape = true
|
||||
result = App.Utils.text2html( result )
|
||||
|
||||
# remember, html snippets are already escaped
|
||||
else if attribute_config.tag is 'richtext'
|
||||
isHtmlEscape = true
|
||||
|
||||
# fillup options
|
||||
if !_.isEmpty(attribute_config.options)
|
||||
if attribute_config.options[result]
|
||||
result = attribute_config.options[result]
|
||||
|
||||
# translate content
|
||||
if attribute_config.translate || ( isObject && item.translate && item.translate() )
|
||||
isHtmlEscape = true
|
||||
result = App.i18n.translateContent( result )
|
||||
|
||||
# transform date
|
||||
if attribute_config.tag is 'date'
|
||||
isHtmlEscape = true
|
||||
result = App.i18n.translateDate(result)
|
||||
|
||||
# use pretty time for datetime
|
||||
else if attribute_config.tag is 'datetime'
|
||||
isHtmlEscape = true
|
||||
result = "<span class=\"humanTimeFromNow #{attribute_config.class}\" data-time=\"#{result}\">?</span>"
|
||||
#result = App.i18n.translateTimestamp(result)
|
||||
|
||||
if !isHtmlEscape && typeof result is 'string'
|
||||
result = App.Utils.htmlEscape(result)
|
||||
|
||||
result
|
||||
|
||||
@view: (name) ->
|
||||
template = ( params = {} ) =>
|
||||
|
||||
# define print name helper
|
||||
params.P = ( item, row = {} ) ->
|
||||
return '-' if item is undefined
|
||||
return '-' if item is ''
|
||||
return item if !item
|
||||
|
||||
# if date is a object, get name of the object
|
||||
if typeof item is 'object'
|
||||
if item.displayNameLong
|
||||
return item.displayNameLong()
|
||||
else if item.displayName
|
||||
return item.displayName()
|
||||
return item.name
|
||||
|
||||
# execute callback on content
|
||||
if row.callback
|
||||
return row.callback( item, row )
|
||||
|
||||
# return raw data
|
||||
item
|
||||
params.P = ( object, attribute_name ) ->
|
||||
App.viewPrint( object, attribute_name )
|
||||
|
||||
# define date format helper
|
||||
params.date = ( time ) ->
|
||||
|
|
|
@ -30,7 +30,7 @@ class App.Browser
|
|||
# define min. required browser version
|
||||
map =
|
||||
Chrome2: 37
|
||||
Firefox: 28
|
||||
Firefox: 31
|
||||
Explorer: 10
|
||||
Safari: 6
|
||||
Opera: 22
|
||||
|
|
|
@ -277,6 +277,7 @@ class App.UserOrganizationAutocompletion extends App.Controller
|
|||
e.preventDefault()
|
||||
new UserNew(
|
||||
parent: @
|
||||
container: @el.closest('.content')
|
||||
)
|
||||
|
||||
class UserNew extends App.ControllerModal
|
||||
|
|
|
@ -4,11 +4,11 @@ class App.EmailAddress extends App.Model
|
|||
@url: @apiPath + '/email_addresses'
|
||||
|
||||
@configure_attributes = [
|
||||
{ name: 'realname', display: 'Realname', tag: 'input', type: 'text', limit: 250, 'null': false, 'class': 'span4' },
|
||||
{ name: 'email', display: 'Email', tag: 'input', type: 'text', limit: 250, 'null': false, 'class': 'span4' },
|
||||
{ name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, 'null': true, 'class': 'span4' },
|
||||
{ name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 },
|
||||
{ name: 'active', display: 'Active', tag: 'boolean', type: 'boolean', 'default': true, 'null': false, 'class': 'span4' },
|
||||
{ name: 'realname', display: 'Realname', tag: 'input', type: 'text', limit: 250, null: false },
|
||||
{ name: 'email', display: 'Email', tag: 'input', type: 'text', limit: 250, null: false },
|
||||
{ name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, null: true },
|
||||
{ name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
|
||||
{ name: 'active', display: 'Active', tag: 'active', default: true },
|
||||
]
|
||||
@configure_overview = [
|
||||
'realname', 'email'
|
||||
|
|
|
@ -4,15 +4,15 @@ class App.Group extends App.Model
|
|||
@url: @apiPath + '/groups'
|
||||
|
||||
@configure_attributes = [
|
||||
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, 'null': false, 'class': 'span4' },
|
||||
{ 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, 'class': 'span4' },
|
||||
{ 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.', 'class': 'span4' },
|
||||
{ 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.', 'class': 'span4' },
|
||||
{ name: 'email_address_id', display: 'Email', tag: 'select', multiple: false, null: true, relation: 'EmailAddress', nulloption: true, class: 'span4' },
|
||||
{ name: 'signature_id', display: 'Signature', tag: 'select', multiple: false, null: true, relation: 'Signature', nulloption: true, class: 'span4' },
|
||||
{ name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, 'null': true, 'class': 'span4' },
|
||||
{ name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 },
|
||||
{ name: 'active', display: 'Active', tag: 'boolean', type: 'boolean', 'default': true, 'null': false, 'class': 'span4' },
|
||||
{ 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: 'email_address_id', display: 'Email', tag: 'select', multiple: false, null: true, relation: 'EmailAddress', nulloption: true },
|
||||
{ name: 'signature_id', display: 'Signature', tag: 'select', multiple: false, null: true, relation: 'Signature', nulloption: true },
|
||||
{ name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, null: true },
|
||||
{ name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
|
||||
{ name: 'active', display: 'Active', tag: 'active', default: true },
|
||||
]
|
||||
@configure_overview = [
|
||||
'name',
|
||||
|
|
|
@ -8,15 +8,15 @@ class App.Job extends App.Model
|
|||
{ name: 'condition', display: 'Conditions for matching objects.', tag: 'ticket_attribute_selection', null: true },
|
||||
{ name: 'execute', display: 'Execute changes on objects.', tag: 'ticket_attribute_set', null: true },
|
||||
{ name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, null: true },
|
||||
{ name: 'active', display: 'Active', tag: 'boolean', note: 'boolean', 'default': true, null: false },
|
||||
{ name: 'active', display: 'Active', tag: 'active', default: true },
|
||||
{ name: 'matching', display: 'Matching', readonly: 1 },
|
||||
{ name: 'processed', display: 'Processed', readonly: 1 },
|
||||
{ name: 'last_run_at', display: 'Last run', type: 'time', readonly: 1 },
|
||||
{ name: 'last_run_at', display: 'Last run', tag: 'datetime', readonly: 1 },
|
||||
{ name: 'running', display: 'Running', tag: 'boolean', readonly: 1 },
|
||||
{ name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 },
|
||||
{ name: 'created_at', display: 'Created', type: 'time', readonly: 1 },
|
||||
{ name: 'created_at', display: 'Created', tag: 'datetime', readonly: 1 },
|
||||
{ name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 },
|
||||
{ name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 },
|
||||
{ name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
|
||||
]
|
||||
@configure_delete = true
|
||||
@configure_overview = [
|
||||
|
|
|
@ -2,8 +2,8 @@ class App.Network extends App.Model
|
|||
@configure 'Network', 'name', 'note', 'active', 'updated_at'
|
||||
@extend Spine.Model.Ajax
|
||||
@configure_attributes = [
|
||||
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, 'null': false, 'class': 'xlarge' },
|
||||
{ name: 'note', display: 'Note', note: 'Notes are visible to agents only, never to customers.', tag: 'textarea', limit: 250, 'null': true, 'class': 'xlarge' },
|
||||
{ name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 },
|
||||
{ name: 'active', display: 'Active', tag: 'boolean', type: 'boolean', 'default': true, 'null': false, 'class': 'xlarge' },
|
||||
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false },
|
||||
{ name: 'note', display: 'Note', note: 'Notes are visible to agents only, never to customers.', tag: 'textarea', limit: 250, null: true },
|
||||
{ name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
|
||||
{ name: 'active', display: 'Active', tag: 'active', default: true },
|
||||
]
|
||||
|
|
|
@ -3,13 +3,13 @@ class App.ObjectManagerAttribute extends App.Model
|
|||
@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: '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: 'boolean', 'default': true, 'null': false },
|
||||
{ name: 'data_type', display: 'Format', tag: 'input', type: 'text', limit: 100, 'null': false },
|
||||
{ name: 'updated_at', display: 'Updated', type: 'time', 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 },
|
||||
]
|
||||
@configure_overview = [
|
||||
#'name',
|
||||
|
|
|
@ -3,11 +3,11 @@ class App.Organization extends App.Model
|
|||
@extend Spine.Model.Ajax
|
||||
@url: @apiPath + '/organizations'
|
||||
@configure_attributes = [
|
||||
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, 'null': false, info: true },
|
||||
{ name: 'shared', display: 'Shared organization', tag: 'boolean', note: 'Customers in the organization can view each other items.', type: 'boolean', 'default': true, 'null': false, info: false },
|
||||
{ name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, 'null': true, info: true },
|
||||
{ name: 'updated_at', display: 'Updated', type: 'time', readonly: 1, info: false },
|
||||
{ name: 'active', display: 'Active', tag: 'boolean', 'default': true, 'null': false, info: false },
|
||||
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false, info: true },
|
||||
{ name: 'shared', display: 'Shared organization', tag: 'boolean', note: 'Customers in the organization can view each other items.', type: 'boolean', default: true, null: false, info: false },
|
||||
{ name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, null: true, info: true },
|
||||
{ name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1, info: false },
|
||||
{ name: 'active', display: 'Active', tag: 'active', default: true, info: false },
|
||||
]
|
||||
@configure_overview = [
|
||||
'name',
|
||||
|
|
|
@ -131,11 +131,11 @@ class App.Overview extends App.Model
|
|||
owner: 'Owner'
|
||||
class: 'span4'
|
||||
},
|
||||
{ name: 'active', display: 'Active', tag: 'boolean', note: 'boolean', 'default': true, 'null': false, 'class': 'span4' },
|
||||
{ name: 'active', display: 'Active', tag: 'active', default: true },
|
||||
{ name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 },
|
||||
{ name: 'created_at', display: 'Created', type: 'time', readonly: 1 },
|
||||
{ name: 'created_at', display: 'Created', tag: 'datetime', readonly: 1 },
|
||||
{ name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 },
|
||||
{ name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 },
|
||||
{ name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
|
||||
]
|
||||
@configure_delete = true
|
||||
@configure_overview = [
|
||||
|
|
|
@ -8,13 +8,13 @@ class App.PostmasterFilter extends App.Model
|
|||
{ name: 'channel', display: 'Channel', type: 'input', readonly: 1 },
|
||||
{ name: 'match', display: 'Match all of the following', tag: 'postmaster_match' },
|
||||
{ name: 'perform', display: 'Perform action of the following', tag: 'postmaster_set' },
|
||||
{ name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, 'null': true },
|
||||
{ name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 },
|
||||
{ name: 'active', display: 'Active', tag: 'boolean', type: 'boolean', 'default': true, 'null': false },
|
||||
{ name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, null: true },
|
||||
{ name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
|
||||
{ name: 'active', display: 'Active', tag: 'active', default: true },
|
||||
{ name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 },
|
||||
{ name: 'created_at', display: 'Created', type: 'time', readonly: 1 },
|
||||
{ name: 'created_at', display: 'Created', tag: 'datetime', readonly: 1 },
|
||||
{ name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 },
|
||||
{ name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 },
|
||||
{ name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
|
||||
]
|
||||
@configure_delete = true
|
||||
@configure_overview = [
|
||||
|
|
|
@ -5,11 +5,11 @@ class App.Role extends App.Model
|
|||
@configure_attributes = [
|
||||
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false },
|
||||
{ name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, null: true },
|
||||
{ name: 'active', display: 'Active', tag: 'boolean', type: 'boolean', 'default': true, null: false },
|
||||
{ name: 'active', display: 'Active', tag: 'active', default: true },
|
||||
{ name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 },
|
||||
{ name: 'created_at', display: 'Created', type: 'time', readonly: 1 },
|
||||
{ name: 'created_at', display: 'Created', tag: 'datetime', readonly: 1 },
|
||||
{ name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 },
|
||||
{ name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 },
|
||||
{ name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
|
||||
]
|
||||
@configure_overview = [
|
||||
'name',
|
||||
|
|
|
@ -4,14 +4,14 @@ class App.Signature extends App.Model
|
|||
@url: @apiPath + '/signatures'
|
||||
|
||||
@configure_attributes = [
|
||||
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, 'null': false, 'class': 'span4' },
|
||||
{ name: 'body', display: 'Text', tag: 'textarea', limit: 250, 'null': true, 'class': 'span4', rows: 10 },
|
||||
{ name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, 'null': true, 'class': 'span4' },
|
||||
{ name: 'active', display: 'Active', tag: 'boolean', type: 'boolean', 'default': true, 'null': false, 'class': 'span4' },
|
||||
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, 'null': false },
|
||||
{ name: 'body', display: 'Text', tag: 'textarea', limit: 250, 'null': true, rows: 10 },
|
||||
{ name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, 'null': true },
|
||||
{ name: 'active', display: 'Active', tag: 'active', default: true },
|
||||
{ name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 },
|
||||
{ name: 'created_at', display: 'Created', type: 'time', readonly: 1 },
|
||||
{ name: 'created_at', display: 'Created', tag: 'datetime', readonly: 1 },
|
||||
{ name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 },
|
||||
{ name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 },
|
||||
{ name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
|
||||
]
|
||||
@configure_overview = [
|
||||
'name',
|
||||
|
|
|
@ -31,11 +31,11 @@ class App.Sla extends App.Model
|
|||
group: 'Group'
|
||||
owner: 'Owner'
|
||||
},
|
||||
{ name: 'active', display: 'Active', tag: 'boolean', note: 'boolean', 'default': true, 'null': false },
|
||||
{ name: 'active', display: 'Active', tag: 'active', default: true },
|
||||
{ name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 },
|
||||
{ name: 'created_at', display: 'Created', type: 'time', readonly: 1 },
|
||||
{ name: 'created_at', display: 'Created', tag: 'datetime', readonly: 1 },
|
||||
{ name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 },
|
||||
{ name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 },
|
||||
{ name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
|
||||
]
|
||||
@configure_delete = true
|
||||
@configure_overview = [
|
||||
|
|
|
@ -3,11 +3,11 @@ class App.TextModule extends App.Model
|
|||
@extend Spine.Model.Ajax
|
||||
@url: @apiPath + '/text_modules'
|
||||
@configure_attributes = [
|
||||
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, 'null': false, 'class': 'span4' },
|
||||
{ name: 'keywords', display: 'Keywords', tag: 'input', type: 'text', limit: 100, 'null': true, 'class': 'span4' },
|
||||
{ name: 'content', display: 'Content', tag: 'textarea', limit: 250, 'null': false, 'class': 'span4' },
|
||||
{ name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 },
|
||||
{ name: 'active', display: 'Active', tag: 'boolean', note: 'boolean', 'default': true, 'null': false, 'class': 'span4' },
|
||||
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false },
|
||||
{ name: 'keywords', display: 'Keywords', tag: 'input', type: 'text', limit: 100, null: true },
|
||||
{ name: 'content', display: 'Content', tag: 'textarea', limit: 250, null: false },
|
||||
{ name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
|
||||
{ name: 'active', display: 'Active', tag: 'active', default: true },
|
||||
]
|
||||
@configure_delete = true
|
||||
@configure_overview = [
|
||||
|
|
|
@ -11,18 +11,18 @@ class App.Ticket extends App.Model
|
|||
{ name: 'title', display: 'Title', tag: 'input', type: 'text', limit: 100, null: false, parentClass: 'noTruncate' },
|
||||
{ name: 'state_id', display: 'State', tag: 'select', multiple: false, null: false, relation: 'TicketState', default: 'new', style: 'width: 12%', edit: true, customer: true, },
|
||||
{ name: 'priority_id', display: 'Priority', tag: 'select', multiple: false, null: false, relation: 'TicketPriority', default: '2 normal', style: 'width: 12%', edit: true, customer: true, },
|
||||
{ name: 'last_contact', display: 'Last contact', type: 'time', null: true, style: 'width: 12%', parentClass: 'noTruncate' },
|
||||
{ name: 'last_contact_agent', display: 'Last contact (Agent)', type: 'time', null: true, style: 'width: 12%', parentClass: 'noTruncate' },
|
||||
{ name: 'last_contact_customer', display: 'Last contact (Customer)', type: 'time', null: true, style: 'width: 12%', parentClass: 'noTruncate' },
|
||||
{ name: 'first_response', display: 'First response', type: 'time', null: true, style: 'width: 12%', parentClass: 'noTruncate' },
|
||||
{ name: 'close_time', display: 'Close time', type: 'time', null: true, style: 'width: 12%', parentClass: 'noTruncate' },
|
||||
{ name: 'pending_time', display: 'Pending Time', type: 'time', null: true, style: 'width: 12%', parentClass: 'noTruncate' },
|
||||
{ name: 'escalation_time', display: 'Escalation', type: 'time', null: true, style: 'width: 12%', class: 'escalation', parentClass: 'noTruncate' },
|
||||
{ name: 'last_contact', display: 'Last contact', tag: 'datetime', null: true, style: 'width: 12%', parentClass: 'noTruncate' },
|
||||
{ name: 'last_contact_agent', display: 'Last contact (Agent)', tag: 'datetime', null: true, style: 'width: 12%', parentClass: 'noTruncate' },
|
||||
{ name: 'last_contact_customer', display: 'Last contact (Customer)', tag: 'datetime', null: true, style: 'width: 12%', parentClass: 'noTruncate' },
|
||||
{ name: 'first_response', display: 'First response', tag: 'datetime', null: true, style: 'width: 12%', parentClass: 'noTruncate' },
|
||||
{ name: 'close_time', display: 'Close time', tag: 'datetime', null: true, style: 'width: 12%', parentClass: 'noTruncate' },
|
||||
{ name: 'pending_time', display: 'Pending Time', tag: 'datetime', null: true, style: 'width: 12%', parentClass: 'noTruncate' },
|
||||
{ name: 'escalation_time', display: 'Escalation', tag: 'datetime', null: true, style: 'width: 12%', class: 'escalation', parentClass: 'noTruncate' },
|
||||
{ name: 'article_count', display: 'Article#', style: 'width: 12%' },
|
||||
{ name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 },
|
||||
{ name: 'created_at', display: 'Created', type: 'time', style: 'width: 120px', readonly: 1, parentClass: 'noTruncate' },
|
||||
{ name: 'created_at', display: 'Created', tag: 'datetime', style: 'width: 120px', readonly: 1, parentClass: 'noTruncate' },
|
||||
{ name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 },
|
||||
{ name: 'updated_at', display: 'Updated', type: 'time', style: 'width: 120px', readonly: 1, parentClass: 'noTruncate' },
|
||||
{ name: 'updated_at', display: 'Updated', tag: 'datetime', style: 'width: 120px', readonly: 1, parentClass: 'noTruncate' },
|
||||
]
|
||||
|
||||
uiUrl: ->
|
||||
|
|
|
@ -4,18 +4,18 @@ class App.TicketArticle extends App.Model
|
|||
@url: @apiPath + '/ticket_articles'
|
||||
@configure_attributes = [
|
||||
{ name: 'ticket_id', display: 'TicketID', null: false, readonly: 1, },
|
||||
{ name: 'from', display: 'From', tag: 'input', type: 'text', limit: 100, null: false, },
|
||||
{ name: 'to', display: 'To', tag: 'input', type: 'text', limit: 100, null: true, },
|
||||
{ name: 'cc', display: 'Cc', tag: 'input', type: 'text', limit: 100, null: true, },
|
||||
{ name: 'subject', display: 'Subject', tag: 'input', type: 'text', limit: 100, null: true, },
|
||||
{ name: 'body', display: 'Text', tag: 'textarea', rows: 5, limit: 100, null: false, },
|
||||
{ name: 'type_id', display: 'Type', tag: 'select', multiple: false, null: false, relation: 'TicketArticleType', default: '', class: 'medium' },
|
||||
{ name: 'sender_id', display: 'Sender', tag: 'select', multiple: false, null: false, relation: 'TicketArticleSender', default: '', class: 'medium' },
|
||||
{ name: 'internal', display: 'Visibility', tag: 'radio', default: false, null: true, options: { true: 'internal', false: 'public' }, class: 'medium' },
|
||||
{ name: 'from', display: 'From', tag: 'input', type: 'text', limit: 100, null: false },
|
||||
{ name: 'to', display: 'To', tag: 'input', type: 'text', limit: 100, null: true },
|
||||
{ name: 'cc', display: 'Cc', tag: 'input', type: 'text', limit: 100, null: true },
|
||||
{ name: 'subject', display: 'Subject', tag: 'input', type: 'text', limit: 100, null: true },
|
||||
{ name: 'body', display: 'Text', tag: 'textarea', rows: 5, limit: 100, null: false },
|
||||
{ name: 'type_id', display: 'Type', tag: 'select', multiple: false, null: false, relation: 'TicketArticleType', default: '' },
|
||||
{ name: 'sender_id', display: 'Sender', tag: 'select', multiple: false, null: false, relation: 'TicketArticleSender', default: '' },
|
||||
{ name: 'internal', display: 'Visibility', tag: 'radio', default: false, null: true, options: { true: 'internal', false: 'public' } },
|
||||
{ name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 },
|
||||
{ name: 'created_at', display: 'Created', type: 'time', readonly: 1 },
|
||||
{ name: 'created_at', display: 'Created', tag: 'datetime', readonly: 1 },
|
||||
{ name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 },
|
||||
{ name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 },
|
||||
{ name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
|
||||
]
|
||||
|
||||
uiUrl: ->
|
||||
|
|
|
@ -3,10 +3,10 @@ class App.TicketPriority extends App.Model
|
|||
@extend Spine.Model.Ajax
|
||||
@url: @apiPath + '/ticket_priorities'
|
||||
@configure_attributes = [
|
||||
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, 'null': false, translate: true },
|
||||
{ name: 'active', display: 'Active', tag: 'boolean', type: 'boolean', 'default': true, 'null': false },
|
||||
{ name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 },
|
||||
{ name: 'created_at', display: 'Created', type: 'time', readonly: 1 },
|
||||
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false, translate: true },
|
||||
{ name: 'active', display: 'Active', tag: 'active', default: true },
|
||||
{ name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
|
||||
{ name: 'created_at', display: 'Created', tag: 'datetime', readonly: 1 },
|
||||
]
|
||||
@configure_translate = true
|
||||
@configure_overview = [
|
||||
|
|
|
@ -3,10 +3,10 @@ class App.TicketState extends App.Model
|
|||
@extend Spine.Model.Ajax
|
||||
@url: @apiPath + '/ticket_states'
|
||||
@configure_attributes = [
|
||||
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, 'null': false, translate: true },
|
||||
{ name: 'active', display: 'Active', tag: 'boolean', type: 'boolean', 'default': true, 'null': false },
|
||||
{ name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 },
|
||||
{ name: 'created_at', display: 'Created', type: 'time', readonly: 1 },
|
||||
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false, translate: true },
|
||||
{ name: 'active', display: 'Active', tag: 'active', default: true },
|
||||
{ name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
|
||||
{ name: 'created_at', display: 'Created', tag: 'datetime', readonly: 1 },
|
||||
]
|
||||
@configure_translate = true
|
||||
@configure_overview = [
|
||||
|
|
|
@ -5,25 +5,25 @@ class App.User extends App.Model
|
|||
|
||||
# @hasMany 'roles', 'App.Role'
|
||||
@configure_attributes = [
|
||||
{ name: 'login', display: 'Login', tag: 'input', type: 'text', limit: 100, null: false, class: 'span4', autocapitalize: false, signup: false, quick: false },
|
||||
{ name: 'firstname', display: 'Firstname', tag: 'input', type: 'text', limit: 100, null: false, class: 'span4', signup: true, info: true, invite_agent: true },
|
||||
{ name: 'lastname', display: 'Lastname', tag: 'input', type: 'text', limit: 100, null: false, class: 'span4', signup: true, info: true, invite_agent: true },
|
||||
{ name: 'email', display: 'Email', tag: 'input', type: 'email', limit: 100, null: false, class: 'span4', signup: true, info: true, invite_agent: true },
|
||||
{ name: 'web', display: 'Web', tag: 'input', type: 'url', limit: 100, null: true, class: 'span4', signup: false, info: true },
|
||||
{ name: 'phone', display: 'Phone', tag: 'input', type: 'phone', limit: 100, null: true, class: 'span4', signup: false, info: true },
|
||||
{ name: 'mobile', display: 'Mobile', tag: 'input', type: 'phone', limit: 100, null: true, class: 'span4', signup: false, info: true },
|
||||
{ name: 'fax', display: 'Fax', tag: 'input', type: 'phone', limit: 100, null: true, class: 'span4', signup: false, info: true },
|
||||
{ name: 'organization_id', display: 'Organization', tag: 'select', multiple: false, nulloption: true, null: true, relation: 'Organization', class: 'span4', signup: false, info: true },
|
||||
{ name: 'department', display: 'Department', tag: 'input', type: 'text', limit: 200, null: true, class: 'span4', signup: false, info: true },
|
||||
{ name: 'street', display: 'Street', tag: 'input', type: 'text', limit: 100, null: true, class: 'span4', signup: false, info: true },
|
||||
{ name: 'zip', display: 'Zip', tag: 'input', type: 'text', limit: 100, null: true, class: 'span4', signup: false, info: true },
|
||||
{ name: 'city', display: 'City', tag: 'input', type: 'text', limit: 100, null: true, class: 'span4', signup: false, info: true },
|
||||
{ name: 'password', display: 'Password', tag: 'input', type: 'password', limit: 50, null: true, autocomplete: 'off', class: 'span4', signup: true, },
|
||||
{ name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, null: true, class: 'span4', info: true },
|
||||
{ name: 'role_ids', display: 'Roles', tag: 'checkbox', multiple: true, null: false, relation: 'Role', class: 'span4' },
|
||||
{ name: 'group_ids', display: 'Groups', tag: 'checkbox', multiple: true, null: true, relation: 'Group', class: 'span4', invite_agent: true },
|
||||
{ name: 'active', display: 'Active', tag: 'boolean', default: true, null: true, class: 'span4' },
|
||||
{ name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 },
|
||||
{ name: 'login', display: 'Login', tag: 'input', type: 'text', limit: 100, null: false, autocapitalize: false, signup: false, quick: false },
|
||||
{ name: 'firstname', display: 'Firstname', tag: 'input', type: 'text', limit: 100, null: false, signup: true, info: true, invite_agent: true },
|
||||
{ name: 'lastname', display: 'Lastname', tag: 'input', type: 'text', limit: 100, null: false, signup: true, info: true, invite_agent: true },
|
||||
{ name: 'email', display: 'Email', tag: 'input', type: 'email', limit: 100, null: false, signup: true, info: true, invite_agent: true },
|
||||
{ name: 'web', display: 'Web', tag: 'input', type: 'url', limit: 100, null: true, signup: false, info: true },
|
||||
{ name: 'phone', display: 'Phone', tag: 'input', type: 'phone', limit: 100, null: true, signup: false, info: true },
|
||||
{ name: 'mobile', display: 'Mobile', tag: 'input', type: 'phone', limit: 100, null: true, signup: false, info: true },
|
||||
{ name: 'fax', display: 'Fax', tag: 'input', type: 'phone', limit: 100, null: true, signup: false, info: true },
|
||||
{ name: 'organization_id', display: 'Organization', tag: 'select', multiple: false, nulloption: true, null: true, relation: 'Organization', signup: false, info: true },
|
||||
{ name: 'department', display: 'Department', tag: 'input', type: 'text', limit: 200, null: true, signup: false, info: true },
|
||||
{ name: 'street', display: 'Street', tag: 'input', type: 'text', limit: 100, null: true, signup: false, info: true },
|
||||
{ name: 'zip', display: 'Zip', tag: 'input', type: 'text', limit: 100, null: true, signup: false, info: true },
|
||||
{ name: 'city', display: 'City', tag: 'input', type: 'text', limit: 100, null: true, signup: false, info: 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 },
|
||||
{ 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 },
|
||||
{ name: 'active', display: 'Active', tag: 'active', default: true },
|
||||
{ name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
|
||||
]
|
||||
@configure_overview = [
|
||||
# 'login', 'firstname', 'lastname', 'email', 'updated_at',
|
||||
|
@ -52,21 +52,21 @@ class App.User extends App.Model
|
|||
else
|
||||
return '??'
|
||||
|
||||
avatar: (size = 40, placement = '', cssClass = '') ->
|
||||
cssClass += " size-#{ size }"
|
||||
avatar: (size = 40, placement = '', cssClass = '', unique = false, avatar) ->
|
||||
cssClass += " size-#{size}"
|
||||
|
||||
if placement
|
||||
placement = "data-placement=\"#{placement}\""
|
||||
|
||||
if !@image || @image is 'none'
|
||||
return @uniqueAvatar(size, placement, cssClass)
|
||||
if !@image || @image is 'none' || unique
|
||||
return @uniqueAvatar(size, placement, cssClass, avatar)
|
||||
else
|
||||
"<span class=\"avatar user-popover #{cssClass}\" data-id=\"#{@id}\" style=\"background-image: url(#{ @imageUrl })\" #{placement}></span>"
|
||||
|
||||
uniqueAvatar: (size = 40, placement = '', cssClass = '', avatar) ->
|
||||
if size
|
||||
cssClass += " size-#{ size }"
|
||||
if @vip
|
||||
cssClass += " vip"
|
||||
image = @imageUrl()
|
||||
"<span class=\"avatar user-popover #{cssClass}\" data-id=\"#{@id}\" style=\"background-image: url(#{image})\" #{placement}></span>"
|
||||
|
||||
uniqueAvatar: (size, placement = '', cssClass = '', avatar) ->
|
||||
width = 300
|
||||
height = 226
|
||||
size = parseInt(size, 10)
|
||||
|
@ -76,12 +76,21 @@ class App.User extends App.Model
|
|||
y = rng() * (height - size)
|
||||
|
||||
if !avatar
|
||||
cssClass += "#{cssClass} user-popover"
|
||||
cssClass += " user-popover"
|
||||
data = "data-id=\"#{@id}\""
|
||||
else
|
||||
data = "data-avatar-id=\"#{avatar.id}\""
|
||||
|
||||
if @vip
|
||||
cssClass += " vip"
|
||||
"<span class=\"avatar unique #{cssClass}\" #{data} style=\"background-position: -#{ x }px -#{ y }px;\" #{placement}>#{ @initials() }</span>"
|
||||
|
||||
imageUrl: ->
|
||||
return if !@image
|
||||
# set image url
|
||||
@constructor.apiPath + '/users/image/' + @image
|
||||
|
||||
|
||||
@_fillUp: (data) ->
|
||||
|
||||
# set socal media links
|
||||
|
@ -92,9 +101,6 @@ class App.User extends App.Model
|
|||
if account == 'facebook'
|
||||
data['accounts'][account]['link'] = 'https://www.facebook.com/profile.php?id=' + data['accounts'][account]['uid']
|
||||
|
||||
# set image url
|
||||
data.imageUrl = @apiPath + '/users/image/' + data.image
|
||||
|
||||
if data.organization_id
|
||||
data.organization = App.Organization.find(data.organization_id)
|
||||
|
||||
|
|
|
@ -11,30 +11,30 @@
|
|||
<input type="checkbox" value="<%= ticket.id %>" name="bulk" class="pull-left"/>
|
||||
</td>
|
||||
<td class="span1">
|
||||
<img class="thumbnail user-popover" data-id="<%= ticket.customer_id %>" src="<%= ticket.customer.imageUrl %>" alt="">
|
||||
<img class="thumbnail user-popover" data-id="<%= ticket.customer_id %>" src="<%- ticket.customer.imageUrl() %>" alt="">
|
||||
</td>
|
||||
<td class="span10">
|
||||
<h3><a href="#" data-type="edit"><%= ticket.title %></a> <small><%= ticket.number %> <span class="humanTimeFromNow" data-time="<%= ticket.created_at %>">?</span></small></h3>
|
||||
<div class="row">
|
||||
<div class="span2">
|
||||
<b><%- @T( 'State' ) %></b> <%- @T( ticket.state.name ) %>
|
||||
<b><%- @T( 'State' ) %></b> <%- @P( ticket, 'state' ) %>
|
||||
</div>
|
||||
<div class="span2">
|
||||
<b><%- @T( 'Group' ) %></b> <%= ticket.group.name %>
|
||||
<b><%- @T( 'Group' ) %></b> <%- @P( ticket, 'group' ) %>
|
||||
</div>
|
||||
<div class="span2">
|
||||
<b><%- @T( 'Customer' ) %></b> <%= ticket.customer.displayName() %>
|
||||
<b><%- @T( 'Customer' ) %></b> <%- @P( ticket, 'customer' ) %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="span2">
|
||||
<b><%- @T( 'Priority' ) %></b> <%- @T( ticket.priority.name ) %>
|
||||
<b><%- @T( 'Priority' ) %></b> <%- @P( ticket, 'priority' ) %>
|
||||
</div>
|
||||
<div class="span2">
|
||||
<b><%- @T( 'Owner' ) %></b> <%= ticket.owner.displayName() %>
|
||||
<b><%- @T( 'Owner' ) %></b> <%- @P( ticket, 'owner' ) %>
|
||||
</div>
|
||||
<div class="span2">
|
||||
<b><%- @T( 'Organization' ) %></b> <%= @P( ticket.customer.organization ) %>
|
||||
<b><%- @T( 'Organization' ) %></b> <%- @P( ticket, 'organization' ) %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -29,16 +29,9 @@
|
|||
<% groupLast = '' %>
|
||||
<% for object in @objects: %>
|
||||
<% if @groupBy: %>
|
||||
<% if object[@groupBy] && object[@groupBy].displayName: %>
|
||||
<% groupByName = object[@groupBy].displayName() %>
|
||||
<% if object[@groupBy].translate(): %>
|
||||
<% groupByName = @T(groupByName) %>
|
||||
<% end %>
|
||||
<% else: %>
|
||||
<% groupByName = object[@groupBy] || '-' %>
|
||||
<% end %>
|
||||
<% groupByName = @P( object, @groupBy ) %>
|
||||
<% if groupLast isnt groupByName: %>
|
||||
<tr class=""><td colspan="<%= length %>"><b><%- @P( groupByName ) %></b></td></tr>
|
||||
<tr class=""><td colspan="<%= length %>"><b><%= groupByName %></b></td></tr>
|
||||
<% groupLast = groupByName %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
@ -56,47 +49,26 @@
|
|||
<td><input type="radio" value="<%= object.id %>" name="radio"/></td>
|
||||
<% end %>
|
||||
<% for item in @header: %>
|
||||
<% translation = false %>
|
||||
<% value = object[item.name] %>
|
||||
<% item_id = item.name.substr(item.name.length-3, item.name.length) %>
|
||||
<% if item_id is '_id' && object[ item.name.substr(0, item.name.length-3) ]: %>
|
||||
<% value = object[ item.name.substr(0, item.name.length-3) ] %>
|
||||
<% value = @P( object, item.name ) %>
|
||||
<% if @callbacks: %>
|
||||
<% if item.name.substr(item.name.length-3, item.name.length) is '_id' && object[ item.name.substr(0, item.name.length-3) ]: %>
|
||||
<% refObject = object[ item.name.substr(0, item.name.length-3) ] %>
|
||||
<% end %>
|
||||
<% if !value: %>
|
||||
<% parts = item.name.split '::' %>
|
||||
<% if parts[0] && parts[1] && object[ parts[0] ]: %>
|
||||
<% value = object[ parts[0] ][ parts[1] ] %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if value && value.displayNameLong : %>
|
||||
<% translation = true %>
|
||||
<% value = value.displayNameLong() %>
|
||||
<% else if value && value.displayName : %>
|
||||
<% translation = true %>
|
||||
<% value = value.displayName() %>
|
||||
<% end %>
|
||||
<% item_clone = item %>
|
||||
<% if @callbacks: %>
|
||||
<% for attribute, callbacksAll of @callbacks: %>
|
||||
<% if attribute is item.name || attribute is item_id: %>
|
||||
<% if attribute is item.name: %>
|
||||
<% for callback in callbacksAll: %>
|
||||
<% value = callback( value, object, item_clone, @header, refObject ) %>
|
||||
<% value = callback( value, object, item, @header, refObject ) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% #console.log('HH', item_clone.name, item_clone.type, item_clone.translate, item_clone, object.translate(), refObject, translation) %>
|
||||
<td <% if item_clone.parentClass: %>class="<%= item_clone.parentClass %>"<% end %>>
|
||||
<% if item_clone.link: %><a href="<%- item_clone.link %>" <% if item_clone.target: %>target="<%= item_clone.target %>"<% end %>><% end %>
|
||||
<% if item_clone.translate || ( translation && !refObject && object.translate && object.translate() ) || ( translation && refObject && refObject.translate && refObject.translate() ) : %>
|
||||
<span <% if item_clone.class: %>class="<%= item_clone.class %>"<% end %>><%- @T( @P( value, item_clone ) ) %></span>
|
||||
<% else if item_clone.type is 'time': %>
|
||||
<span class="humanTimeFromNow <% if item_clone.class: %><%= item_clone.class %><% end %>" data-time="<%- object[item_clone.name] %>">?</span>
|
||||
<% else: %>
|
||||
<span <% if item_clone.class: %>class="<%= item_clone.class %>"<% end %> <% if item_clone.title: %>title="<%= item_clone.title %>"<% end %> <% if item_clone.data: %><% for data_key, data_item of item_clone.data: %>data-<%- data_key %>="<%= data_item %>" <% end %><% end %>><%= @P(value) %></span>
|
||||
<% end %>
|
||||
<% if item_clone.link: %></a><% end %>
|
||||
|
||||
<td <% if item.parentClass: %>class="<%= item.parentClass %>"<% end %>>
|
||||
|
||||
<% if item.link: %><a href="<%- item.link %>" <% if item.target: %>target="<%= item.target %>"<% end %>><% end %>
|
||||
<span <% if item.class: %>class="<%= item.class %>"<% end %> <% if item.title: %>title="<%= item.title %>"<% end %> <% if item.data: %><% for data_key, data_item of item.data: %>data-<%- data_key %>="<%= data_item %>" <% end %><% end %>><%- value %></span>
|
||||
<% if item.link: %></a><% end %>
|
||||
|
||||
</td>
|
||||
<% end %>
|
||||
<% if @destroy: %>
|
||||
|
|
|
@ -243,6 +243,10 @@ Oliver<br>
|
|||
<div class="attachment-name u-highlight">something with very long line which is slonger as the container arount it if you can see it I wish you a very happy day.txt</div>
|
||||
<div class="attachment-size">17.1 kb</div>
|
||||
</div>
|
||||
<div class="attachment horizontal">
|
||||
<div class="attachment-name u-highlight">something-withverylonglinewhichisslongerasthecontainerarountitifyoucanseeitIwishyouaveryhappyday.txt</div>
|
||||
<div class="attachment-size">17.1 kb</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
<div class="flex profile">
|
||||
|
||||
<div class="profile-window">
|
||||
<div class="profile-section vertical centered">
|
||||
<div class="align-right profile-action js-action"></div>
|
||||
<div class="profile-organizationIcon">
|
||||
<div class="organization icon"></div>
|
||||
</div>
|
||||
<h1><%= @organization.displayName() %></h1>
|
||||
</div>
|
||||
<div class="profile-section">
|
||||
<div class="profile-details horizontal wrap">
|
||||
<% for row in @organizationData: %>
|
||||
<% if @organization[row.name]: %>
|
||||
<% if row.tag is 'richtext': %>
|
||||
<div class="profile-detailsEntry" style="width: 100%;">
|
||||
<label><%- @Ti( row.display ) %></label>
|
||||
<div contenteditable="true" data-name="<%= row.name %>" data-type="update" data-placeholder="<%- @T('Add a Note') %>"><%- @organization[row.name] %></div>
|
||||
</div>
|
||||
<% else: %>
|
||||
<div class="profile-detailsEntry">
|
||||
<label><%- @Ti( row.display ) %></label>
|
||||
<%- @L( @P( @organization[row.name] ) ) %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% if @organization.members: %>
|
||||
<div class="profile-section profile-memberSection">
|
||||
<label><%- @T('Members') %></label>
|
||||
<div class="profile-details horizontal wrap">
|
||||
|
||||
<% for user in @organization.members: %>
|
||||
<div class="profile-organizationMember">
|
||||
<%- user.avatar("40") %>
|
||||
<a href="<%- user.uiUrl() %>" class="user-popover" data-id="<%- user.id %>"><%= user.displayName() %></a>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="profile-section js-ticket-stats"></div>
|
||||
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,6 @@
|
|||
<div class="flex profile">
|
||||
<div class="profile-window">
|
||||
<div class="js-object-container"></div>
|
||||
<div class="profile-section js-ticket-stats"></div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,39 @@
|
|||
<div class="profile-section vertical centered">
|
||||
<div class="align-right profile-action js-action"></div>
|
||||
<div class="profile-organizationIcon">
|
||||
<div class="organization icon"></div>
|
||||
</div>
|
||||
<h1><%= @organization.displayName() %></h1>
|
||||
</div>
|
||||
<div class="profile-section">
|
||||
<div class="profile-details horizontal wrap">
|
||||
<% for row in @organizationData: %>
|
||||
<% if row.tag is 'richtext': %>
|
||||
<div class="profile-detailsEntry" style="width: 100%;">
|
||||
<label><%- @Ti( row.display ) %></label>
|
||||
<div contenteditable="true" data-name="<%= row.name %>" data-type="update" data-placeholder="<%- @T('Add a Note') %>"><%- @organization[row.name] %></div>
|
||||
</div>
|
||||
<% else: %>
|
||||
<% if @organization[row.name]: %>
|
||||
<div class="profile-detailsEntry">
|
||||
<label><%- @Ti( row.display ) %></label>
|
||||
<%- @P( @organization, row.name ) %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% if @organization.members: %>
|
||||
<div class="profile-section profile-memberSection">
|
||||
<label><%- @T('Members') %></label>
|
||||
<div class="profile-details horizontal wrap">
|
||||
<% for user in @organization.members: %>
|
||||
<div class="profile-organizationMember">
|
||||
<%- user.avatar("40") %>
|
||||
<a href="<%- user.uiUrl() %>" class="user-popover" data-id="<%- user.id %>"><%= user.displayName() %></a>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
|
@ -1,24 +1,19 @@
|
|||
<hr>
|
||||
<div>
|
||||
<% for row in @organizationData: %>
|
||||
<% if @organization[row.name]: %>
|
||||
<div class="column">
|
||||
<h3><%- @T( row.display ) %></h3>
|
||||
<% if row.tag is 'richtext': %>
|
||||
<div><%- @organization[row.name] %></div>
|
||||
<% else: %>
|
||||
<div><%- @L( @P( @organization[row.name] ) ) %></div>
|
||||
<% end %>
|
||||
<div class="popover-block">
|
||||
<label><%- @T( row.display ) %></label>
|
||||
<%- @P( @organization, row.name ) %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% if @organization.members: %>
|
||||
<hr>
|
||||
<h3><%- @T('Members') %></h3>
|
||||
<div class="popover-block">
|
||||
<label><%- @T('Members') %></label>
|
||||
<% for user in @organization.members: %>
|
||||
<div class="person">
|
||||
<%= user.displayName() %>
|
||||
</div>
|
||||
<div class="person"><%= user.displayName() %></div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
|
@ -2,36 +2,40 @@
|
|||
<span class="<%- @ticket.icon() %>" title="<%- @ticket.iconTitle() %>"></span> <span class="<%- @ticket.iconTextClass() %>"><%- @ticket.iconTitle() %></span>
|
||||
</div>
|
||||
<hr>
|
||||
<h3><%- @P('Agent') %></h3>
|
||||
<div class="person">
|
||||
<div class="popover-block">
|
||||
<label><%- @T('Agent') %></label>
|
||||
<div class="person">
|
||||
<%= @ticket.owner.displayName() %>
|
||||
<% if @ticket.owner.organization_id: %>
|
||||
<% if @ticket.owner.organization: %>
|
||||
<span class="organization"><%= @ticket.owner.organization.displayName() %></span>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<h3><%- @P('Customer') %></h3>
|
||||
<div class="person">
|
||||
<div class="popover-block">
|
||||
<label><%- @T('Customer') %></label>
|
||||
<div class="person">
|
||||
<%= @ticket.customer.displayName() %>
|
||||
<% if @ticket.customer.organization_id: %>
|
||||
<% if @ticket.customer.organization: %>
|
||||
<span class="organization"><%= @ticket.customer.organization.displayName() %></span>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="horizontal two-columns">
|
||||
<div class="column">
|
||||
<h3>#</h3>
|
||||
<div class="u-textTruncate"><%- @P( @ticket.number ) %></div>
|
||||
<label>#</label>
|
||||
<div class="u-textTruncate"><%- @P( @ticket, 'number' ) %></div>
|
||||
</div>
|
||||
<div class="column">
|
||||
<h3><%- @T( 'Priority' ) %></h3>
|
||||
<div class="u-textTruncate"><%- @T( @ticket.priority.name ) %></div>
|
||||
<label><%- @T( 'Priority' ) %></label>
|
||||
<div class="u-textTruncate"><%- @P( @ticket, 'priority' ) %></div>
|
||||
</div>
|
||||
<div class="column">
|
||||
<h3><%- @T( 'Created' ) %></h3>
|
||||
<div class="u-textTruncate"><%- @P( @ticket.humanTime ) %></div>
|
||||
<label><%- @T( 'Created' ) %></label>
|
||||
<div class="u-textTruncate"><%- @P( @ticket, 'created_at' ) %></div>
|
||||
</div>
|
||||
<div class="column">
|
||||
<h3><%- @T( 'Group' ) %></h3>
|
||||
<div class="u-textTruncate"><%- @P( @ticket.group ) %></div>
|
||||
<label><%- @T( 'Group' ) %></label>
|
||||
<div class="u-textTruncate"><%- @P( @ticket, 'group' ) %></div>
|
||||
</div>
|
||||
</div>
|
|
@ -5,19 +5,15 @@
|
|||
<div>
|
||||
<% for row in @userData: %>
|
||||
<% if @user[row.name]: %>
|
||||
<div class="column">
|
||||
<h3><%- @T( row.display ) %></h3>
|
||||
<% if row.tag is 'richtext': %>
|
||||
<div><%- @user[row.name] %></div>
|
||||
<% else: %>
|
||||
<div><%- @L( @P( @user[row.name] ) ) %></div>
|
||||
<% end %>
|
||||
<div class="popover-block">
|
||||
<label><%- @T( row.display ) %></label>
|
||||
<%- @P( @user, row.name ) %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if !_.isEmpty(@user['accounts']): %>
|
||||
<div class="customer-info">
|
||||
<h3><%- @T( 'Linked Accounts' ) %></h3>
|
||||
<div class="popover-block">
|
||||
<label><%- @T( 'Linked Accounts' ) %></label>
|
||||
<% for account of @user['accounts']: %>
|
||||
<a href="<%= @user['accounts'][account]['link'] %>" target="_blank"><%= account %></a>
|
||||
<% end %>
|
||||
|
@ -25,8 +21,8 @@
|
|||
<% end %>
|
||||
<% if !_.isEmpty( @user['links'] ): %>
|
||||
<% for link in @user['links']: %>
|
||||
<div class="customer-info">
|
||||
<h3><%- @T( link['title'] ) %></h3>
|
||||
<div class="popover-block">
|
||||
<label><%- @T( link['title'] ) %></label>
|
||||
<% for item in link['items']: %>
|
||||
<% if item['url']: %>
|
||||
<a href="<%= item['url'] %>" title="<%- @Ti( item['title'] ) %>" style="<%= item['style'] %>" data-type="<%= item['data'] %>" class="<%= item['class'] %>" <% if link.new_window: %>target="_blank"<% end %>>
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
<ul>
|
||||
<% for ticket in @tickets: %>
|
||||
<div class="customer-info"><a href="#ticket/zoom/<%= ticket.id %>" title="<%= ticket.title %>">T:<%= ticket.number %></a> <span title="<%= ticket.created_at_short %>"><%= ticket.humanTime %></span><br/><%= ticket.title %></div>
|
||||
<li>
|
||||
<a href="#ticket/zoom/<%= ticket.id %>" title="<%= ticket.title %>">T:<%= ticket.number %></a> <%- @P( ticket, 'title') %><br/>
|
||||
<%- @T('Created') %> <%- @P( ticket, 'created_at') %>
|
||||
</li>
|
||||
<% end %>
|
||||
<ul>
|
|
@ -17,7 +17,7 @@
|
|||
<% if avatar.default: %>
|
||||
<% cssClass = 'is-active' %>
|
||||
<% end %>
|
||||
<%- App.Session.get().uniqueAvatar('50', '', cssClass, avatar) %>
|
||||
<%- App.Session.get().avatar('50', '', cssClass, true, avatar) %>
|
||||
<% else: %>
|
||||
<span class="avatar size-50 <% if avatar.default: %>is-active<% end %>" data-avatar-id="<%- avatar.id %>" style="background-image: url(<%- avatar.content %>)"></span>
|
||||
<% if avatar.deletable: %>
|
||||
|
|
|
@ -1 +1 @@
|
|||
<h1 contenteditable="true" class="ticket-title-update" data-placeholder="<%= @T('Enter Title...') %>"><%= @P( @ticket.title ) %></h1>
|
||||
<div contenteditable="true" class="ticket-title-update" data-placeholder="<%= @T('Enter Title...') %>"><%= @ticket.title %></div>
|
|
@ -1,37 +0,0 @@
|
|||
<div class="flex profile">
|
||||
|
||||
<div class="profile-window">
|
||||
<div class="profile-section vertical centered">
|
||||
<div class="align-right profile-action js-action"></div>
|
||||
<%- @user.avatar("80") %>
|
||||
<h1><%= @user.displayName() %></h1>
|
||||
<% if @user.organization: %>
|
||||
<div class="profile-organization"><a href="<%- @user.organization.uiUrl() %>"><%= @user.organization.displayName() %></a></div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="profile-section">
|
||||
<div class="profile-details horizontal wrap">
|
||||
<% for row in @userData: %>
|
||||
<% if @user[row.name]: %>
|
||||
<% if row.tag is 'richtext': %>
|
||||
<div class="profile-detailsEntry" style="width: 100%;">
|
||||
<label><%- @Ti( row.display ) %></label>
|
||||
<div contenteditable="true" data-name="<%= row.name %>" data-type="update" data-placeholder="<%- @T('Add a Note') %>"><%- @user[row.name] %></div>
|
||||
</div>
|
||||
<% else: %>
|
||||
<div class="profile-detailsEntry">
|
||||
<label><%- @Ti( row.display ) %></label>
|
||||
<%- @L( @P( @user[row.name] ) ) %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="profile-section js-ticket-stats"></div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
|
@ -0,0 +1,6 @@
|
|||
<div class="flex profile">
|
||||
<div class="profile-window">
|
||||
<div class="js-object-container"></div>
|
||||
<div class="profile-section js-ticket-stats"></div>
|
||||
</div>
|
||||
</div>
|
27
app/assets/javascripts/app/views/user_profile/object.jst.eco
Normal file
27
app/assets/javascripts/app/views/user_profile/object.jst.eco
Normal file
|
@ -0,0 +1,27 @@
|
|||
<div class="profile-section vertical centered">
|
||||
<div class="align-right profile-action js-action"></div>
|
||||
<%- @user.avatar("80") %>
|
||||
<h1><%= @user.displayName() %></h1>
|
||||
<% if @user.organization: %>
|
||||
<div class="profile-organization"><a href="<%- @user.organization.uiUrl() %>"><%= @user.organization.displayName() %></a></div>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="profile-section">
|
||||
<div class="profile-details horizontal wrap">
|
||||
<% for row in @userData: %>
|
||||
<% if row.tag is 'richtext': %>
|
||||
<div class="profile-detailsEntry" style="width: 100%;">
|
||||
<label><%- @Ti( row.display ) %></label>
|
||||
<div contenteditable="true" data-name="<%= row.name %>" data-type="update" data-placeholder="<%- @T('Add a Note') %>"><%- @user[row.name] %></div>
|
||||
</div>
|
||||
<% else: %>
|
||||
<% if @user[row.name]: %>
|
||||
<div class="profile-detailsEntry">
|
||||
<label><%- @Ti( row.display ) %></label>
|
||||
<%- @P( @user, row.name ) %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
|
@ -1,7 +1,8 @@
|
|||
<div class="sidebar-block">
|
||||
<h3 class="u-textTruncate" title="<%- @Ti( 'Name') %>">
|
||||
<%= @organization.displayName() %>
|
||||
</h3>
|
||||
<div class="avatar organizationInfo-avatar size-50">
|
||||
<a href="<%- @organization.uiUrl() %>" class="organization icon"></a>
|
||||
</div>
|
||||
<h3 title="<%- @Ti( 'Name') %>"><%= @organization.displayName() %></h3>
|
||||
</div>
|
||||
|
||||
<% for row in @organizationData: %>
|
||||
|
@ -9,7 +10,7 @@
|
|||
<div class="sidebar-block">
|
||||
<% if row.tag isnt 'richtext': %>
|
||||
<label><%- @T( row.display ) %></label>
|
||||
<%- @L( @P( @organization[row.name] ) ) %>
|
||||
<%- @P( @organization, row.name ) %>
|
||||
<% else: %>
|
||||
<label><%- @T( row.display ) %></label>
|
||||
<div contenteditable="true" data-name="<%= row.name %>" data-type="update-org" data-placeholder="<%- @T('Add a Note') %>"><%- @organization[row.name] %></div>
|
||||
|
|
|
@ -1,16 +1,13 @@
|
|||
<div class="userInfo">
|
||||
<div class="sidebar-block">
|
||||
<div class="sidebar-block">
|
||||
<%- @user.avatar("50", "", "userInfo-avatar") %>
|
||||
<h3 class="u-textTruncate" title="<%- @Ti( 'Name') %>">
|
||||
<%= @user.displayName() %>
|
||||
</h3>
|
||||
</div>
|
||||
<h3 title="<%- @Ti( 'Name') %>"><%= @user.displayName() %></h3>
|
||||
</div>
|
||||
<% for row in @userData: %>
|
||||
<% if @user[row.name] || row.name is 'note': %>
|
||||
<div class="sidebar-block">
|
||||
<% if row.tag isnt 'richtext': %>
|
||||
<label><%- @T( row.display ) %></label>
|
||||
<%- @L( @P( @user[row.name] ) ) %>
|
||||
<%- @P( @user, row.name ) %>
|
||||
<% else: %>
|
||||
<label><%- @T( row.display ) %></label>
|
||||
<div contenteditable="true" data-name="<%= row.name %>" data-type="update" data-placeholder="<%- @T('Add a Note') %>"><%- @user[row.name] %></div>
|
||||
|
@ -46,4 +43,3 @@
|
|||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
|
@ -74,41 +74,49 @@ function difference(object1, object2) {
|
|||
}
|
||||
|
||||
// clone, just data, no instances of objects
|
||||
function clone(item) {
|
||||
if (!item) { return item; }
|
||||
function clone(item, full) {
|
||||
if (!item) { return item }
|
||||
|
||||
// ignore certain objects
|
||||
var acceptedInstances = [ 'Object', 'Number', 'String', 'Boolean', 'Array' ];
|
||||
var acceptedInstances = [ 'Object', 'Number', 'String', 'Boolean', 'Array' ]
|
||||
if (full) {
|
||||
acceptedInstances.push( 'Function' )
|
||||
}
|
||||
if (item && item.constructor) {
|
||||
if (!_.contains(acceptedInstances, item.constructor.name)) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var result;
|
||||
// copy array
|
||||
var result;
|
||||
if ( _.isArray(item) ) {
|
||||
result = [];
|
||||
result = []
|
||||
item.forEach(function(child, index, array) {
|
||||
result[index] = clone( child );
|
||||
result[index] = clone( child, full )
|
||||
});
|
||||
}
|
||||
|
||||
// copy function
|
||||
else if ( _.isFunction(item) ) {
|
||||
result = item.bind({})
|
||||
}
|
||||
|
||||
// copy object
|
||||
else if ( _.isObject(item) ) {
|
||||
result = {};
|
||||
result = {}
|
||||
for(var key in item) {
|
||||
if (item.hasOwnProperty(key)) {
|
||||
result[key] = clone(item[key])
|
||||
result[key] = clone( item[key], full )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// copy others
|
||||
else {
|
||||
result = item;
|
||||
result = item
|
||||
}
|
||||
return result;
|
||||
return result
|
||||
}
|
||||
|
||||
// taken from http://stackoverflow.com/questions/4459928/how-to-deep-clone-in-javascript
|
||||
|
|
|
@ -714,6 +714,10 @@ textarea,
|
|||
background: none;
|
||||
}
|
||||
|
||||
textarea.form-control {
|
||||
height: 118px;
|
||||
}
|
||||
|
||||
select.form-control {
|
||||
padding-left: 10px;
|
||||
padding-right: 34px;
|
||||
|
@ -2492,6 +2496,11 @@ footer {
|
|||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
:not(.navigation) .avatar.vip {
|
||||
border: 2px dotted;
|
||||
border-color: #fff;
|
||||
}
|
||||
|
||||
.avatar.size-50 {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
|
@ -2515,16 +2524,30 @@ footer {
|
|||
cursor: default;
|
||||
}
|
||||
|
||||
:not(.navigation) .unique.avatar.vip {
|
||||
border: 2px dotted;
|
||||
line-height: 40px;
|
||||
}
|
||||
|
||||
.unique.avatar.size-50 {
|
||||
font-size: 16px;
|
||||
line-height: 52px;
|
||||
}
|
||||
|
||||
:not(.navigation) .unique.avatar.size-50.vip {
|
||||
line-height: 50px;
|
||||
}
|
||||
|
||||
.unique.avatar.size-80 {
|
||||
font-size: 20px;
|
||||
line-height: 84px;
|
||||
}
|
||||
|
||||
:not(.navigation) .unique.avatar.size-80.vip {
|
||||
font-size: 20px;
|
||||
line-height: 82px;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 32%;
|
||||
max-width: 300px;
|
||||
|
@ -2549,12 +2572,18 @@ footer {
|
|||
|
||||
.sidebar-block {
|
||||
margin: 20px 0;
|
||||
word-wrap: break-word;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
.sidebar-block,
|
||||
.sidebar-block > * {
|
||||
@extend .u-textTruncate;
|
||||
}
|
||||
.sidebar-block [contenteditable=true] {
|
||||
white-space: normal; // do no u-textTruncate, we want to edit it inline
|
||||
}
|
||||
|
||||
.main + .sidebar {
|
||||
border-right: none;
|
||||
|
@ -2718,10 +2747,16 @@ footer {
|
|||
}
|
||||
|
||||
.popover .user-organization {
|
||||
@extend .u-textTruncate;
|
||||
margin-bottom: 8px;
|
||||
margin-top: -4px;
|
||||
}
|
||||
|
||||
.popover-block {
|
||||
@extend .sidebar-block;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.popover hr {
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
@ -2729,7 +2764,15 @@ footer {
|
|||
.popover .person .organization:before { content: '('; }
|
||||
.popover .person .organization:after { content: ')'; }
|
||||
|
||||
.popover .column h3 {
|
||||
.popover label {
|
||||
font-size: 13px;
|
||||
color: #a9bcc4;
|
||||
font-weight: 200;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.07em;
|
||||
}
|
||||
|
||||
.popover .column label {
|
||||
margin: 8px 0 1px;
|
||||
}
|
||||
|
||||
|
@ -3140,16 +3183,14 @@ footer {
|
|||
padding: 0 81px;
|
||||
}
|
||||
|
||||
.ticket-title h1 {
|
||||
.ticket-title-update {
|
||||
@extend h1;
|
||||
white-space: normal;
|
||||
margin-top: 15px;
|
||||
margin-bottom: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ticket-title-update {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.task-subline {
|
||||
text-align: center;
|
||||
display: block;
|
||||
|
@ -4178,6 +4219,7 @@ footer {
|
|||
border-radius: 0;
|
||||
border: 1px solid hsl(0,0%,90%);
|
||||
box-shadow: none;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
|
@ -4381,15 +4423,9 @@ footer {
|
|||
position: relative;
|
||||
}
|
||||
|
||||
.userInfo-avatar:after {
|
||||
content: "";
|
||||
background: image_url("/assets/images/sprite.svg");
|
||||
background-position: -236px 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 97px;
|
||||
height: 96px;
|
||||
position: absolute;
|
||||
.organizationInfo-avatar {
|
||||
@extend .userInfo-avatar;
|
||||
padding: 18px 0 0 18px;
|
||||
}
|
||||
|
||||
.userList {
|
||||
|
@ -4397,17 +4433,16 @@ footer {
|
|||
padding: 0;
|
||||
|
||||
li {
|
||||
@extend .horizontal;
|
||||
@extend .center;
|
||||
position: relative;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
a {
|
||||
@extend .u-textTruncate;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
margin-right: 7px;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 0px;
|
||||
left: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -264,7 +264,7 @@ class ApplicationController < ActionController::Base
|
|||
begin
|
||||
|
||||
# create object
|
||||
generic_object = object.new( object.param_cleanup( params[object.to_app_model_url] ) )
|
||||
generic_object = object.new( object.param_cleanup( params[object.to_app_model_url], true ) )
|
||||
|
||||
# save object
|
||||
generic_object.save!
|
||||
|
|
|
@ -97,9 +97,7 @@ class TicketsController < ApplicationController
|
|||
:customer_id => params[:customer_id],
|
||||
:limit => 15,
|
||||
)
|
||||
render :json => {
|
||||
:tickets => result
|
||||
}
|
||||
render :json => result
|
||||
end
|
||||
|
||||
# GET /api/v1/ticket_history/1
|
||||
|
|
|
@ -64,7 +64,7 @@ class UsersController < ApplicationController
|
|||
# @response_message 200 [User] Created User record.
|
||||
# @response_message 401 Invalid session.
|
||||
def create
|
||||
user = User.new( User.param_cleanup(params) )
|
||||
user = User.new( User.param_cleanup(params, true) )
|
||||
|
||||
begin
|
||||
# check if it's first user
|
||||
|
@ -122,7 +122,7 @@ class UsersController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
user.save
|
||||
user.save!
|
||||
|
||||
# if first user was added, set system init done
|
||||
if count <= 2
|
||||
|
@ -702,7 +702,7 @@ curl http://localhost/api/v1/users/avatar -v -u #{login}:#{password} -H "Content
|
|||
private
|
||||
|
||||
def password_policy(password)
|
||||
if Setting.get('password_min_size') > password.length
|
||||
if Setting.get('password_min_size').to_i > password.length
|
||||
return ["Can\'t update password, it must be at least %s characters long!", Setting.get('password_min_size')]
|
||||
end
|
||||
if Setting.get('password_need_digit').to_i == 1 && password !~ /\d/
|
||||
|
|
|
@ -41,10 +41,10 @@ class ApplicationModel < ActiveRecord::Base
|
|||
|
||||
attr_accessor :history_changes_last_done
|
||||
|
||||
@@import_class_list = ['Ticket', 'Ticket::Article', 'History', 'Ticket::State', 'Ticket::Priority', 'Group', 'User' ]
|
||||
@@import_class_list = ['Ticket', 'Ticket::Article', 'History', 'Ticket::State', 'Ticket::StateType', 'Ticket::Priority', 'Group', 'User', 'Role' ]
|
||||
|
||||
def check_attributes_protected
|
||||
if Setting.get('import_mode') && @@import_class_list.include?( self.class.to_s )
|
||||
if ( !Setting.get('system_init_done') || Setting.get('import_mode')) && @@import_class_list.include?( self.class.to_s )
|
||||
# do noting, use id as it is
|
||||
else
|
||||
self[:id] = nil
|
||||
|
@ -57,18 +57,28 @@ remove all not used model attributes of params
|
|||
|
||||
result = Model.param_cleanup(params)
|
||||
|
||||
for object creation, ignore id's
|
||||
|
||||
result = Model.param_cleanup(params, true)
|
||||
|
||||
|
||||
returns
|
||||
|
||||
result = params # params with valid attributes of model
|
||||
|
||||
=end
|
||||
|
||||
def self.param_cleanup(params)
|
||||
def self.param_cleanup(params, newObject = false)
|
||||
|
||||
if params == nil
|
||||
raise "No params for #{self.to_s}!"
|
||||
end
|
||||
|
||||
# ignore id for new objects
|
||||
if newObject && params[:id]
|
||||
params[:id] = nil
|
||||
end
|
||||
|
||||
# only use object attributes
|
||||
data = {}
|
||||
self.new.attributes.each {|item|
|
||||
|
|
|
@ -27,7 +27,7 @@ add a new history entry for an object
|
|||
:id_to => 3,
|
||||
:id_from => 2,
|
||||
:value_from => 'open',
|
||||
:value_to => 'pending',
|
||||
:value_to => 'pending reminder',
|
||||
:created_by_id => 1,
|
||||
:created_at => '2013-06-04 10:00:00',
|
||||
:updated_at => '2013-06-04 10:00:00'
|
||||
|
|
|
@ -65,7 +65,7 @@ list of all attributes
|
|||
|
||||
=begin
|
||||
|
||||
add a new activity entry for an object
|
||||
add a new attribute entry for an object
|
||||
|
||||
ObjectManager::Attribute.add(
|
||||
:object => 'Ticket',
|
||||
|
@ -101,7 +101,6 @@ add a new activity entry for an object
|
|||
:updated_at => '2014-06-04 10:00:00',
|
||||
)
|
||||
|
||||
|
||||
=end
|
||||
|
||||
def self.add(data)
|
||||
|
@ -129,6 +128,30 @@ add a new activity entry for an object
|
|||
|
||||
=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.where(
|
||||
:object_lookup_id => data[:object_lookup_id],
|
||||
:name => data[:name],
|
||||
).first
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
get user based list of object attributes
|
||||
|
||||
attribute_list = ObjectManager::Attribute.by_object('Ticket', user)
|
||||
|
|
|
@ -128,6 +128,9 @@ class Observer::Ticket::Notification < ActiveRecord::Observer
|
|||
# return if we run import mode
|
||||
return if Setting.get('import_mode')
|
||||
|
||||
# ignore updates on articles / we just want send notifications on ticket updates
|
||||
return if record.class.name == 'Ticket::Article'
|
||||
|
||||
# ignore certain attributes
|
||||
real_changes = {}
|
||||
record.changes.each {|key, value|
|
||||
|
|
|
@ -165,12 +165,15 @@ class Observer::Ticket::Notification::BackgroundJob
|
|||
attribute_name = key.to_s
|
||||
object_manager_attribute = attribute_list[attribute_name]
|
||||
if attribute_name[-3,3] == '_id'
|
||||
attribute_name = attribute_name[ 0, attribute_name.length-3 ]
|
||||
end
|
||||
if key == attribute_name
|
||||
changes[key] = value
|
||||
attribute_name = attribute_name[ 0, attribute_name.length-3 ].to_s
|
||||
end
|
||||
|
||||
# add item to changes hash
|
||||
if key.to_s == attribute_name
|
||||
changes[attribute_name] = value
|
||||
end
|
||||
|
||||
# if changed item is an _id field/reference, do an lookup for the realy values
|
||||
value_id = []
|
||||
value_str = [ value[0], value[1] ]
|
||||
if key.to_s[-3,3] == '_id'
|
||||
|
@ -201,9 +204,16 @@ class Observer::Ticket::Notification::BackgroundJob
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
# check if we have an dedcated display name for it
|
||||
display = attribute_name
|
||||
if object_manager_attribute && object_manager_attribute[:display]
|
||||
display = object_manager_attribute[:display]
|
||||
|
||||
# delete old key
|
||||
changes.delete( display )
|
||||
|
||||
# set new key
|
||||
display = object_manager_attribute[:display].to_s
|
||||
end
|
||||
if object_manager_attribute && object_manager_attribute[:translate]
|
||||
changes[display] = ["i18n(#{value_str[0].to_s})", "i18n(#{value_str[1].to_s})"]
|
||||
|
|
30
app/models/observer/ticket/ref_object_touch.rb
Normal file
30
app/models/observer/ticket/ref_object_touch.rb
Normal file
|
@ -0,0 +1,30 @@
|
|||
# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class Observer::Ticket::RefObjectTouch < ActiveRecord::Observer
|
||||
observe 'ticket'
|
||||
|
||||
def after_create(record)
|
||||
ref_object_touch(record)
|
||||
end
|
||||
def after_update(record)
|
||||
ref_object_touch(record)
|
||||
end
|
||||
def after_touch(record)
|
||||
ref_object_touch(record)
|
||||
end
|
||||
def after_destroy(record)
|
||||
ref_object_touch(record)
|
||||
end
|
||||
|
||||
def ref_object_touch(record)
|
||||
|
||||
# return if we run import mode
|
||||
return if Setting.get('import_mode')
|
||||
if record.customer
|
||||
record.customer.touch
|
||||
end
|
||||
if record.organization
|
||||
record.organization.touch
|
||||
end
|
||||
end
|
||||
end
|
|
@ -15,7 +15,7 @@ class Ticket < ApplicationModel
|
|||
extend Ticket::Search
|
||||
|
||||
before_create :check_generate, :check_defaults, :check_title
|
||||
before_update :check_defaults, :check_title
|
||||
before_update :check_defaults, :check_title, :reset_pending_time
|
||||
before_destroy :destroy_dependencies
|
||||
|
||||
notify_clients_support
|
||||
|
@ -187,6 +187,21 @@ returns
|
|||
end
|
||||
end
|
||||
|
||||
def reset_pending_time
|
||||
|
||||
# ignore if no state has changed
|
||||
return if !self.changes['state_id']
|
||||
|
||||
# check if new state isn't pending*
|
||||
current_state = Ticket::State.lookup( :id => self.state_id )
|
||||
current_state_type = Ticket::StateType.lookup( :id => current_state.state_type_id )
|
||||
|
||||
# in case, set pending_time to nil
|
||||
if current_state_type.name !~ /^pending/i
|
||||
self.pending_time = nil
|
||||
end
|
||||
end
|
||||
|
||||
def destroy_dependencies
|
||||
|
||||
# delete articles
|
||||
|
|
|
@ -133,8 +133,9 @@ list tickets by customer groupd in state categroie open and closed
|
|||
returns
|
||||
|
||||
result = {
|
||||
:open => tickets_open,
|
||||
:closed => tickets_closed,
|
||||
:ticket_ids_open => tickets_open,
|
||||
:ticket_ids_closed => tickets_closed,
|
||||
:assets => { ...list of assets... },
|
||||
}
|
||||
|
||||
=end
|
||||
|
@ -150,15 +151,27 @@ returns
|
|||
:customer_id => data[:customer_id],
|
||||
:state_id => state_list_open
|
||||
).limit( data[:limit] || 15 ).order('created_at DESC')
|
||||
assets = {}
|
||||
ticket_ids_open = []
|
||||
tickets_open.each {|ticket|
|
||||
ticket_ids_open.push ticket.id
|
||||
assets = ticket.assets(assets)
|
||||
}
|
||||
|
||||
tickets_closed = Ticket.where(
|
||||
:customer_id => data[:customer_id],
|
||||
:state_id => state_list_closed
|
||||
).limit( data[:limit] || 15 ).order('created_at DESC')
|
||||
ticket_ids_closed = []
|
||||
tickets_closed.each {|ticket|
|
||||
ticket_ids_closed.push ticket.id
|
||||
assets = ticket.assets(assets)
|
||||
}
|
||||
|
||||
return {
|
||||
:open => tickets_open,
|
||||
:closed => tickets_closed,
|
||||
:ticket_ids_open => ticket_ids_open,
|
||||
:ticket_ids_closed => ticket_ids_closed,
|
||||
:assets => assets,
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -192,7 +192,7 @@ returns
|
|||
end
|
||||
|
||||
# check failed logins
|
||||
max_login_failed = Setting.get('password_max_login_failed') || 10
|
||||
max_login_failed = Setting.get('password_max_login_failed').to_i || 10
|
||||
if user && user.login_failed > max_login_failed
|
||||
return false
|
||||
end
|
||||
|
|
16
app/views/tests/model_ui.html.erb
Normal file
16
app/views/tests/model_ui.html.erb
Normal file
|
@ -0,0 +1,16 @@
|
|||
|
||||
<link rel="stylesheet" href="/assets/tests/qunit-1.10.0.css">
|
||||
<script src="/assets/tests/qunit-1.10.0.js"></script>
|
||||
<script src="/assets/tests/model-ui.js"></script>
|
||||
|
||||
<style type="text/css">
|
||||
body {
|
||||
padding-top: 0px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">
|
||||
</script>
|
||||
|
||||
<div id="qunit"></div>
|
||||
|
|
@ -40,6 +40,7 @@ module Zammad
|
|||
'observer::_ticket::_notification',
|
||||
'observer::_ticket::_reset_new_state',
|
||||
'observer::_ticket::_escalation_calculation',
|
||||
'observer::_ticket::_ref_object_touch',
|
||||
'observer::_tag::_ticket_history',
|
||||
'observer::_user::_geo'
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ Zammad::Application.routes.draw do
|
|||
match '/tests-core', :to => 'tests#core', :via => :get
|
||||
match '/tests-ui', :to => 'tests#ui', :via => :get
|
||||
match '/tests-model', :to => 'tests#model', :via => :get
|
||||
match '/tests-model-ui', :to => 'tests#model_ui', :via => :get
|
||||
match '/tests-form', :to => 'tests#form', :via => :get
|
||||
match '/tests-form-extended', :to => 'tests#form_extended', :via => :get
|
||||
match '/tests-form-validation', :to => 'tests#form_validation', :via => :get
|
||||
|
|
|
@ -463,6 +463,7 @@ class UpdateObjectManager2 < ActiveRecord::Migration
|
|||
:maxlength => 100,
|
||||
:null => true,
|
||||
:autocomplete => 'off',
|
||||
:item_class => 'formGroup--halfSize',
|
||||
},
|
||||
:editable => false,
|
||||
:active => true,
|
||||
|
@ -496,6 +497,7 @@ class UpdateObjectManager2 < ActiveRecord::Migration
|
|||
:maxlength => 250,
|
||||
:null => true,
|
||||
:note => 'Notes are visible to agents only, never to customers.',
|
||||
# :item_class => 'formGroup--halfSize',
|
||||
},
|
||||
:editable => false,
|
||||
:active => true,
|
||||
|
@ -591,9 +593,8 @@ class UpdateObjectManager2 < ActiveRecord::Migration
|
|||
:object => 'User',
|
||||
:name => 'active',
|
||||
:display => 'Active',
|
||||
:data_type => 'boolean',
|
||||
:data_type => 'active',
|
||||
:data_option => {
|
||||
:maxlength => 250,
|
||||
:null => true,
|
||||
:default => true,
|
||||
},
|
||||
|
@ -722,10 +723,8 @@ class UpdateObjectManager2 < ActiveRecord::Migration
|
|||
:object => 'Organization',
|
||||
:name => 'active',
|
||||
:display => 'Active',
|
||||
:data_type => 'boolean',
|
||||
:data_type => 'active',
|
||||
:data_option => {
|
||||
:maxlength => 250,
|
||||
:null => true,
|
||||
:default => true,
|
||||
},
|
||||
:editable => false,
|
||||
|
|
44
db/migrate/20150206000001_create_vip.rb
Normal file
44
db/migrate/20150206000001_create_vip.rb
Normal file
|
@ -0,0 +1,44 @@
|
|||
class CreateVip < ActiveRecord::Migration
|
||||
def up
|
||||
add_column :users, :vip, :boolean, :default => false
|
||||
|
||||
ObjectManager::Attribute.add(
|
||||
:object => 'User',
|
||||
:name => 'vip',
|
||||
:display => 'VIP',
|
||||
:data_type => 'boolean',
|
||||
:data_option => {
|
||||
:null => true,
|
||||
:default => false,
|
||||
:item_class => 'formGroup--halfSize',
|
||||
:options => {
|
||||
:false => 'no',
|
||||
:true => 'yes',
|
||||
},
|
||||
:translate => true,
|
||||
},
|
||||
:editable => false,
|
||||
:active => true,
|
||||
:screens => {
|
||||
:edit => {
|
||||
:Admin => {
|
||||
:null => true,
|
||||
},
|
||||
},
|
||||
:view => {
|
||||
'-all-' => {
|
||||
:shown => false,
|
||||
},
|
||||
},
|
||||
},
|
||||
:pending_migration => false,
|
||||
:position => 1490,
|
||||
:created_by_id => 1,
|
||||
:updated_by_id => 1,
|
||||
)
|
||||
|
||||
end
|
||||
|
||||
def down
|
||||
end
|
||||
end
|
70
db/migrate/20150206000002_create_address.rb
Normal file
70
db/migrate/20150206000002_create_address.rb
Normal file
|
@ -0,0 +1,70 @@
|
|||
class CreateAddress < ActiveRecord::Migration
|
||||
def up
|
||||
add_column :users, :address, :string, :limit => 500, :null => true
|
||||
|
||||
User.all.each {|user|
|
||||
address = ''
|
||||
if user.street && !user.street.empty?
|
||||
address += "#{user.street}\n"
|
||||
end
|
||||
if user.zip && !user.zip.empty?
|
||||
address += "#{user.zip} "
|
||||
end
|
||||
if user.city && !user.city.empty?
|
||||
address += "#{user.city}"
|
||||
end
|
||||
if !address.empty?
|
||||
user.address = address
|
||||
user.save
|
||||
end
|
||||
}
|
||||
|
||||
['street', 'zip', 'city', 'department'].each {|attribute_name|
|
||||
attribute = ObjectManager::Attribute.get(
|
||||
:object => 'User',
|
||||
:name => attribute_name,
|
||||
)
|
||||
if attribute
|
||||
attribute.active = false
|
||||
attribute.save
|
||||
end
|
||||
}
|
||||
|
||||
ObjectManager::Attribute.add(
|
||||
:object => 'User',
|
||||
:name => 'address',
|
||||
:display => 'Address',
|
||||
:data_type => 'textarea',
|
||||
:data_option => {
|
||||
:type => 'text',
|
||||
:maxlength => 500,
|
||||
:null => true,
|
||||
:item_class => 'formGroup--halfSize',
|
||||
},
|
||||
:editable => false,
|
||||
:active => true,
|
||||
:screens => {
|
||||
:signup => {},
|
||||
:invite_agent => {},
|
||||
:edit => {
|
||||
'-all-' => {
|
||||
:null => true,
|
||||
},
|
||||
},
|
||||
:view => {
|
||||
'-all-' => {
|
||||
:shown => true,
|
||||
},
|
||||
},
|
||||
},
|
||||
:pending_migration => false,
|
||||
:position => 1350,
|
||||
:created_by_id => 1,
|
||||
:updated_by_id => 1,
|
||||
)
|
||||
|
||||
end
|
||||
|
||||
def down
|
||||
end
|
||||
end
|
100
db/migrate/20150208000001_update_object_manager3.rb
Normal file
100
db/migrate/20150208000001_update_object_manager3.rb
Normal file
|
@ -0,0 +1,100 @@
|
|||
class UpdateObjectManager3 < ActiveRecord::Migration
|
||||
def up
|
||||
|
||||
ObjectManager::Attribute.add(
|
||||
:object => 'User',
|
||||
:name => 'active',
|
||||
:display => 'Active',
|
||||
:data_type => 'active',
|
||||
:data_option => {
|
||||
:default => true,
|
||||
},
|
||||
:editable => false,
|
||||
:active => true,
|
||||
:screens => {
|
||||
:signup => {},
|
||||
:invite_agent => {},
|
||||
:edit => {
|
||||
:Admin => {
|
||||
:null => false,
|
||||
},
|
||||
},
|
||||
:view => {
|
||||
'-all-' => {
|
||||
:shown => false,
|
||||
},
|
||||
},
|
||||
},
|
||||
:pending_migration => false,
|
||||
:position => 1800,
|
||||
:created_by_id => 1,
|
||||
:updated_by_id => 1,
|
||||
)
|
||||
|
||||
ObjectManager::Attribute.add(
|
||||
:object => 'Organization',
|
||||
:name => 'active',
|
||||
:display => 'Active',
|
||||
:data_type => 'active',
|
||||
:data_option => {
|
||||
:default => true,
|
||||
},
|
||||
:editable => false,
|
||||
:active => true,
|
||||
:screens => {
|
||||
:edit => {
|
||||
:Admin => {
|
||||
:null => false,
|
||||
},
|
||||
},
|
||||
:view => {
|
||||
'-all-' => {
|
||||
:shown => false,
|
||||
},
|
||||
},
|
||||
},
|
||||
:pending_migration => false,
|
||||
:position => 1800,
|
||||
:created_by_id => 1,
|
||||
:updated_by_id => 1,
|
||||
)
|
||||
|
||||
ObjectManager::Attribute.add(
|
||||
:object => 'User',
|
||||
:name => 'password',
|
||||
:display => 'Password',
|
||||
:data_type => 'input',
|
||||
:data_option => {
|
||||
:type => 'password',
|
||||
:maxlength => 100,
|
||||
:null => true,
|
||||
:autocomplete => 'off',
|
||||
:item_class => 'formGroup--halfSize',
|
||||
},
|
||||
:editable => false,
|
||||
:active => true,
|
||||
:screens => {
|
||||
:signup => {
|
||||
'-all-' => {
|
||||
:null => false,
|
||||
},
|
||||
},
|
||||
:invite_agent => {},
|
||||
:edit => {
|
||||
:Admin => {
|
||||
:null => true,
|
||||
},
|
||||
},
|
||||
:view => {}
|
||||
},
|
||||
:pending_migration => false,
|
||||
:position => 1400,
|
||||
:created_by_id => 1,
|
||||
:updated_by_id => 1,
|
||||
)
|
||||
|
||||
end
|
||||
|
||||
def down
|
||||
end
|
||||
end
|
15
db/seeds.rb
15
db/seeds.rb
|
@ -1335,13 +1335,13 @@ Link::Object.create_if_not_exists( :name => 'Question/Answer' )
|
|||
Link::Object.create_if_not_exists( :name => 'Idea' )
|
||||
Link::Object.create_if_not_exists( :name => 'Bug' )
|
||||
|
||||
Ticket::StateType.create_if_not_exists( :id => 1, :name => 'new', :updated_by_id => 1 )
|
||||
Ticket::StateType.create_if_not_exists( :id => 2, :name => 'open', :updated_by_id => 1 )
|
||||
Ticket::StateType.create_if_not_exists( :id => 3, :name => 'pending reminder', :updated_by_id => 1 )
|
||||
Ticket::StateType.create_if_not_exists( :id => 4, :name => 'pending action', :updated_by_id => 1 )
|
||||
Ticket::StateType.create_if_not_exists( :id => 5, :name => 'closed', :updated_by_id => 1 )
|
||||
Ticket::StateType.create_if_not_exists( :id => 6, :name => 'merged', :updated_by_id => 1 )
|
||||
Ticket::StateType.create_if_not_exists( :id => 7, :name => 'removed', :updated_by_id => 1 )
|
||||
Ticket::StateType.create_if_not_exists( :id => 1, :name => 'new' )
|
||||
Ticket::StateType.create_if_not_exists( :id => 2, :name => 'open' )
|
||||
Ticket::StateType.create_if_not_exists( :id => 3, :name => 'pending reminder' )
|
||||
Ticket::StateType.create_if_not_exists( :id => 4, :name => 'pending action' )
|
||||
Ticket::StateType.create_if_not_exists( :id => 5, :name => 'closed' )
|
||||
Ticket::StateType.create_if_not_exists( :id => 6, :name => 'merged' )
|
||||
Ticket::StateType.create_if_not_exists( :id => 7, :name => 'removed' )
|
||||
|
||||
Ticket::State.create_if_not_exists( :id => 1, :name => 'new', :state_type_id => Ticket::StateType.where(:name => 'new').first.id )
|
||||
Ticket::State.create_if_not_exists( :id => 2, :name => 'open', :state_type_id => Ticket::StateType.where(:name => 'open').first.id )
|
||||
|
@ -1938,6 +1938,7 @@ Translation.create_if_not_exists( :locale => 'de', :source => "to", :target => "
|
|||
Translation.create_if_not_exists( :locale => 'de', :source => "%s ago", :target => "vor %s" )
|
||||
Translation.create_if_not_exists( :locale => 'de', :source => "in %s", :target => "in %s" )
|
||||
Translation.create_if_not_exists( :locale => 'de', :source => "Mark all as seen.", :target => "Alle als gelesen markieren." )
|
||||
Translation.create_if_not_exists( :locale => 'de', :source => "Pending till", :target => "Warten bis" )
|
||||
#Translation.create_if_not_exists( :locale => 'de', :source => "", :target => "" )
|
||||
|
||||
# install all packages in auto_install
|
||||
|
|
|
@ -971,6 +971,12 @@ module Import::OTRS2
|
|||
# check if agent already exists
|
||||
user_old = User.where( :id => user_new[:id] ).first
|
||||
|
||||
# check if login is already used
|
||||
login_in_use = User.where( "login = ? AND id != #{user_new[:id]}", user_new[:login].downcase ).count
|
||||
if login_in_use > 0
|
||||
user_new[:login] = "#{user_new[:login]}_#{user_new[:id]}"
|
||||
end
|
||||
|
||||
# create / update agent
|
||||
if user_old
|
||||
puts "update User.find(#{user_old[:id]})"
|
||||
|
|
|
@ -80,7 +80,7 @@ module NotificationFactory
|
|||
}
|
||||
|
||||
# translate
|
||||
data[:string].gsub!( /i18n\((.+?)\)/ ) { |placeholder|
|
||||
data[:string].gsub!( /i18n\((|.+?)\)/ ) { |placeholder|
|
||||
string = $1
|
||||
locale = data[:locale] || 'en'
|
||||
placeholder = Translation.translate( locale, string )
|
||||
|
|
|
@ -486,10 +486,12 @@ test( "clone", function() {
|
|||
var source = [
|
||||
{ name: 'some name' },
|
||||
{ name: 'some name2' },
|
||||
{ fn: function() { return 'test' } },
|
||||
]
|
||||
var reference = [
|
||||
{ name: 'some name' },
|
||||
{ name: 'some name2' },
|
||||
{ fn: undefined },
|
||||
]
|
||||
var result = clone( source )
|
||||
|
||||
|
@ -498,6 +500,33 @@ test( "clone", function() {
|
|||
|
||||
deepEqual( result, reference, 'clone' );
|
||||
|
||||
|
||||
// full test
|
||||
var source = [
|
||||
{ name: 'some name' },
|
||||
{ name: 'some name2' },
|
||||
{ fn: function a() { return 'test' } },
|
||||
]
|
||||
var reference = [
|
||||
{ name: 'some name' },
|
||||
{ name: 'some name2' },
|
||||
{ fn: function a() { return 'test' } },
|
||||
]
|
||||
var result = clone( source, true )
|
||||
|
||||
// modify source later, should not have any result
|
||||
source[0].name = 'some new name'
|
||||
source[2].fn = 'some new name'
|
||||
|
||||
deepEqual( result[0], reference[0], 'clone full' );
|
||||
deepEqual( result[1], reference[1], 'clone full' );
|
||||
|
||||
equal( typeof reference[2].fn, 'function')
|
||||
equal( typeof result[2].fn, 'function')
|
||||
|
||||
equal( reference[2].fn(), 'test')
|
||||
equal( result[2].fn(), 'test')
|
||||
|
||||
});
|
||||
|
||||
// diff
|
||||
|
|
|
@ -17,7 +17,7 @@ test( "form validation check", function() {
|
|||
{ name: 'richtext1', display: 'Richtext1', tag: 'richtext', maxlength: 100, null: false, type: 'richtext', multiline: true, upload: true, default: defaults['richtext1'] },
|
||||
{ name: 'datetime1', display: 'Datetime1', tag: 'datetime', null: false, default: defaults['datetime1'] },
|
||||
{ name: 'date1', display: 'Date1', tag: 'date', null: false, default: defaults['date1'] },
|
||||
{ name: 'active1', display: 'Active1', tag: 'boolean', type: 'boolean', default: defaults['active1'], null: false },
|
||||
{ name: 'active1', display: 'Active1', tag: 'active', default: defaults['active1'] },
|
||||
],
|
||||
},
|
||||
params: defaults,
|
||||
|
@ -249,3 +249,83 @@ test( "date validation check", function() {
|
|||
equal( el.find('[data-name="date1"]').closest('.form-group').find('.help-inline').text(), '', 'check date1 error message')
|
||||
|
||||
});
|
||||
|
||||
test( "datetime selector check", function() {
|
||||
|
||||
$('#forms').append('<hr><h1>datetime selector check</h1><form id="form4"></form>')
|
||||
|
||||
var el = $('#form4')
|
||||
var defaults = {}
|
||||
var form = new App.ControllerForm({
|
||||
el: el,
|
||||
model: {
|
||||
configure_attributes: [
|
||||
{ name: 'datetime1', display: 'Datetime1', tag: 'datetime', null: false, default: defaults['datetime1'] },
|
||||
],
|
||||
},
|
||||
params: defaults,
|
||||
});
|
||||
|
||||
// check params
|
||||
params = App.ControllerForm.params( el )
|
||||
test_params = {
|
||||
datetime1: undefined,
|
||||
}
|
||||
deepEqual( params, test_params, 'params check' )
|
||||
|
||||
el.find('.js-today').click()
|
||||
|
||||
// check params
|
||||
timeStamp = new Date()
|
||||
currentTime = timeStamp.toISOString()
|
||||
currentTime = currentTime.replace(/(\d\d\.\d\d\dZ)$/, '00.000Z')
|
||||
params = App.ControllerForm.params( el )
|
||||
test_params = {
|
||||
datetime1: currentTime,
|
||||
}
|
||||
deepEqual( params, test_params, 'params check' )
|
||||
|
||||
});
|
||||
|
||||
test( "date selector check", function() {
|
||||
|
||||
$('#forms').append('<hr><h1>date selector check</h1><form id="form5"></form>')
|
||||
|
||||
var el = $('#form5')
|
||||
var defaults = {}
|
||||
var form = new App.ControllerForm({
|
||||
el: el,
|
||||
model: {
|
||||
configure_attributes: [
|
||||
{ name: 'date1', display: 'Datet1', tag: 'date', null: false, default: defaults['date1'] },
|
||||
],
|
||||
},
|
||||
params: defaults,
|
||||
});
|
||||
|
||||
// check params
|
||||
params = App.ControllerForm.params( el )
|
||||
test_params = {
|
||||
date1: undefined,
|
||||
}
|
||||
deepEqual( params, test_params, 'params check' )
|
||||
|
||||
el.find('.js-today').click()
|
||||
|
||||
// check params
|
||||
format = function (number) {
|
||||
if ( parseInt(number) < 10 ) {
|
||||
number = '0' + number.toString()
|
||||
}
|
||||
return number
|
||||
}
|
||||
|
||||
timeStamp = new Date()
|
||||
currentTime = timeStamp.getFullYear() + '-' + format(timeStamp.getMonth()+1) + '-' + format(timeStamp.getDate())
|
||||
params = App.ControllerForm.params( el )
|
||||
test_params = {
|
||||
date1: currentTime,
|
||||
}
|
||||
deepEqual( params, test_params, 'params check' )
|
||||
|
||||
});
|
|
@ -159,8 +159,8 @@ test( "form params check", function() {
|
|||
{ name: 'date2', display: 'Date2', tag: 'date', null: true, default: defaults['date2'] },
|
||||
{ name: 'date3', display: 'Date3', tag: 'date', null: false, default: defaults['date3'] },
|
||||
{ name: 'date4', display: 'Date4', tag: 'date', null: false, default: defaults['date4'] },
|
||||
{ name: 'active1', display: 'Active1', tag: 'boolean', type: 'boolean', default: defaults['active1'], null: false },
|
||||
{ name: 'active2', display: 'Active2', tag: 'boolean', type: 'boolean', default: defaults['active2'], null: false },
|
||||
{ name: 'active1', display: 'Active1', tag: 'active', default: defaults['active1'] },
|
||||
{ name: 'active2', display: 'Active2', tag: 'active', default: defaults['active2'] },
|
||||
],
|
||||
},
|
||||
params: defaults,
|
||||
|
@ -795,7 +795,7 @@ test( "form required_if + shown_if", function() {
|
|||
{ name: 'input2', display: 'Input2', tag: 'input', type: 'text', limit: 100, null: true, default: 'some used default', required_if: { active: true }, shown_if: { active: true } },
|
||||
{ name: 'input3', display: 'Input3', tag: 'input', type: 'text', limit: 100, null: true, default: 'some used default', required_if: { active: [true,false] }, shown_if: { active: [true,false] } },
|
||||
{ name: 'input4', display: 'Input4', tag: 'input', type: 'text', limit: 100, null: true, default: 'some used default', required_if: { active: [55,66] }, shown_if: { active: [55,66] } },
|
||||
{ name: 'active', display: 'Active', tag: 'boolean', type: 'boolean', 'default': true, null: false },
|
||||
{ name: 'active', display: 'Active', tag: 'active', 'default': true },
|
||||
],
|
||||
},
|
||||
params: defaults,
|
||||
|
|
87
public/assets/tests/model-ui.js
Normal file
87
public/assets/tests/model-ui.js
Normal file
|
@ -0,0 +1,87 @@
|
|||
window.onload = function() {
|
||||
|
||||
// model
|
||||
test( "model ui basic tests", function() {
|
||||
|
||||
// load ref object
|
||||
App.Collection.loadAssets({
|
||||
TicketState: {
|
||||
1: {
|
||||
name: 'new', id: 1, updated_at: "2014-11-07T23:43:08.000Z",
|
||||
},
|
||||
2: {
|
||||
name: 'open', id: 2, updated_at: "2014-11-07T23:43:08.000Z",
|
||||
},
|
||||
3: {
|
||||
name: 'closed <>&', id: 3, updated_at: "2014-11-07T23:43:08.000Z",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// create ticket
|
||||
var attribute1 = {
|
||||
name: 'date', display: 'date 1', tag: 'date', null: true
|
||||
};
|
||||
App.Ticket.configure_attributes.push( attribute1 )
|
||||
var attribute2 = {
|
||||
name: 'textarea', display: 'textarea 1', tag: 'textarea', null: true
|
||||
};
|
||||
App.Ticket.configure_attributes.push( attribute2 )
|
||||
|
||||
var ticket = new App.Ticket()
|
||||
ticket.load({
|
||||
id: 1000,
|
||||
title: 'some title <>&',
|
||||
state_id: 2,
|
||||
updated_at: '2014-11-07T23:43:08.000Z',
|
||||
date: '2015-02-07',
|
||||
textarea: "some new\nline"
|
||||
})
|
||||
|
||||
App.i18n.set('en')
|
||||
equal( App.viewPrint( ticket, 'id' ), 1000)
|
||||
equal( App.viewPrint( ticket, 'title' ), 'some title <>&')
|
||||
equal( App.viewPrint( ticket, 'state' ), 'open')
|
||||
equal( App.viewPrint( ticket, 'state_id' ), 'open')
|
||||
equal( App.viewPrint( ticket, 'not_existing' ), '-')
|
||||
equal( App.viewPrint( ticket, 'updated_at' ), "<span class=\"humanTimeFromNow undefined\" data-time=\"2014-11-07T23:43:08.000Z\">?</span>")
|
||||
equal( App.viewPrint( ticket, 'date' ), '2015-02-07')
|
||||
equal( App.viewPrint( ticket, 'textarea' ), '<div>some new</div><div>line</div>')
|
||||
|
||||
|
||||
App.i18n.set('de')
|
||||
equal( App.viewPrint( ticket, 'id' ), 1000)
|
||||
equal( App.viewPrint( ticket, 'title' ), 'some title <>&')
|
||||
equal( App.viewPrint( ticket, 'state' ), 'offen')
|
||||
equal( App.viewPrint( ticket, 'state_id' ), 'offen')
|
||||
equal( App.viewPrint( ticket, 'not_existing' ), '-')
|
||||
equal( App.viewPrint( ticket, 'updated_at' ), "<span class=\"humanTimeFromNow undefined\" data-time=\"2014-11-07T23:43:08.000Z\">?</span>")
|
||||
equal( App.viewPrint( ticket, 'date' ), '07.02.2015')
|
||||
equal( App.viewPrint( ticket, 'textarea' ), '<div>some new</div><div>line</div>')
|
||||
|
||||
|
||||
App.i18n.set('en')
|
||||
ticket.state_id = 3
|
||||
equal( App.viewPrint( ticket, 'state' ), 'closed <>&')
|
||||
equal( App.viewPrint( ticket, 'state_id' ), 'closed <>&')
|
||||
|
||||
App.i18n.set('de')
|
||||
equal( App.viewPrint( ticket, 'state' ), 'closed <>&')
|
||||
equal( App.viewPrint( ticket, 'state_id' ), 'closed <>&')
|
||||
|
||||
// normal string
|
||||
data = {
|
||||
a: 1,
|
||||
b: 'abc',
|
||||
c: {
|
||||
displayName: function() { return "my displayName <>&" }
|
||||
},
|
||||
}
|
||||
equal( App.viewPrint( data, 'a' ), 1)
|
||||
equal( App.viewPrint( data, 'b' ), 'abc')
|
||||
equal( App.viewPrint( data, 'c' ), 'my displayName <>&')
|
||||
|
||||
});
|
||||
|
||||
|
||||
}
|
|
@ -410,14 +410,13 @@ test( "table test 2", function() {
|
|||
equal( el.find('tbody > tr:nth-child(1) > td:nth-child(1)').text().trim(), 'adapter1', 'check row 1')
|
||||
equal( el.find('tbody > tr:nth-child(1) > td:nth-child(2)').text().trim(), 'host1', 'check row 1')
|
||||
equal( el.find('tbody > tr:nth-child(1) > td:nth-child(3)').text().trim(), 'user1', 'check row 1')
|
||||
equal( el.find('tbody > tr:nth-child(1) > td:nth-child(4)').text().trim(), 'true', 'check row 1')
|
||||
equal( el.find('tbody > tr:nth-child(1) > td:nth-child(4)').text().trim(), 'true', 'check row 1')
|
||||
equal( el.find('tbody > tr:nth-child(1) > td:nth-child(4)').text().trim(), 'ja', 'check row 1')
|
||||
equal( el.find('tbody > tr:nth-child(1) > td:nth-child(5)').text().trim(), '', 'check row 1')
|
||||
equal( el.find('tbody > tr:nth-child(2) > td').length, 5, 'check row 2')
|
||||
equal( el.find('tbody > tr:nth-child(2) > td:nth-child(1)').text().trim(), 'adapter2', 'check row 2')
|
||||
equal( el.find('tbody > tr:nth-child(2) > td:nth-child(2)').text().trim(), 'host2', 'check row 2')
|
||||
equal( el.find('tbody > tr:nth-child(2) > td:nth-child(3)').text().trim(), 'user2', 'check row 2')
|
||||
equal( el.find('tbody > tr:nth-child(2) > td:nth-child(4)').text().trim(), 'true', 'check row 2')
|
||||
equal( el.find('tbody > tr:nth-child(2) > td:nth-child(4)').text().trim(), 'ja', 'check row 2')
|
||||
equal( el.find('tbody > tr:nth-child(2) > td:nth-child(5)').text().trim(), '', 'check row 2')
|
||||
});
|
||||
|
||||
|
|
|
@ -68,6 +68,28 @@ class AAbUnitTest < TestCase
|
|||
]
|
||||
browser_single_test(tests)
|
||||
end
|
||||
def test_model_ui
|
||||
tests = [
|
||||
{
|
||||
:name => 'start',
|
||||
:instance => browser_instance,
|
||||
:url => browser_url + '/tests-model-ui',
|
||||
:action => [
|
||||
{
|
||||
:execute => 'wait',
|
||||
:value => 8,
|
||||
},
|
||||
{
|
||||
:execute => 'match',
|
||||
:css => '.result .failed',
|
||||
:value => '0',
|
||||
:match_result => true,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
browser_single_test(tests)
|
||||
end
|
||||
def test_form
|
||||
tests = [
|
||||
{
|
||||
|
|
188
test/browser/agent_organization_profile_test.rb
Normal file
188
test/browser/agent_organization_profile_test.rb
Normal file
|
@ -0,0 +1,188 @@
|
|||
# encoding: utf-8
|
||||
require 'browser_test_helper'
|
||||
|
||||
class AgentOrganizationProfileTest < TestCase
|
||||
def test_search_and_edit_verify_in_second
|
||||
message = 'comment 1 ' + rand(99999999999999999).to_s
|
||||
tests = [
|
||||
{
|
||||
:name => 'start',
|
||||
:instance1 => browser_instance,
|
||||
:instance2 => browser_instance,
|
||||
:instance1_username => 'master@example.com',
|
||||
:instance1_password => 'test',
|
||||
:instance2_username => 'agent1@example.com',
|
||||
:instance2_password => 'test',
|
||||
:url => browser_url,
|
||||
:action => [
|
||||
{
|
||||
:where => :instance1,
|
||||
:execute => 'close_all_tasks',
|
||||
},
|
||||
{
|
||||
:where => :instance2,
|
||||
:execute => 'close_all_tasks',
|
||||
},
|
||||
{
|
||||
:where => :instance1,
|
||||
:execute => 'search_organization',
|
||||
:term => 'Zammad Foundation',
|
||||
},
|
||||
{
|
||||
:where => :instance2,
|
||||
:execute => 'search_organization',
|
||||
:term => 'Zammad Foundation',
|
||||
},
|
||||
|
||||
# update note
|
||||
{
|
||||
:where => :instance1,
|
||||
:execute => 'set',
|
||||
:css => '.active [data-name="note"]',
|
||||
:value => message,
|
||||
},
|
||||
{
|
||||
:where => :instance1,
|
||||
:execute => 'click',
|
||||
:css => '.active .profile',
|
||||
},
|
||||
{
|
||||
:where => :instance1,
|
||||
:execute => 'wait',
|
||||
:value => 3,
|
||||
},
|
||||
|
||||
# verify
|
||||
{
|
||||
:where => :instance2,
|
||||
:execute => 'match',
|
||||
:css => '.active .profile-window',
|
||||
:value => message,
|
||||
:match_result => true,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
browser_double_test(tests)
|
||||
end
|
||||
|
||||
def test_search_and_edit_in_one
|
||||
message = '1 ' + rand(99999999).to_s
|
||||
tests = [
|
||||
{
|
||||
:name => 'search and edit',
|
||||
:action => [
|
||||
{
|
||||
:execute => 'close_all_tasks',
|
||||
},
|
||||
|
||||
# search and open org
|
||||
{
|
||||
:execute => 'search_organization',
|
||||
:term => 'Zammad Foundation',
|
||||
},
|
||||
{
|
||||
:execute => 'match',
|
||||
:css => '.active .profile-window',
|
||||
:value => 'note',
|
||||
:match_result => true,
|
||||
},
|
||||
{
|
||||
:execute => 'match',
|
||||
:css => '.active .profile-window',
|
||||
:value => 'member',
|
||||
:match_result => true,
|
||||
|
||||
},
|
||||
|
||||
# update note
|
||||
{
|
||||
:execute => 'set',
|
||||
:css => '.active [data-name="note"]',
|
||||
:value => 'some note 123'
|
||||
},
|
||||
{
|
||||
:execute => 'click',
|
||||
:css => '.active .profile',
|
||||
},
|
||||
{
|
||||
:execute => 'wait',
|
||||
:value => 1,
|
||||
},
|
||||
|
||||
# check and change note again in edit screen
|
||||
{
|
||||
:execute => 'click',
|
||||
:css => '.active .js-action .select-arrow',
|
||||
},
|
||||
{
|
||||
:execute => 'click',
|
||||
:css => '.active .js-action a[data-type="edit"]',
|
||||
},
|
||||
{
|
||||
:execute => 'wait',
|
||||
:value => 1,
|
||||
},
|
||||
{
|
||||
:execute => 'match',
|
||||
:css => '.active .modal',
|
||||
:value => 'note',
|
||||
:match_result => true,
|
||||
},
|
||||
{
|
||||
:execute => 'match',
|
||||
:css => '.active .modal',
|
||||
:value => 'some note 123',
|
||||
:match_result => true,
|
||||
},
|
||||
{
|
||||
:execute => 'set',
|
||||
:css => '.active .modal [data-name="note"]',
|
||||
:value => 'some note abc'
|
||||
},
|
||||
{
|
||||
:execute => 'click',
|
||||
:css => '.active .modal button.js-submit',
|
||||
},
|
||||
{
|
||||
:execute => 'wait',
|
||||
:value => 4,
|
||||
},
|
||||
{
|
||||
:execute => 'match',
|
||||
:css => '.active .profile-window',
|
||||
:value => 'some note abc',
|
||||
:match_result => true,
|
||||
},
|
||||
|
||||
# create new ticket
|
||||
{
|
||||
:execute => 'create_ticket',
|
||||
:group => 'Users',
|
||||
:subject => 'org profile check ' + message,
|
||||
:body => 'org profile check ' + message,
|
||||
},
|
||||
{
|
||||
:execute => 'wait',
|
||||
:value => 4,
|
||||
},
|
||||
|
||||
# switch to org tab, verify if ticket is shown
|
||||
{
|
||||
:execute => 'search_organization',
|
||||
:term => 'Zammad Foundation',
|
||||
},
|
||||
|
||||
{
|
||||
:execute => 'match',
|
||||
:css => '.active .profile-window',
|
||||
:value => 'org profile check ' + message,
|
||||
:match_result => true,
|
||||
},
|
||||
|
||||
],
|
||||
},
|
||||
]
|
||||
browser_signle_test_with_login(tests, { :username => 'master@example.com' })
|
||||
end
|
||||
end
|
188
test/browser/agent_user_profile_test.rb
Normal file
188
test/browser/agent_user_profile_test.rb
Normal file
|
@ -0,0 +1,188 @@
|
|||
# encoding: utf-8
|
||||
require 'browser_test_helper'
|
||||
|
||||
class AgentUserProfileTest < TestCase
|
||||
def test_search_and_edit_verify_in_second
|
||||
message = 'comment 1 ' + rand(99999999999999999).to_s
|
||||
tests = [
|
||||
{
|
||||
:name => 'start',
|
||||
:instance1 => browser_instance,
|
||||
:instance2 => browser_instance,
|
||||
:instance1_username => 'master@example.com',
|
||||
:instance1_password => 'test',
|
||||
:instance2_username => 'agent1@example.com',
|
||||
:instance2_password => 'test',
|
||||
:url => browser_url,
|
||||
:action => [
|
||||
{
|
||||
:where => :instance1,
|
||||
:execute => 'close_all_tasks',
|
||||
},
|
||||
{
|
||||
:where => :instance2,
|
||||
:execute => 'close_all_tasks',
|
||||
},
|
||||
{
|
||||
:where => :instance1,
|
||||
:execute => 'search_user',
|
||||
:term => 'Braun',
|
||||
},
|
||||
{
|
||||
:where => :instance2,
|
||||
:execute => 'search_user',
|
||||
:term => 'Braun',
|
||||
},
|
||||
|
||||
# update note
|
||||
{
|
||||
:where => :instance1,
|
||||
:execute => 'set',
|
||||
:css => '.active [data-name="note"]',
|
||||
:value => message,
|
||||
},
|
||||
{
|
||||
:where => :instance1,
|
||||
:execute => 'click',
|
||||
:css => '.active .profile',
|
||||
},
|
||||
{
|
||||
:where => :instance1,
|
||||
:execute => 'wait',
|
||||
:value => 3,
|
||||
},
|
||||
|
||||
# verify
|
||||
{
|
||||
:where => :instance2,
|
||||
:execute => 'match',
|
||||
:css => '.active .profile-window',
|
||||
:value => message,
|
||||
:match_result => true,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
browser_double_test(tests)
|
||||
end
|
||||
|
||||
def test_search_and_edit_in_one
|
||||
message = '1 ' + rand(99999999).to_s
|
||||
tests = [
|
||||
{
|
||||
:name => 'search and edit',
|
||||
:action => [
|
||||
{
|
||||
:execute => 'close_all_tasks',
|
||||
},
|
||||
|
||||
# search and open user
|
||||
{
|
||||
:execute => 'search_user',
|
||||
:term => 'Braun',
|
||||
},
|
||||
{
|
||||
:execute => 'match',
|
||||
:css => '.active .profile-window',
|
||||
:value => 'note',
|
||||
:match_result => true,
|
||||
},
|
||||
{
|
||||
:execute => 'match',
|
||||
:css => '.active .profile-window',
|
||||
:value => 'email',
|
||||
:match_result => true,
|
||||
|
||||
},
|
||||
|
||||
# update note
|
||||
{
|
||||
:execute => 'set',
|
||||
:css => '.active [data-name="note"]',
|
||||
:value => 'some note 123'
|
||||
},
|
||||
{
|
||||
:execute => 'click',
|
||||
:css => '.active .profile',
|
||||
},
|
||||
{
|
||||
:execute => 'wait',
|
||||
:value => 1,
|
||||
},
|
||||
|
||||
# check and change note again in edit screen
|
||||
{
|
||||
:execute => 'click',
|
||||
:css => '.active .js-action .select-arrow',
|
||||
},
|
||||
{
|
||||
:execute => 'click',
|
||||
:css => '.active .js-action a[data-type="edit"]',
|
||||
},
|
||||
{
|
||||
:execute => 'wait',
|
||||
:value => 1,
|
||||
},
|
||||
{
|
||||
:execute => 'match',
|
||||
:css => '.active .modal',
|
||||
:value => 'note',
|
||||
:match_result => true,
|
||||
},
|
||||
{
|
||||
:execute => 'match',
|
||||
:css => '.active .modal',
|
||||
:value => 'some note 123',
|
||||
:match_result => true,
|
||||
},
|
||||
{
|
||||
:execute => 'set',
|
||||
:css => '.active .modal [data-name="note"]',
|
||||
:value => 'some note abc'
|
||||
},
|
||||
{
|
||||
:execute => 'click',
|
||||
:css => '.active .modal button.js-submit',
|
||||
},
|
||||
{
|
||||
:execute => 'wait',
|
||||
:value => 4,
|
||||
},
|
||||
{
|
||||
:execute => 'match',
|
||||
:css => '.active .profile-window',
|
||||
:value => 'some note abc',
|
||||
:match_result => true,
|
||||
},
|
||||
|
||||
# create new ticket
|
||||
{
|
||||
:execute => 'create_ticket',
|
||||
:group => 'Users',
|
||||
:subject => 'user profile check ' + message,
|
||||
:body => 'user profile check ' + message,
|
||||
},
|
||||
{
|
||||
:execute => 'wait',
|
||||
:value => 4,
|
||||
},
|
||||
|
||||
# switch to org tab, verify if ticket is shown
|
||||
{
|
||||
:execute => 'search_user',
|
||||
:term => 'Braun',
|
||||
},
|
||||
|
||||
{
|
||||
:execute => 'match',
|
||||
:css => '.active .profile-window',
|
||||
:value => 'user profile check ' + message,
|
||||
:match_result => true,
|
||||
},
|
||||
|
||||
],
|
||||
},
|
||||
]
|
||||
browser_signle_test_with_login(tests, { :username => 'master@example.com' })
|
||||
end
|
||||
end
|
|
@ -525,6 +525,55 @@ class TestCase < Test::Unit::TestCase
|
|||
}
|
||||
assert( false, "(#{test[:name]}) ticket creation failed, can't get zoom url" )
|
||||
return
|
||||
elsif action[:execute] == 'search_user'
|
||||
element = instance.find_elements( { :css => '#global-search' } )[0]
|
||||
element.click
|
||||
element.clear
|
||||
if @stack
|
||||
action[:term].gsub! '###stack###', @stack
|
||||
end
|
||||
element.send_keys( action[:term] )
|
||||
sleep 3
|
||||
element = instance.find_element( { :partial_link_text => action[:term] } ).click
|
||||
name = instance.find_elements( { :css => '.active h1' } )[0].text
|
||||
if name !~ /#{action[:term]}/
|
||||
assert( false, "(#{test[:name]}) unable to search/find user #{action[:term]}!" )
|
||||
return
|
||||
end
|
||||
assert( true, "(#{test[:name]}) user #{action[:term]} found" )
|
||||
return
|
||||
elsif action[:execute] == 'search_organization'
|
||||
element = instance.find_elements( { :css => '#global-search' } )[0]
|
||||
element.click
|
||||
element.clear
|
||||
if @stack
|
||||
action[:term].gsub! '###stack###', @stack
|
||||
end
|
||||
element.send_keys( action[:term] )
|
||||
sleep 3
|
||||
instance.find_elements( { :css => '.search .empty-search' } )[0].click
|
||||
sleep 0.5
|
||||
text = instance.find_elements( { :css => '#global-search' } )[0].attribute('value')
|
||||
if !text
|
||||
assert( false, "(#{test[:name]}) #global-search is not empty!" )
|
||||
return
|
||||
end
|
||||
element = instance.find_elements( { :css => '#global-search' } )[0]
|
||||
element.click
|
||||
element.clear
|
||||
if @stack
|
||||
action[:term].gsub! '###stack###', @stack
|
||||
end
|
||||
element.send_keys( action[:term] )
|
||||
sleep 2
|
||||
element = instance.find_element( { :partial_link_text => action[:term] } ).click
|
||||
name = instance.find_elements( { :css => '.active h1' } )[0].text
|
||||
if name !~ /#{action[:term]}/
|
||||
assert( false, "(#{test[:name]}) unable to search/find org #{action[:term]}!" )
|
||||
return
|
||||
end
|
||||
assert( true, "(#{test[:name]}) org #{action[:term]} found" )
|
||||
return
|
||||
elsif action[:execute] == 'search_ticket'
|
||||
element = instance.find_elements( { :css => '#global-search' } )[0]
|
||||
element.click
|
||||
|
@ -544,7 +593,7 @@ class TestCase < Test::Unit::TestCase
|
|||
element.clear
|
||||
action[:number].gsub! '###stack###', @stack
|
||||
element.send_keys( action[:number] )
|
||||
sleep 3
|
||||
sleep 2
|
||||
element = instance.find_element( { :partial_link_text => action[:number] } ).click
|
||||
number = instance.find_elements( { :css => '.active .page-header .ticket-number' } )[0].text
|
||||
if number !~ /#{action[:number]}/
|
||||
|
@ -684,14 +733,14 @@ class TestCase < Test::Unit::TestCase
|
|||
match = false
|
||||
if action[:no_quote]
|
||||
#puts "aaaa #{text}/#{action[:value]}"
|
||||
if text =~ /#{action[:value]}/
|
||||
if text =~ /#{action[:value]}/i
|
||||
if $1
|
||||
@stack = $1
|
||||
end
|
||||
match = $1 || true
|
||||
end
|
||||
else
|
||||
if text =~ /#{Regexp.quote(action[:value])}/
|
||||
if text =~ /#{Regexp.quote(action[:value])}/i
|
||||
match = true
|
||||
end
|
||||
end
|
||||
|
|
56
test/unit/db_auto_increment_test.rb
Normal file
56
test/unit/db_auto_increment_test.rb
Normal file
|
@ -0,0 +1,56 @@
|
|||
# encoding: utf-8
|
||||
require 'test_helper'
|
||||
|
||||
class DbAutoIncrementTest < ActiveSupport::TestCase
|
||||
|
||||
test 'id overwrite' do
|
||||
|
||||
setting_backup = Setting.get('system_init_done')
|
||||
|
||||
Setting.set('system_init_done', false)
|
||||
|
||||
Ticket::StateType.create_if_not_exists( :id => 200, :name => 'unit test 1', :updated_by_id => 1, :created_by_id => 1 )
|
||||
state_type = Ticket::StateType.where( :name => 'unit test 1' ).first
|
||||
assert_equal( Ticket::StateType.to_s, state_type.class.to_s )
|
||||
assert_equal( 'unit test 1', state_type.name )
|
||||
|
||||
Ticket::StateType.create_if_not_exists( :id => 200, :name => 'unit test 1 _ should not be created', :updated_by_id => 1, :created_by_id => 1 )
|
||||
state_type = Ticket::StateType.where( :id => 200 ).first
|
||||
assert_equal( Ticket::StateType.to_s, state_type.class.to_s )
|
||||
assert_equal( 'unit test 1', state_type.name )
|
||||
|
||||
Ticket::StateType.create_or_update( :id => 200, :name => 'unit test 1 _ should be updated', :updated_by_id => 1, :created_by_id => 1 )
|
||||
state_type = Ticket::StateType.where( :name => 'unit test 1 _ should be updated' ).first
|
||||
assert_equal( Ticket::StateType.to_s, state_type.class.to_s )
|
||||
assert_equal( 'unit test 1 _ should be updated', state_type.name )
|
||||
|
||||
state_type = Ticket::StateType.where( :id => 200 ).first
|
||||
assert_equal( Ticket::StateType.to_s, state_type.class.to_s )
|
||||
assert_equal( 'unit test 1 _ should be updated', state_type.name )
|
||||
|
||||
|
||||
Ticket::State.create_if_not_exists( :id => 210, :name => 'unit test 1', :state_type_id => Ticket::StateType.where(:name => 'unit test 1 _ should be updated').first.id, :updated_by_id => 1, :created_by_id => 1 )
|
||||
state = Ticket::State.where( :name => 'unit test 1' ).first
|
||||
assert_equal( Ticket::State.to_s, state.class.to_s )
|
||||
assert_equal( 'unit test 1', state.name )
|
||||
|
||||
Ticket::State.create_if_not_exists( :id => 210, :name => 'unit test 1 _ should not be created', :state_type_id => Ticket::StateType.where(:name => 'unit test 1 _ should be updated').first.id, :updated_by_id => 1, :created_by_id => 1 )
|
||||
state = Ticket::State.where( :id => 210 ).first
|
||||
assert_equal( Ticket::State.to_s, state.class.to_s )
|
||||
assert_equal( 'unit test 1', state.name )
|
||||
|
||||
Ticket::State.create_or_update( :id => 210, :name => 'unit test 1 _ should be updated', :state_type_id => Ticket::StateType.where(:name => 'unit test 1 _ should be updated').first.id, :updated_by_id => 1, :created_by_id => 1 )
|
||||
state = Ticket::State.where( :name => 'unit test 1 _ should be updated' ).first
|
||||
assert_equal( Ticket::State.to_s, state.class.to_s )
|
||||
assert_equal( 'unit test 1 _ should be updated', state.name )
|
||||
|
||||
state = Ticket::State.where( :id => 210 ).first
|
||||
assert_equal( Ticket::State.to_s, state.class.to_s )
|
||||
assert_equal( 'unit test 1 _ should be updated', state.name )
|
||||
|
||||
|
||||
|
||||
Setting.set('system_init_done', setting_backup)
|
||||
|
||||
end
|
||||
end
|
|
@ -126,6 +126,21 @@ class NotificationFactoryTest < ActiveSupport::TestCase
|
|||
:string => '\#{puts `ls`}',
|
||||
:result => '\#{puts `ls`} (not allowed)',
|
||||
},
|
||||
{
|
||||
:locale => 'de',
|
||||
:string => 'test i18n(new)',
|
||||
:result => 'test neu',
|
||||
},
|
||||
{
|
||||
:locale => 'de',
|
||||
:string => 'test i18n()',
|
||||
:result => 'test ',
|
||||
},
|
||||
{
|
||||
:locale => 'de',
|
||||
:string => 'test i18n(new) i18n(open)',
|
||||
:result => 'test neu offen',
|
||||
},
|
||||
]
|
||||
tests.each { |test|
|
||||
result = NotificationFactory.build(
|
||||
|
|
|
@ -288,6 +288,20 @@ class TicketNotificationTest < ActiveSupport::TestCase
|
|||
assert_equal( 0, notification_check(ticket3, agent2), ticket3.id )
|
||||
|
||||
|
||||
# update article / not notification should be sent
|
||||
article_inbound.internal = true
|
||||
article_inbound.save
|
||||
|
||||
# execute ticket events
|
||||
Observer::Ticket::Notification.transaction
|
||||
#puts Delayed::Job.all.inspect
|
||||
Delayed::Worker.new.work_off
|
||||
|
||||
# verify notifications not to agent1 and not to agent2
|
||||
assert_equal( 2, notification_check(ticket3, agent1), ticket3.id )
|
||||
assert_equal( 0, notification_check(ticket3, agent2), ticket3.id )
|
||||
|
||||
|
||||
delete = ticket1.destroy
|
||||
assert( delete, "ticket1 destroy" )
|
||||
|
||||
|
@ -392,16 +406,22 @@ class TicketNotificationTest < ActiveSupport::TestCase
|
|||
:article_id => article.id,
|
||||
:type => 'update',
|
||||
:changes => {
|
||||
:priority_id => [1, 2],
|
||||
'priority_id' => [1, 2],
|
||||
'pending_time' => [nil, Time.parse("2015-01-11 23:33:47 UTC")],
|
||||
},
|
||||
)
|
||||
|
||||
# check changed attributes
|
||||
human_changes = bg.human_changes(agent1,ticket1)
|
||||
assert( human_changes['Priority'], 'Check if attributes translated based on ObjectManager::Attribute' )
|
||||
assert( human_changes['Pending till'], 'Check if attributes translated based on ObjectManager::Attribute' )
|
||||
assert_equal( 'i18n(1 low)', human_changes['Priority'][0] )
|
||||
assert_equal( 'i18n(2 normal)', human_changes['Priority'][1] )
|
||||
assert_equal( 'i18n()', human_changes['Pending till'][0] )
|
||||
assert_equal( 'i18n(2015-01-11 23:33:47 UTC)', human_changes['Pending till'][1] )
|
||||
assert_not( human_changes['priority_id'] )
|
||||
assert_not( human_changes['pending_time'] )
|
||||
assert_not( human_changes['pending_till'] )
|
||||
|
||||
# en template
|
||||
template = bg.template_update(agent2, ticket1, article, human_changes)
|
||||
|
@ -410,6 +430,8 @@ class TicketNotificationTest < ActiveSupport::TestCase
|
|||
assert_match( /Priority/, template[:body] )
|
||||
assert_match( /1 low/, template[:body] )
|
||||
assert_match( /2 normal/, template[:body] )
|
||||
assert_match( /Pending till/, template[:body] )
|
||||
assert_match( /2015-01-11 23:33:47 UTC/, template[:body] )
|
||||
assert_match( /updated/i, template[:subject] )
|
||||
|
||||
# en notification
|
||||
|
@ -435,7 +457,11 @@ class TicketNotificationTest < ActiveSupport::TestCase
|
|||
assert_match( /Priority/, body )
|
||||
assert_match( /1 low/, body )
|
||||
assert_match( /2 normal/, body )
|
||||
assert_match( /Pending till/, body )
|
||||
assert_match( /2015-01-11 23:33:47 UTC/, body )
|
||||
assert_match( /update/, body )
|
||||
assert_no_match( /pending_till/, body )
|
||||
assert_no_match( /i18n/, body )
|
||||
|
||||
# de template
|
||||
template = bg.template_update(agent1, ticket1, article, human_changes)
|
||||
|
@ -444,6 +470,8 @@ class TicketNotificationTest < ActiveSupport::TestCase
|
|||
assert_match( /Priority/, template[:body] )
|
||||
assert_match( /1 low/, template[:body] )
|
||||
assert_match( /2 normal/, template[:body] )
|
||||
assert_match( /Pending till/, template[:body] )
|
||||
assert_match( /2015-01-11 23:33:47 UTC/, template[:body] )
|
||||
assert_match( /aktualis/, template[:subject] )
|
||||
|
||||
# de notification
|
||||
|
@ -470,7 +498,45 @@ class TicketNotificationTest < ActiveSupport::TestCase
|
|||
assert_match( /Priorität/, body )
|
||||
assert_match( /1 niedrig/, body )
|
||||
assert_match( /2 normal/, body )
|
||||
assert_match( /Warten/, body )
|
||||
assert_match( /2015-01-11 23:33:47 UTC/, body )
|
||||
assert_match( /aktualis/, body )
|
||||
assert_no_match( /pending_till/, body )
|
||||
assert_no_match( /i18n/, body )
|
||||
|
||||
bg = Observer::Ticket::Notification::BackgroundJob.new(
|
||||
:ticket_id => ticket1.id,
|
||||
:article_id => article.id,
|
||||
:type => 'update',
|
||||
:changes => {
|
||||
:title => ['some notification template test 1', 'some notification template test 1 #2'],
|
||||
:priority_id => [2, 3],
|
||||
},
|
||||
)
|
||||
|
||||
puts "hc #{human_changes.inspect}"
|
||||
# check changed attributes
|
||||
human_changes = bg.human_changes(agent1,ticket1)
|
||||
assert( human_changes['Title'], 'Check if attributes translated based on ObjectManager::Attribute' )
|
||||
assert( human_changes['Priority'], 'Check if attributes translated based on ObjectManager::Attribute' )
|
||||
assert_equal( 'i18n(2 normal)', human_changes['Priority'][0] )
|
||||
assert_equal( 'i18n(3 high)', human_changes['Priority'][1] )
|
||||
assert_equal( 'some notification template test 1', human_changes['Title'][0] )
|
||||
assert_equal( 'some notification template test 1 #2', human_changes['Title'][1] )
|
||||
assert_not( human_changes['priority_id'] )
|
||||
assert_not( human_changes['pending_time'] )
|
||||
assert_not( human_changes['pending_till'] )
|
||||
|
||||
human_changes = bg.human_changes(agent2,ticket1)
|
||||
puts "hc2 #{human_changes.inspect}"
|
||||
|
||||
template = bg.template_update(agent1, ticket1, article, human_changes)
|
||||
puts "t1 #{template.inspect}"
|
||||
|
||||
template = bg.template_update(agent2, ticket1, article, human_changes)
|
||||
puts "t2 #{template.inspect}"
|
||||
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
|
161
test/unit/ticket_ref_object_update_test.rb
Normal file
161
test/unit/ticket_ref_object_update_test.rb
Normal file
|
@ -0,0 +1,161 @@
|
|||
# encoding: utf-8
|
||||
require 'test_helper'
|
||||
|
||||
class TicketRefObjectTouchTest < ActiveSupport::TestCase
|
||||
|
||||
# create base
|
||||
groups = Group.where( :name => 'Users' )
|
||||
roles = Role.where( :name => 'Agent' )
|
||||
agent1 = User.create_or_update(
|
||||
:login => 'ticket-ref-object-update-agent1@example.com',
|
||||
:firstname => 'Notification',
|
||||
:lastname => 'Agent1',
|
||||
:email => 'ticket-ref-object-update-agent1@example.com',
|
||||
:password => 'agentpw',
|
||||
:active => true,
|
||||
:roles => roles,
|
||||
:groups => groups,
|
||||
:updated_at => '2015-02-05 16:37:00',
|
||||
:updated_by_id => 1,
|
||||
:created_by_id => 1,
|
||||
)
|
||||
roles = Role.where( :name => 'Customer' )
|
||||
organization1 = Organization.create_if_not_exists(
|
||||
:name => 'Ref Object Update Org',
|
||||
:updated_at => '2015-02-05 16:37:00',
|
||||
:updated_by_id => 1,
|
||||
:created_by_id => 1,
|
||||
)
|
||||
customer1 = User.create_or_update(
|
||||
:login => 'ticket-ref-object-update-customer1@example.com',
|
||||
:firstname => 'Notification',
|
||||
:lastname => 'Agent1',
|
||||
:email => 'ticket-ref-object-update-customer1@example.com',
|
||||
:password => 'customerpw',
|
||||
:active => true,
|
||||
:organization_id => organization1.id,
|
||||
:roles => roles,
|
||||
:updated_at => '2015-02-05 16:37:00',
|
||||
:updated_by_id => 1,
|
||||
:created_by_id => 1,
|
||||
)
|
||||
customer2 = User.create_or_update(
|
||||
:login => 'ticket-ref-object-update-customer2@example.com',
|
||||
:firstname => 'Notification',
|
||||
:lastname => 'Agent2',
|
||||
:email => 'ticket-ref-object-update-customer2@example.com',
|
||||
:password => 'customerpw',
|
||||
:active => true,
|
||||
:organization_id => nil,
|
||||
:roles => roles,
|
||||
:updated_at => '2015-02-05 16:37:00',
|
||||
:updated_by_id => 1,
|
||||
:created_by_id => 1,
|
||||
)
|
||||
|
||||
test 'a - check if customer and organization has been updated' do
|
||||
|
||||
ticket = Ticket.create(
|
||||
:title => "some title1\n äöüß",
|
||||
:group => Group.lookup( :name => 'Users'),
|
||||
:customer_id => customer1.id,
|
||||
:owner_id => agent1.id,
|
||||
:state => Ticket::State.lookup( :name => 'new' ),
|
||||
:priority => Ticket::Priority.lookup( :name => '2 normal' ),
|
||||
:updated_by_id => 1,
|
||||
:created_by_id => 1,
|
||||
)
|
||||
assert( ticket, "ticket created" )
|
||||
assert_equal( ticket.customer.id, customer1.id )
|
||||
assert_equal( ticket.organization.id, organization1.id )
|
||||
|
||||
# check if customer and organization has been touched
|
||||
customer1 = User.find(customer1.id)
|
||||
if customer1.updated_at > 2.second.ago
|
||||
assert( true, "customer1.updated_at has been updated" )
|
||||
else
|
||||
assert( false, "customer1.updated_at has not been updated" )
|
||||
end
|
||||
|
||||
organization1 = Organization.find(organization1.id)
|
||||
if organization1.updated_at > 2.second.ago
|
||||
assert( true, "organization1.updated_at has been updated" )
|
||||
else
|
||||
assert( false, "organization1.updated_at has not been updated" )
|
||||
end
|
||||
|
||||
sleep 4
|
||||
|
||||
delete = ticket.destroy
|
||||
assert( delete, "ticket destroy" )
|
||||
|
||||
# check if customer and organization has been touched
|
||||
customer1 = User.find(customer1.id)
|
||||
if customer1.updated_at > 2.second.ago
|
||||
assert( true, "customer1.updated_at has been updated" )
|
||||
else
|
||||
assert( false, "customer1.updated_at has not been updated" )
|
||||
end
|
||||
|
||||
organization1 = Organization.find(organization1.id)
|
||||
if organization1.updated_at > 2.second.ago
|
||||
assert( true, "organization1.updated_at has been updated" )
|
||||
else
|
||||
assert( false, "organization1.updated_at has not been updated" )
|
||||
end
|
||||
end
|
||||
|
||||
test 'b - check if customer (not organization) has been updated' do
|
||||
|
||||
sleep 3
|
||||
ticket = Ticket.create(
|
||||
:title => "some title2\n äöüß",
|
||||
:group => Group.lookup( :name => 'Users'),
|
||||
:customer_id => customer2.id,
|
||||
:owner_id => agent1.id,
|
||||
:state => Ticket::State.lookup( :name => 'new' ),
|
||||
:priority => Ticket::Priority.lookup( :name => '2 normal' ),
|
||||
:updated_by_id => 1,
|
||||
:created_by_id => 1,
|
||||
)
|
||||
assert( ticket, "ticket created" )
|
||||
assert_equal( ticket.customer.id, customer2.id )
|
||||
assert_equal( ticket.organization, nil )
|
||||
|
||||
# check if customer and organization has been touched
|
||||
customer2 = User.find(customer2.id)
|
||||
if customer2.updated_at > 2.second.ago
|
||||
assert( true, "customer2.updated_at has been updated" )
|
||||
else
|
||||
assert( false, "customer2.updated_at has not been updated" )
|
||||
end
|
||||
|
||||
organization1 = Organization.find(organization1.id)
|
||||
if organization1.updated_at > 2.second.ago
|
||||
assert( false, "organization1.updated_at has been updated" )
|
||||
else
|
||||
assert( true, "organization1.updated_at has not been updated" )
|
||||
end
|
||||
|
||||
sleep 3
|
||||
|
||||
delete = ticket.destroy
|
||||
assert( delete, "ticket destroy" )
|
||||
|
||||
# check if customer and organization has been touched
|
||||
customer2 = User.find(customer2.id)
|
||||
if customer2.updated_at > 2.second.ago
|
||||
assert( true, "customer2.updated_at has been updated" )
|
||||
else
|
||||
assert( false, "customer2.updated_at has not been updated" )
|
||||
end
|
||||
|
||||
organization1 = Organization.find(organization1.id)
|
||||
if organization1.updated_at > 2.second.ago
|
||||
assert( false, "organization1.updated_at has been updated" )
|
||||
else
|
||||
assert( true, "organization1.updated_at has not been updated" )
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -101,6 +101,25 @@ class TicketTest < ActiveSupport::TestCase
|
|||
assert( ticket.close_time, 'ticket.close_time verify - state update' )
|
||||
|
||||
|
||||
# set pending time
|
||||
ticket.state_id = Ticket::State.where(:name => 'pending reminder').first.id
|
||||
ticket.pending_time = Time.parse("1977-10-27 22:00:00 +0000")
|
||||
ticket.save
|
||||
|
||||
ticket = Ticket.find(ticket.id)
|
||||
assert_equal( ticket.state.name, 'pending reminder', 'state verify' )
|
||||
assert_equal( ticket.pending_time, Time.parse("1977-10-27 22:00:00 +0000"), 'pending_time verify' )
|
||||
|
||||
|
||||
# reset pending state, should also reset pending time
|
||||
ticket.state_id = Ticket::State.where(:name => 'closed').first.id
|
||||
ticket.save
|
||||
|
||||
ticket = Ticket.find(ticket.id)
|
||||
assert_equal( ticket.state.name, 'closed', 'state verify' )
|
||||
assert_equal( ticket.pending_time, nil )
|
||||
|
||||
|
||||
delete = ticket.destroy
|
||||
assert( delete, "ticket destroy" )
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue