Added support for signature and different sender addresses.

This commit is contained in:
Martin Edenhofer 2012-10-01 20:41:08 +02:00
parent 6b9af49fa8
commit 7d23efda79
21 changed files with 643 additions and 132 deletions

View file

@ -1,6 +1,6 @@
source 'http://rubygems.org'
gem 'rails', '3.2.3'
gem 'rails', '3.2.8'
# Bundle edge Rails instead:
#gem 'rails', :git => 'git://github.com/rails/rails.git'
@ -47,7 +47,7 @@ gem 'simple-rss'
# gem 'therubyracer'
# e. g. for mysql you need to load mysql
# gem 'mysql2'
gem 'mysql2'
# Use unicorn as the web server
# gem 'unicorn'

View file

@ -382,19 +382,11 @@ class App.ControllerForm extends App.Controller
# if active or if active doesn't exist
if item.active || !( 'active' of item )
name = '???'
if item.name
name = item.name
else if item.firstname
name = item.firstname
if item.lastname
if name
name = name + ' '
name = name + item.lastname
name_new = name
name_new = '?'
if item.displayName
name_new = item.displayName()
if attribute.translate
name_new = Ti(name)
name_new = Ti(name_new)
attribute.options.push {
name: name_new,
value: item.id,

View file

@ -21,12 +21,14 @@ class App.ChannelEmail extends App.ControllerTabs
controller: App.ChannelEmailOutbound,
},
{
name: 'Sigantures',
target: 'c-signature',
name: 'Sigantures',
target: 'c-signature',
controller: App.ChannelEmailSignature,
},
{
name: 'Adresses',
target: 'c-address',
name: 'Adresses',
target: 'c-address',
controller: App.ChannelEmailAddress,
},
{
name: 'Filter',
@ -42,6 +44,183 @@ class App.ChannelEmail extends App.ControllerTabs
@render()
class App.ChannelEmailAddress extends App.Controller
events:
'click [data-type=new]': 'new'
'click [data-type=edit]': 'edit'
constructor: ->
super
App.EmailAddress.bind 'refresh change', @render
App.EmailAddress.fetch()
render: =>
data = App.EmailAddress.all()
html = $('<div></div>')
table = @table(
model: App.EmailAddress,
objects: data,
)
html.append( table )
html.append( '<a data-type="new" class="btn">' + T('New') + '</a>' )
@html html
new: (e) =>
e.preventDefault()
new App.ChannelEmailAddressEdit()
edit: (e) =>
e.preventDefault()
item = $(e.target).item( App.EmailAddress )
new App.ChannelEmailAddressEdit( object: item )
class App.ChannelEmailAddressEdit extends App.ControllerModal
constructor: ->
super
@render(@object)
render: (data = {}) ->
if @object
@html App.view('generic/admin/edit')(
head: 'Email-Address'
)
@form = new App.ControllerForm(
el: @el.find('#object_edit'),
model: App.EmailAddress,
params: @object,
autofocus: true,
)
else
@html App.view('generic/admin/new')(
head: 'Email-Address'
)
@form = new App.ControllerForm(
el: @el.find('#object_new'),
model: App.EmailAddress,
autofocus: true,
)
@modalShow()
submit: (e) =>
e.preventDefault()
# get params
params = @formParam(e.target)
object = @object || new App.EmailAddress
object.load(params)
# validate form
errors = @form.validate( params )
# show errors in form
if errors
@log 'error new', errors
@formValidate( form: e.target, errors: errors )
return false
# save object
object.save(
success: =>
@modalHide()
error: =>
@log 'errors'
@modalHide()
)
class App.ChannelEmailSignature extends App.Controller
events:
'click [data-type=new]': 'new'
'click [data-type=edit]': 'edit'
constructor: ->
super
App.Signature.bind 'refresh change', @render
App.Signature.fetch()
render: =>
data = App.Signature.all()
html = $('<div></div>')
table = @table(
model: App.Signature,
objects: data,
)
html.append( table )
html.append( '<a data-type="new" class="btn">' + T('New') + '</a>' )
@html html
new: (e) =>
e.preventDefault()
new App.ChannelEmailSignatureEdit()
edit: (e) =>
e.preventDefault()
item = $(e.target).item( App.Signature )
@log '123', item, $(e.target)
new App.ChannelEmailSignatureEdit( object: item )
class App.ChannelEmailSignatureEdit extends App.ControllerModal
constructor: ->
super
@render(@object)
render: (data = {}) ->
if @object
@html App.view('generic/admin/edit')(
head: 'Signature'
)
@form = new App.ControllerForm(
el: @el.find('#object_edit'),
model: App.Signature,
params: @object,
autofocus: true,
)
else
@html App.view('generic/admin/new')(
head: 'Signature'
)
@form = new App.ControllerForm(
el: @el.find('#object_new'),
model: App.Signature,
autofocus: true,
)
@modalShow()
submit: (e) =>
e.preventDefault()
# get params
params = @formParam(e.target)
object = @object || new App.Signature
object.load(params)
# validate form
errors = @form.validate( params )
# show errors in form
if errors
@log 'error new', errors
@formValidate( form: e.target, errors: errors )
return false
# save object
object.save(
success: =>
@modalHide()
error: =>
@log 'errors'
@modalHide()
)
class App.ChannelEmailInbound extends App.Controller
events:
'click [data-type=new]': 'new'
@ -65,13 +244,14 @@ class App.ChannelEmailInbound extends App.Controller
data.push channel
table = @table(
header: ['Host', 'User', 'Adapter', 'Active'],
overview: ['host', 'user', 'adapter', 'active'],
model: App.Channel,
objects: data,
)
html.append( table )
html.append( '<a data-type="new" class="btn">new account</a>' )
html.append( '<a data-type="new" class="btn">' + T('New') + '</a>' )
@html html
new: (e) =>
@ -100,14 +280,24 @@ class App.ChannelEmailInboundEdit extends App.ControllerModal
{ name: 'group_id', display: 'Group', tag: 'select', multiple: false, null: false, filter: @edit_form, nulloption: false, relation: 'Group', class: 'span4', default: data['group_id'] },
{ name: 'active', display: 'Active', tag: 'select', multiple: false, null: false, options: { true: 'yes', false: 'no' } , class: 'span4', default: data['active'] },
]
@html App.view('generic/admin/new')(
head: 'New Channel'
)
@form = new App.ControllerForm(
el: @el.find('#object_new'),
model: { configure_attributes: configure_attributes, className: '' },
autofocus: true,
)
if @object
@html App.view('generic/admin/edit')(
head: 'Channel'
)
@form = new App.ControllerForm(
el: @el.find('#object_edit'),
model: { configure_attributes: configure_attributes, className: '' },
autofocus: true,
)
else
@html App.view('generic/admin/new')(
head: 'Channel'
)
@form = new App.ControllerForm(
el: @el.find('#object_new'),
model: { configure_attributes: configure_attributes, className: '' },
autofocus: true,
)
@modalShow()
submit: (e) =>
@ -179,15 +369,17 @@ class App.ChannelEmailOutbound extends App.Controller
adapter_used = channel.adapter
channel_used = channel
if adapter_used is 'Sendmail'
configure_attributes = [
{ name: 'adapter', display: 'Send Mails via', tag: 'select', multiple: false, null: false, options: adapters , class: 'span4', default: adapter_used },
]
@form = new App.ControllerForm(
el: @el.find('#form-email-adapter'),
model: { configure_attributes: configure_attributes, className: '' },
autofocus: true,
)
configure_attributes = [
{ name: 'adapter', display: 'Send Mails via', tag: 'select', multiple: false, null: false, options: adapters , class: 'span4', default: adapter_used },
]
new App.ControllerForm(
el: @el.find('#form-email-adapter'),
model: { configure_attributes: configure_attributes, className: '' },
autofocus: true,
)
# if adapter_used is 'Sendmail'
# # some form
if adapter_used is 'SMTP'
configure_attributes = [

View file

@ -78,7 +78,7 @@ class _Singleton
if _.isArray( params.data )
for object in params.data
console.log( 'load ARRAY', object)
# console.log( 'load ARRAY', object)
App[params.type].refresh( object, options: { clear: true } )
# remember in store if not already requested from local storage
@ -88,7 +88,7 @@ class _Singleton
# if _.isObject( params.data )
for key, object of params.data
console.log( 'load OB', object)
# console.log( 'load OB', object)
App[params.type].refresh( object, options: { clear: true } )
# remember in store if not already requested from local storage
@ -100,7 +100,7 @@ class _Singleton
console.log( 'find', type, id )
# if App[type].exists( id ) && !callback
if App[type].exists( id )
console.log( 'find exists', type, id )
# console.log( 'find exists', type, id )
data = App[type].find( id )
if callback
callback( data )

View file

@ -1,5 +1,18 @@
class App.Model extends Spine.Model
displayName: ->
return @name if @name
if @realname
return "#{@realname} <#{@email}>"
if @firstname
name = @firstname
if @lastname
if name
name = name + ' '
name = name + @lastname
return name
return '???'
@validate: ( data = {} ) ->
return if !data['model'].configure_attributes

View file

@ -0,0 +1,15 @@
class App.EmailAddress extends App.Model
@configure 'EmailAddress', 'realname', 'email', 'note', 'active'
@extend Spine.Model.Ajax
@url: '/api/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' },
]
@configure_overview = [
'realname', 'email'
]

View file

@ -1,13 +1,15 @@
class App.Group extends App.Model
@configure 'Group', 'name', 'assignment_timeout', 'follow_up_possible', 'follow_up_assignment', 'note', 'active'
@configure 'Group', 'name', 'assignment_timeout', 'follow_up_possible', 'follow_up_assignment', 'email_address_id', 'signature_id', 'note', 'active'
@extend Spine.Model.Ajax
@url: '/api/groups'
@configure_attributes = [
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, 'null': false, 'class': 'span4' },
{ name: 'assignment_timeout', display: 'Assignment Timout', tag: 'input', note: 'Assignment timout in minutes if assigned agent is not working on it. Ticket will be shown as unassigend.', type: 'text', limit: 100, 'null': false, 'class': 'span4' },
{ name: 'assignment_timeout', display: 'Assignment Timout', tag: 'input', note: 'Assignment timout 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 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: { yes: 'yes', no: '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' },

View file

@ -1,5 +1,5 @@
<form class="form-horizontal" id="mail_adapter">
<div id="form-email-adapter"></div>
<div id="form-email-adapter-settings"></div>
<button data-type="" type="submit" class="btn">submit</botton>
<button data-type="" type="submit" class="btn"><%- T('Submit') %></botton>
</form>

View file

@ -0,0 +1,144 @@
class EmailAddressesController < ApplicationController
before_filter :authentication_check
=begin
Format:
JSON
Example:
{
"id":1,
"realname":"some realname",
"email":"system@example.com",
"updated_at":"2012-09-14T17:51:53Z",
"created_at":"2012-09-14T17:51:53Z",
"updated_by_id":2,
"created_by_id":2,
}
=end
=begin
Resource:
GET /api/email_address.json
Response:
[
{
"id": 1,
"realname":"some realname1",
...
},
{
"id": 2,
"realname":"some realname2",
...
}
]
Test:
curl http://localhost/api/email_address.json -v -u #{login}:#{password}
=end
def index
model_index_render(EmailAddress, params)
end
=begin
Resource:
GET /api/email_address/#{id}.json
Response:
{
"id": 1,
"name": "name_1",
...
}
Test:
curl http://localhost/api/email_address/#{id}.json -v -u #{login}:#{password}
=end
def show
model_show_render(EmailAddress, params)
end
=begin
Resource:
POST /api/email_address.json
Payload:
{
"realname":"some realname",
"email":"system@example.com",
"note": "",
"active":true,
}
Response:
{
"id": 1,
"realname":"some realname",
"email":"system@example.com",
...
}
Test:
curl http://localhost/api/email_address.json -v -u #{login}:#{password} -H "Content-Type: application/json" -X POST -d '{"name": "some_name","active": true, "note": "some note"}'
=end
def create
model_create_render(EmailAddress, params)
end
=begin
Resource:
PUT /api/email_address/{id}.json
Payload:
{
"realname":"some realname",
"email":"system@example.com",
"note": "",
"active":true,
}
Response:
{
"id": 1,
"realname":"some realname",
"email":"system@example.com",
...
}
Test:
curl http://localhost/api/email_address.json -v -u #{login}:#{password} -H "Content-Type: application/json" -X PUT -d '{"name": "some_name","active": true, "note": "some note"}'
=end
def update
model_update_render(EmailAddress, params)
end
=begin
Resource:
Response:
Test:
=end
def destroy
model_destory_render(EmailAddress, params)
end
end

View file

@ -1,12 +1,19 @@
module ExtraCollection
def add(collections)
# all ticket stuff
collections['TicketStateType'] = Ticket::StateType.all
collections['TicketState'] = Ticket::State.all
collections['TicketPriority'] = Ticket::Priority.all
collections['TicketArticleType'] = Ticket::Article::Type.all
collections['TicketArticleSender'] = Ticket::Article::Sender.all
# all signatures
collections['Signature'] = Signature.all
# all email addresses
collections['EmailAddress'] = EmailAddress.all
end
module_function :add
end

View file

@ -0,0 +1,141 @@
class SignaturesController < ApplicationController
before_filter :authentication_check
=begin
Format:
JSON
Example:
{
"id":1,
"name":"some signature name",
"body":"some signature body",
"note":"some note",
"updated_at":"2012-09-14T17:51:53Z",
"created_at":"2012-09-14T17:51:53Z",
"updated_by_id":2,
"created_by_id":2,
}
=end
=begin
Resource:
GET /api/signatures.json
Response:
[
{
"id": 1,
"name": "some_name1",
...
},
{
"id": 2,
"name": "some_name2",
...
}
]
Test:
curl http://localhost/api/signatures.json -v -u #{login}:#{password}
=end
def index
model_index_render(Signature, params)
end
=begin
Resource:
GET /api/signatures/#{id}.json
Response:
{
"id": 1,
"name": "name_1",
...
}
Test:
curl http://localhost/api/signatures/#{id}.json -v -u #{login}:#{password}
=end
def show
model_show_render(Signature, params)
end
=begin
Resource:
POST /api/signatures.json
Payload:
{
"name": "some name",
"note": "",
"active":true,
}
Response:
{
"id": 1,
"name": "some_name",
...
}
Test:
curl http://localhost/api/signatures.json -v -u #{login}:#{password} -H "Content-Type: application/json" -X POST -d '{"name": "some_name","active": true, "note": "some note"}'
=end
def create
model_create_render(Signature, params)
end
=begin
Resource:
PUT /api/signatures/{id}.json
Payload:
{
"name": "some name",
"note": "",
"active":true,
}
Response:
{
"id": 1,
"name": "some_name",
...
}
Test:
curl http://localhost/api/signatures.json -v -u #{login}:#{password} -H "Content-Type: application/json" -X PUT -d '{"name": "some_name","active": true, "note": "some note"}'
=end
def update
model_update_render(Signature, params)
end
=begin
Resource:
Response:
Test:
=end
def destroy
model_destory_render(Signature, params)
end
end

View file

@ -0,0 +1,6 @@
class EmailAddress < ApplicationModel
has_many :groups, :after_add => :cache_update, :after_remove => :cache_update
after_create :cache_delete
after_update :cache_delete
after_destroy :cache_delete
end

View file

@ -1,5 +1,7 @@
class Group < ApplicationModel
has_and_belongs_to_many :users, :after_add => :cache_update, :after_remove => :cache_update
belongs_to :email_address
belongs_to :signature
after_create :cache_delete
after_update :cache_delete
after_destroy :cache_delete

6
app/models/signature.rb Normal file
View file

@ -0,0 +1,6 @@
class Signature < ApplicationModel
has_many :groups, :after_add => :cache_update, :after_remove => :cache_update
after_create :cache_delete
after_update :cache_delete
after_destroy :cache_delete
end

View file

@ -29,27 +29,28 @@ class Ticket::Article < ApplicationModel
# set email attributes
if type['name'] == 'email'
# set subject if empty
if !self.subject || self.subject == ''
self.subject = ticket.title
end
# clean subject
self.subject = ticket.subject_clean(self.subject)
# generate message id
fqdn = Setting.get('fqdn')
self.message_id = '<' + DateTime.current.to_s(:number) + '.' + self.ticket_id.to_s + '.' + rand(999999).to_s() + '@' + fqdn + '>'
# set sender
email_address = ticket.group.email_address
system_sender = "#{email_address.realname} <#{email_address.email}>"
if Setting.get('ticket_define_email_from') == 'AgentNameSystemAddressName'
seperator = Setting.get('ticket_define_email_from_seperator')
sender = User.find(self.created_by_id)
system_sender = Setting.get('system_sender')
self.from = "#{sender.firstname} #{sender.lastname} #{seperator} #{system_sender}"
else
self.from = Setting.get('system_sender')
self.from = system_sender
end
end
end
@ -76,7 +77,8 @@ class Ticket::Article < ApplicationModel
# if sender is customer, do not communication
sender = Ticket::Article::Sender.where( :id => self.ticket_article_sender_id ).first
return 1 if sender == nil || sender['name'] == 'Customer'
return 1 if sender == nil
return 1 if sender['name'] == 'Customer'
type = Ticket::Article::Type.where( :id => self.ticket_article_type_id ).first
ticket = Ticket.find(self.ticket_id)

View file

@ -3,68 +3,11 @@ Zammad::Application.routes.draw do
# app init
match '/init', :to => 'init#index'
match '/app', :to => 'init#index'
# You can have the root of your site routed with "root"
# just remember to delete public/index.html.
root :to => 'init#index'
# The priority is based upon order of creation:
# first created -> highest priority.
# Sample of regular route:
# match 'products/:id' => 'catalog#view'
# Keep in mind you can assign values other than :controller and :action
# Sample of named route:
# match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase
# This route can be invoked with purchase_url(:id => product.id)
# Sample resource route (maps HTTP verbs to controller actions automatically):
# resources :products
# Sample resource route with options:
# resources :products do
# member do
# get 'short'
# post 'toggle'
# end
#
# collection do
# get 'sold'
# end
# end
# Sample resource route with sub-resources:
# resources :products do
# resources :comments, :sales
# resource :seller
# end
# Sample resource route with more complex sub-resources
# resources :products do
# resources :comments
# resources :sales do
# get 'recent', :on => :collection
# end
# end
# Sample resource route within a namespace:
# namespace :admin do
# # Directs /admin/products/* to Admin::ProductsController
# # (app/controllers/admin/products_controller.rb)
# resources :products
# end
# You can have the root of your site routed with "root"
# just remember to delete public/index.html.
# root :to => 'welcome#index'
# See how all your routes lay out with "rake routes"
# This is a legacy wild controller route that's not recommended for RESTful applications.
# Note: This route will make all actions in every controller accessible via GET requests.
# match ':controller(/:action(/:id))(.:format)'
# load routes from external files
dir = File.expand_path('../', __FILE__)
files = Dir.glob( "#{dir}/routes/*.rb" )

View file

@ -0,0 +1,12 @@
module ExtraRoutes
def add(map)
# groups
map.match '/api/email_addresses', :to => 'email_addresses#index', :via => :get
map.match '/api/email_addresses/:id', :to => 'email_addresses#show', :via => :get
map.match '/api/email_addresses', :to => 'email_addresses#create', :via => :post
map.match '/api/email_addresses/:id', :to => 'email_addresses#update', :via => :put
end
module_function :add
end

View file

@ -0,0 +1,13 @@
module ExtraRoutes
def add(map)
# groups
map.match '/api/signatures', :to => 'signatures#index', :via => :get
map.match '/api/signatures/:id', :to => 'signatures#show', :via => :get
map.match '/api/signatures', :to => 'signatures#create', :via => :post
map.match '/api/signatures/:id', :to => 'signatures#update', :via => :put
map.match '/api/signatures/:id', :to => 'signatures#destroy', :via => :delete
end
module_function :add
end

View file

@ -54,8 +54,20 @@ class CreateBase < ActiveRecord::Migration
end
add_index :signatures, [:name], :unique => true
create_table :email_addresses do |t|
t.column :realname, :string, :limit => 250, :null => false
t.column :email, :string, :limit => 250, :null => false
t.column :active, :boolean, :null => false, :default => true
t.column :note, :string, :limit => 250, :null => true
t.column :updated_by_id, :integer, :null => false
t.column :created_by_id, :integer, :null => false
t.timestamps
end
add_index :email_addresses, [:email], :unique => true
create_table :groups do |t|
t.references :signature, :null => false
t.references :signature, :null => true
t.references :email_address, :null => true
t.column :name, :string, :limit => 100, :null => false
t.column :assignment_timeout, :integer, :null => true
t.column :follow_up_possible, :string, :limit => 100, :default => 'yes', :null => true

View file

@ -0,0 +1,21 @@
class SignatureUpdate < ActiveRecord::Migration
def up
create_table :email_addresses do |t|
t.column :realname, :string, :limit => 250, :null => false
t.column :email, :string, :limit => 250, :null => false
t.column :active, :boolean, :null => false, :default => true
t.column :note, :string, :limit => 250, :null => true
t.column :updated_by_id, :integer, :null => false
t.column :created_by_id, :integer, :null => false
t.timestamps
end
add_index :email_addresses, [:email], :unique => true
add_column :groups, :email_address_id, :integer, :null => true
end
def down
end
end

View file

@ -986,26 +986,6 @@ Setting.create(
:frontend => false
)
Setting.create(
:title => 'System Sender',
:name => 'system_sender',
:area => 'Email::Base',
:description => 'ONLY TEMP!',
:options => {
:form => [
{
:display => '',
:null => false,
:name => 'system_sender',
:tag => 'input',
},
],
},
:state => {
:value => 'Zammad Team <zammad@#{config.fqdn}>',
},
:frontend => false
)
Setting.create(
:title => 'Block Notifications',
:name => 'send_no_auto_response_reg_exp',
@ -1052,6 +1032,13 @@ Setting.create(
:frontend => true
)
email_address = EmailAddress.create(
:id => 1,
:realname => 'Zammad',
:email => 'zammad@localhost',
:updated_by_id => 1,
:created_by_id => 1
)
Role.create(
:id => 1,
@ -1076,11 +1063,12 @@ Role.create(
)
Group.create(
:id => 1,
:name => 'Users',
:note => 'Standard Group/Pool for Tickets.',
:updated_by_id => 1,
:created_by_id => 1
:id => 1,
:name => 'Users',
:email_address_id => email_address.id,
:note => 'Standard Group/Pool for Tickets.',
:updated_by_id => 1,
:created_by_id => 1
)
Group.create(
:id => 2,