Implemented issue #373 - Feature "Time recording”.
This commit is contained in:
parent
9cb57cdbe6
commit
ad53128091
20 changed files with 685 additions and 10 deletions
|
@ -428,6 +428,8 @@ class App.TicketZoom extends App.Controller
|
||||||
callback: @submit
|
callback: @submit
|
||||||
task_key: @task_key
|
task_key: @task_key
|
||||||
)
|
)
|
||||||
|
#if @shown
|
||||||
|
# @attributeBar.start()
|
||||||
|
|
||||||
@form_id = App.ControllerForm.formId()
|
@form_id = App.ControllerForm.formId()
|
||||||
|
|
||||||
|
@ -660,7 +662,7 @@ class App.TicketZoom extends App.Controller
|
||||||
|
|
||||||
taskAction = @$('.js-secondaryActionButtonLabel').data('type')
|
taskAction = @$('.js-secondaryActionButtonLabel').data('type')
|
||||||
|
|
||||||
ticketParams = @formParam( @$('.edit') )
|
ticketParams = @formParam(@$('.edit'))
|
||||||
|
|
||||||
# validate ticket
|
# validate ticket
|
||||||
ticket = App.Ticket.find(@ticket_id)
|
ticket = App.Ticket.find(@ticket_id)
|
||||||
|
@ -745,6 +747,35 @@ class App.TicketZoom extends App.Controller
|
||||||
|
|
||||||
ticket.article = article
|
ticket.article = article
|
||||||
|
|
||||||
|
if !ticket.article
|
||||||
|
@submitPost(e, ticket)
|
||||||
|
return
|
||||||
|
|
||||||
|
# verify if time accounting is enabled
|
||||||
|
if @Config.get('time_accounting') isnt true
|
||||||
|
@submitPost(e, ticket)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
# verify if time accounting is active for ticket
|
||||||
|
if false
|
||||||
|
@submitPost(e, ticket)
|
||||||
|
return
|
||||||
|
|
||||||
|
# time tracking
|
||||||
|
new App.TicketZoomTimeAccounting(
|
||||||
|
container: @el.closest('.content')
|
||||||
|
ticket: ticket
|
||||||
|
cancelCallback: =>
|
||||||
|
@formEnable(e)
|
||||||
|
submitCallback: (params) =>
|
||||||
|
if params.time_unit
|
||||||
|
ticket.article.time_unit = params.time_unit
|
||||||
|
@submitPost(e, ticket)
|
||||||
|
)
|
||||||
|
|
||||||
|
submitPost: (e, ticket) =>
|
||||||
|
|
||||||
# submit changes
|
# submit changes
|
||||||
@ajax(
|
@ajax(
|
||||||
id: "ticket_update_#{ticket.id}"
|
id: "ticket_update_#{ticket.id}"
|
||||||
|
|
|
@ -42,7 +42,7 @@ class App.TicketZoomSidebar extends App.ObserverController
|
||||||
|
|
||||||
render: (ticket) =>
|
render: (ticket) =>
|
||||||
editTicket = (el) =>
|
editTicket = (el) =>
|
||||||
el.append('<form><fieldset class="edit"></fieldset></form><div class="tags"></div><div class="links"></div>')
|
el.append(App.view('ticket_zoom/sidebar_ticket')())
|
||||||
|
|
||||||
@edit = new Edit(
|
@edit = new Edit(
|
||||||
object_id: ticket.id
|
object_id: ticket.id
|
||||||
|
@ -52,7 +52,7 @@ class App.TicketZoomSidebar extends App.ObserverController
|
||||||
markForm: @markForm
|
markForm: @markForm
|
||||||
)
|
)
|
||||||
|
|
||||||
if !@permissionCheck('ticket.customer')
|
if @permissionCheck('ticket.agent')
|
||||||
@tagWidget = new App.WidgetTag(
|
@tagWidget = new App.WidgetTag(
|
||||||
el: @el.find('.tags')
|
el: @el.find('.tags')
|
||||||
object_type: 'Ticket'
|
object_type: 'Ticket'
|
||||||
|
@ -66,6 +66,11 @@ class App.TicketZoomSidebar extends App.ObserverController
|
||||||
links: @links
|
links: @links
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@timeUnitWidget = new App.TicketZoomTimeUnit(
|
||||||
|
el: @el.find('.js-timeUnit')
|
||||||
|
object_id: ticket.id
|
||||||
|
)
|
||||||
|
|
||||||
showTicketHistory = =>
|
showTicketHistory = =>
|
||||||
new App.TicketHistory(
|
new App.TicketHistory(
|
||||||
ticket_id: ticket.id
|
ticket_id: ticket.id
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
class App.TicketZoomTimeAccounting extends App.ControllerModal
|
||||||
|
buttonClose: true
|
||||||
|
buttonCancel: 'skip'
|
||||||
|
buttonSubmit: 'Account Time'
|
||||||
|
buttonClass: 'btn--success'
|
||||||
|
head: 'Time Accounting'
|
||||||
|
small: true
|
||||||
|
|
||||||
|
content: ->
|
||||||
|
App.view('ticket_zoom/time_accounting')()
|
||||||
|
|
||||||
|
onCancel: =>
|
||||||
|
if @cancelCallback
|
||||||
|
@cancelCallback()
|
||||||
|
|
||||||
|
onSubmit: =>
|
||||||
|
@close()
|
||||||
|
if @submitCallback
|
||||||
|
params = @formParams()
|
||||||
|
@submitCallback(params)
|
|
@ -0,0 +1,11 @@
|
||||||
|
class App.TicketZoomTimeUnit extends App.ObserverController
|
||||||
|
model: 'Ticket'
|
||||||
|
observe:
|
||||||
|
time_unit: true
|
||||||
|
|
||||||
|
render: (ticket) =>
|
||||||
|
return if !@permissionCheck('ticket.agent')
|
||||||
|
return if !ticket.time_unit
|
||||||
|
@html App.view('ticket_zoom/time_unit')(
|
||||||
|
ticket: ticket
|
||||||
|
)
|
205
app/assets/javascripts/app/controllers/time_accounting.coffee
Normal file
205
app/assets/javascripts/app/controllers/time_accounting.coffee
Normal file
|
@ -0,0 +1,205 @@
|
||||||
|
class Index extends App.ControllerSubContent
|
||||||
|
requiredPermission: 'admin.time_accounting'
|
||||||
|
header: 'Time Accounting'
|
||||||
|
events:
|
||||||
|
'change .js-timeAccountingSetting input': 'setTimeAccounting'
|
||||||
|
'click .js-timePickerYear': 'setYear'
|
||||||
|
'click .js-timePickerMonth': 'setMonth'
|
||||||
|
|
||||||
|
elements:
|
||||||
|
'.js-timeAccountingSetting input': 'timeAccountingSetting'
|
||||||
|
|
||||||
|
constructor: ->
|
||||||
|
super
|
||||||
|
|
||||||
|
current = new Date()
|
||||||
|
currentDay = current.getDate()
|
||||||
|
currentMonth = current.getMonth() + 1
|
||||||
|
currentYear = current.getFullYear()
|
||||||
|
currentWeek = current.getWeek()
|
||||||
|
if !@month
|
||||||
|
@month = currentMonth
|
||||||
|
if !@year
|
||||||
|
@year = currentYear
|
||||||
|
|
||||||
|
@subscribeId = App.Setting.subscribe(@render, initFetch: true, clear: false)
|
||||||
|
|
||||||
|
release: =>
|
||||||
|
App.Setting.unsubscribe(@subscribeId)
|
||||||
|
|
||||||
|
render: =>
|
||||||
|
currentNewTagSetting = @Config.get('time_accounting') || false
|
||||||
|
#return if currentNewTagSetting is @lastNewTagSetting
|
||||||
|
@lastNewTagSetting = currentNewTagSetting
|
||||||
|
|
||||||
|
timeRangeYear = []
|
||||||
|
year = new Date().getFullYear()
|
||||||
|
for item in [year-2..year]
|
||||||
|
record = {
|
||||||
|
display: item
|
||||||
|
value: item
|
||||||
|
}
|
||||||
|
timeRangeYear.push record
|
||||||
|
|
||||||
|
timeRangeMonth = [
|
||||||
|
{
|
||||||
|
display: 'Jan'
|
||||||
|
value: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
display: 'Feb'
|
||||||
|
value: 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
display: 'Mar'
|
||||||
|
value: 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
display: 'Apr'
|
||||||
|
value: 4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
display: 'Mai'
|
||||||
|
value: 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
display: 'Jun'
|
||||||
|
value: 6,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
display: 'Jul'
|
||||||
|
value: 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
display: 'Aug'
|
||||||
|
value: 8,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
display: 'Sep'
|
||||||
|
value: 9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
display: 'Oct'
|
||||||
|
value: 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
display: 'Nov'
|
||||||
|
value: 11,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
display: 'Dec'
|
||||||
|
value: 12,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
@html App.view('time_accounting/index')(
|
||||||
|
timeRangeYear: timeRangeYear
|
||||||
|
timeRangeMonth: timeRangeMonth
|
||||||
|
year: @year
|
||||||
|
month: @month
|
||||||
|
)
|
||||||
|
|
||||||
|
configure_attributes = [
|
||||||
|
{ name: 'condition', display: 'Conditions for effected objects', tag: 'ticket_selector', null: false, preview: false, action: false, hasChanged: false },
|
||||||
|
]
|
||||||
|
|
||||||
|
new App.ControllerForm(
|
||||||
|
el: @$('.js-selector')
|
||||||
|
model:
|
||||||
|
configure_attributes: configure_attributes,
|
||||||
|
autofocus: true
|
||||||
|
)
|
||||||
|
|
||||||
|
new ByTicket(
|
||||||
|
el: @$('.js-tableTicket')
|
||||||
|
year: @year
|
||||||
|
month: @month
|
||||||
|
)
|
||||||
|
|
||||||
|
new ByCustomer(
|
||||||
|
el: @$('.js-tableCustomer')
|
||||||
|
year: @year
|
||||||
|
month: @month
|
||||||
|
)
|
||||||
|
|
||||||
|
new ByOrganization(
|
||||||
|
el: @$('.js-tableOrganization')
|
||||||
|
year: @year
|
||||||
|
month: @month
|
||||||
|
)
|
||||||
|
|
||||||
|
setTimeAccounting: (e) =>
|
||||||
|
value = @timeAccountingSetting.prop('checked')
|
||||||
|
App.Setting.set('time_accounting', value)
|
||||||
|
|
||||||
|
setYear: (e) =>
|
||||||
|
e.preventDefault()
|
||||||
|
@year = $(e.target).data('type')
|
||||||
|
@render()
|
||||||
|
|
||||||
|
setMonth: (e) =>
|
||||||
|
e.preventDefault()
|
||||||
|
@month = $(e.target).data('type')
|
||||||
|
@render()
|
||||||
|
|
||||||
|
class ByTicket extends App.Controller
|
||||||
|
constructor: ->
|
||||||
|
super
|
||||||
|
@load()
|
||||||
|
|
||||||
|
load: =>
|
||||||
|
@ajax(
|
||||||
|
id: 'by_ticket'
|
||||||
|
type: 'GET'
|
||||||
|
url: "#{@apiPath}/time_accounting/log/by_ticket/#{@year}/#{@month}"
|
||||||
|
processData: true
|
||||||
|
success: (data, status, xhr) =>
|
||||||
|
@render(data)
|
||||||
|
)
|
||||||
|
|
||||||
|
render: (rows) =>
|
||||||
|
@html App.view('time_accounting/by_ticket')(
|
||||||
|
rows: rows
|
||||||
|
)
|
||||||
|
|
||||||
|
class ByCustomer extends App.Controller
|
||||||
|
constructor: ->
|
||||||
|
super
|
||||||
|
@load()
|
||||||
|
|
||||||
|
load: =>
|
||||||
|
@ajax(
|
||||||
|
id: 'by_customer'
|
||||||
|
type: 'GET'
|
||||||
|
url: "#{@apiPath}/time_accounting/log/by_customer/#{@year}/#{@month}"
|
||||||
|
processData: true
|
||||||
|
success: (data, status, xhr) =>
|
||||||
|
@render(data)
|
||||||
|
)
|
||||||
|
|
||||||
|
render: (rows) =>
|
||||||
|
@html App.view('time_accounting/by_customer')(
|
||||||
|
rows: rows
|
||||||
|
)
|
||||||
|
|
||||||
|
class ByOrganization extends App.Controller
|
||||||
|
constructor: ->
|
||||||
|
super
|
||||||
|
@load()
|
||||||
|
|
||||||
|
load: =>
|
||||||
|
@ajax(
|
||||||
|
id: 'by_organization'
|
||||||
|
type: 'GET'
|
||||||
|
url: "#{@apiPath}/time_accounting/log/by_organization/#{@year}/#{@month}"
|
||||||
|
processData: true
|
||||||
|
success: (data, status, xhr) =>
|
||||||
|
@render(data)
|
||||||
|
)
|
||||||
|
|
||||||
|
render: (rows) =>
|
||||||
|
@html App.view('time_accounting/by_organization')(
|
||||||
|
rows: rows
|
||||||
|
)
|
||||||
|
|
||||||
|
App.Config.set('TimeAccounting', { prio: 8500, name: 'Time Accounting', parent: '#manage', target: '#manage/time_accounting', controller: Index, permission: ['admin.time_accounting'] }, 'NavBarAdmin')
|
|
@ -1,5 +1,5 @@
|
||||||
class App.TicketArticle extends App.Model
|
class App.TicketArticle extends App.Model
|
||||||
@configure 'TicketArticle', 'from', 'to', 'cc', 'subject', 'body', 'content_type', 'ticket_id', 'type_id', 'sender_id', 'internal', 'in_reply_to', 'form_id', 'preferences', 'updated_at'
|
@configure 'TicketArticle', 'from', 'to', 'cc', 'subject', 'body', 'content_type', 'ticket_id', 'type_id', 'sender_id', 'internal', 'in_reply_to', 'form_id', 'time_unit', 'preferences', 'updated_at'
|
||||||
@extend Spine.Model.Ajax
|
@extend Spine.Model.Ajax
|
||||||
@url: @apiPath + '/ticket_articles'
|
@url: @apiPath + '/ticket_articles'
|
||||||
@configure_attributes = [
|
@configure_attributes = [
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
<form>
|
||||||
|
<fieldset class="edit"></fieldset>
|
||||||
|
</form>
|
||||||
|
<div class="tags"></div>
|
||||||
|
<div class="links"></div>
|
||||||
|
<div class="js-timeUnit"></div>
|
|
@ -0,0 +1,3 @@
|
||||||
|
<form>
|
||||||
|
<input type="text" name="time_unit" placeholder="<%- @T('Please enter your time which you want to account.') %>"/>
|
||||||
|
</form>
|
|
@ -0,0 +1,4 @@
|
||||||
|
<div>
|
||||||
|
<label><%- @T('Accounted Time') %></label>
|
||||||
|
<div><%= @ticket.time_unit %></div>
|
||||||
|
</div>
|
|
@ -0,0 +1,23 @@
|
||||||
|
<% if !@rows.length: %>
|
||||||
|
<table class="settings-list settings-list--stretch settings-list--placeholder">
|
||||||
|
<thead><tr><th><%- @T('No Entries') %>
|
||||||
|
</table>
|
||||||
|
<% else: %>
|
||||||
|
<table class="table table-striped table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th><%- @T('Customer') %>
|
||||||
|
<th><%- @T('Organization') %>
|
||||||
|
<th><%- @T('Time Units') %>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<% for row in @rows: %>
|
||||||
|
<tr>
|
||||||
|
<td><a href="#user/profile/<%- row.customer.id %>"><%= row.customer.email %></a>
|
||||||
|
<td><% if row.organization: %><%= row.organization.name %><% end %>
|
||||||
|
<td><%= row.time_unit %>
|
||||||
|
<% end %>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
|
@ -0,0 +1,21 @@
|
||||||
|
<% if !@rows.length: %>
|
||||||
|
<table class="settings-list settings-list--stretch settings-list--placeholder">
|
||||||
|
<thead><tr><th><%- @T('No Entries') %>
|
||||||
|
</table>
|
||||||
|
<% else: %>
|
||||||
|
<table class="table table-striped table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th><%- @T('Organization') %>
|
||||||
|
<th><%- @T('Time Units') %>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<% for row in @rows: %>
|
||||||
|
<tr>
|
||||||
|
<td><a href="#organization/profile/<%- row.organization.id %>"><%= row.organization.name %></a>
|
||||||
|
<td><%= row.time_unit %>
|
||||||
|
<% end %>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
|
@ -0,0 +1,31 @@
|
||||||
|
<% if !@rows.length: %>
|
||||||
|
<table class="settings-list settings-list--stretch settings-list--placeholder">
|
||||||
|
<thead><tr><th><%- @T('No Entries') %>
|
||||||
|
</table>
|
||||||
|
<% else: %>
|
||||||
|
<table class="table table-striped table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th><%- @T('Ticket#') %>
|
||||||
|
<th><%- @T('Title') %>
|
||||||
|
<th><%- @T('Customer') %>
|
||||||
|
<th><%- @T('Organization') %>
|
||||||
|
<th><%- @T('Agent') %>
|
||||||
|
<th><%- @T('Time Units') %>
|
||||||
|
<th><%- @T('Time Units Total') %>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<% for row in @rows: %>
|
||||||
|
<tr>
|
||||||
|
<td><a href="#ticket/zoom/<%- row.ticket.id %>"><%= row.ticket.number %></a>
|
||||||
|
<td title="<%= row.ticket.title %>"><%= row.ticket.title %>
|
||||||
|
<td><%= row.customer %>
|
||||||
|
<td><%= row.organization %>
|
||||||
|
<td><%= row.agent %>
|
||||||
|
<td><%= row.time_unit %>
|
||||||
|
<td><%= row.ticket.time_unit %>
|
||||||
|
<% end %>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
|
@ -0,0 +1,46 @@
|
||||||
|
<div class="page-header">
|
||||||
|
<div class="page-header-title">
|
||||||
|
<div class="zammad-switch zammad-switch--small js-timeAccountingSetting">
|
||||||
|
<input name="chat" type="checkbox" id="time-accounting" <% if @C('time_accounting'): %>checked<% end %>>
|
||||||
|
<label for="time-accounting"></label>
|
||||||
|
</div>
|
||||||
|
<h1><%- @T('Time Accounting') %><small></small></h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="page-content">
|
||||||
|
|
||||||
|
<div class="settings-entry">
|
||||||
|
<div class="page-header-title">
|
||||||
|
<h2><%- @T('Selector') %></h2>
|
||||||
|
</div>
|
||||||
|
<p><%- @T('Enable time accounting for following matching tickets.') %></p>
|
||||||
|
<div class="js-selector"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-entry">
|
||||||
|
|
||||||
|
<h2><%- @T('Overviews') %></h2>
|
||||||
|
<div class="well">
|
||||||
|
<div class="btn-group btn-group--full" role="group" aria-label="">
|
||||||
|
<% for item in @timeRangeYear: %>
|
||||||
|
<div class="btn btn--textLarge js-timePickerYear<%- ' is-selected' if @year is item.value %>" data-id="<%= @timeRange %>" data-type="<%= item.value %>"><%= item.display %></div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="btn-group btn-group--full" role="group" aria-label="">
|
||||||
|
<% for item in @timeRangeMonth: %>
|
||||||
|
<div class="btn btn--textLarge js-timePickerMonth<%- ' is-selected' if @month is item.value %>" data-id="<%= @timeRange %>" data-type="<%= item.value %>"><%= item.display %></div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h3><%- @T('Ticket') %></h3>
|
||||||
|
<div class="js-tableTicket"></div>
|
||||||
|
<br>
|
||||||
|
<h3><%- @T('Customer') %></h3>
|
||||||
|
<div class="js-tableCustomer"></div>
|
||||||
|
<br>
|
||||||
|
<h3><%- @T('Organization') %></h3>
|
||||||
|
<div class="js-tableOrganization"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
|
@ -398,6 +398,9 @@ class ApplicationController < ActionController::Base
|
||||||
params[:sender_id] = Ticket::Article::Sender.lookup(name: sender).id
|
params[:sender_id] = Ticket::Article::Sender.lookup(name: sender).id
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# remember time accounting
|
||||||
|
time_unit = params[:time_unit]
|
||||||
|
|
||||||
clean_params = Ticket::Article.param_association_lookup(params)
|
clean_params = Ticket::Article.param_association_lookup(params)
|
||||||
clean_params = Ticket::Article.param_cleanup(clean_params, true)
|
clean_params = Ticket::Article.param_cleanup(clean_params, true)
|
||||||
|
|
||||||
|
@ -447,6 +450,15 @@ class ApplicationController < ActionController::Base
|
||||||
end
|
end
|
||||||
article.save!
|
article.save!
|
||||||
|
|
||||||
|
# account time
|
||||||
|
if time_unit.present?
|
||||||
|
Ticket::TimeAccounting.create!(
|
||||||
|
ticket_id: article.ticket_id,
|
||||||
|
ticket_article_id: article.id,
|
||||||
|
time_unit: time_unit
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
# remove attachments from upload cache
|
# remove attachments from upload cache
|
||||||
return article if !form_id
|
return article if !form_id
|
||||||
|
|
||||||
|
|
152
app/controllers/time_accountings_controller.rb
Normal file
152
app/controllers/time_accountings_controller.rb
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
|
||||||
|
|
||||||
|
class TimeAccountingsController < ApplicationController
|
||||||
|
before_action { authentication_check(permission: 'admin.time_accounting') }
|
||||||
|
|
||||||
|
def by_ticket
|
||||||
|
|
||||||
|
year = params[:year] || Time.zone.now.year
|
||||||
|
month = params[:month] || Time.zone.now.month
|
||||||
|
|
||||||
|
start_periode = Date.parse("#{year}-#{month}-01")
|
||||||
|
end_periode = start_periode.end_of_month
|
||||||
|
|
||||||
|
time_unit = {}
|
||||||
|
Ticket::TimeAccounting.where('created_at >= ? AND created_at <= ?', start_periode, end_periode).pluck(:ticket_id, :time_unit, :created_by_id).each { |record|
|
||||||
|
if !time_unit[record[0]]
|
||||||
|
time_unit[record[0]] = {
|
||||||
|
time_unit: 0,
|
||||||
|
agent_id: record[2],
|
||||||
|
}
|
||||||
|
end
|
||||||
|
time_unit[record[0]][:time_unit] += record[1]
|
||||||
|
}
|
||||||
|
customers = {}
|
||||||
|
organizations = {}
|
||||||
|
agents = {}
|
||||||
|
results = []
|
||||||
|
time_unit.each { |ticket_id, local_time_unit|
|
||||||
|
ticket = Ticket.lookup(id: ticket_id)
|
||||||
|
next if !ticket
|
||||||
|
if !customers[ticket.customer_id]
|
||||||
|
customers[ticket.customer_id] = '-'
|
||||||
|
if ticket.customer_id
|
||||||
|
customer_user = User.lookup(id: ticket.customer_id)
|
||||||
|
if customer_user
|
||||||
|
customers[ticket.customer_id] = customer_user.fullname
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if !organizations[ticket.organization_id]
|
||||||
|
organizations[ticket.organization_id] = '-'
|
||||||
|
if ticket.organization_id
|
||||||
|
organization = Organization.lookup(id: ticket.organization_id)
|
||||||
|
if organization
|
||||||
|
organizations[ticket.organization_id] = organization.name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if !customers[local_time_unit[:agent_id]]
|
||||||
|
agent_user = User.lookup(id: local_time_unit[:agent_id])
|
||||||
|
agent = '-'
|
||||||
|
if agent_user
|
||||||
|
agents[local_time_unit[:agent_id]] = agent_user.fullname
|
||||||
|
end
|
||||||
|
end
|
||||||
|
result = {
|
||||||
|
ticket: ticket.attributes,
|
||||||
|
time_unit: local_time_unit[:time_unit],
|
||||||
|
customer: customers[ticket.customer_id],
|
||||||
|
organization: organizations[ticket.organization_id],
|
||||||
|
agent: agents[local_time_unit[:agent_id]],
|
||||||
|
}
|
||||||
|
results.push result
|
||||||
|
}
|
||||||
|
render json: results
|
||||||
|
end
|
||||||
|
|
||||||
|
def by_customer
|
||||||
|
|
||||||
|
year = params[:year] || Time.zone.now.year
|
||||||
|
month = params[:month] || Time.zone.now.month
|
||||||
|
|
||||||
|
start_periode = Date.parse("#{year}-#{month}-01")
|
||||||
|
end_periode = start_periode.end_of_month
|
||||||
|
|
||||||
|
time_unit = {}
|
||||||
|
Ticket::TimeAccounting.where('created_at >= ? AND created_at <= ?', start_periode, end_periode).pluck(:ticket_id, :time_unit, :created_by_id).each { |record|
|
||||||
|
if !time_unit[record[0]]
|
||||||
|
time_unit[record[0]] = {
|
||||||
|
time_unit: 0,
|
||||||
|
agent_id: record[2],
|
||||||
|
}
|
||||||
|
end
|
||||||
|
time_unit[record[0]][:time_unit] += record[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
customers = {}
|
||||||
|
time_unit.each { |ticket_id, local_time_unit|
|
||||||
|
ticket = Ticket.lookup(id: ticket_id)
|
||||||
|
next if !ticket
|
||||||
|
if !customers[ticket.customer_id]
|
||||||
|
organization = nil
|
||||||
|
if ticket.organization_id
|
||||||
|
organization = Organization.lookup(id: ticket.organization_id).attributes
|
||||||
|
end
|
||||||
|
customers[ticket.customer_id] = {
|
||||||
|
customer: User.lookup(id: ticket.customer_id).attributes,
|
||||||
|
organization: organization,
|
||||||
|
time_unit: local_time_unit[:time_unit],
|
||||||
|
}
|
||||||
|
next
|
||||||
|
end
|
||||||
|
customers[ticket.customer_id][:time_unit] += local_time_unit[:time_unit]
|
||||||
|
}
|
||||||
|
results = []
|
||||||
|
customers.each { |_customer_id, content|
|
||||||
|
results.push content
|
||||||
|
}
|
||||||
|
render json: results
|
||||||
|
end
|
||||||
|
|
||||||
|
def by_organization
|
||||||
|
|
||||||
|
year = params[:year] || Time.zone.now.year
|
||||||
|
month = params[:month] || Time.zone.now.month
|
||||||
|
|
||||||
|
start_periode = Date.parse("#{year}-#{month}-01")
|
||||||
|
end_periode = start_periode.end_of_month
|
||||||
|
|
||||||
|
time_unit = {}
|
||||||
|
Ticket::TimeAccounting.where('created_at >= ? AND created_at <= ?', start_periode, end_periode).pluck(:ticket_id, :time_unit, :created_by_id).each { |record|
|
||||||
|
if !time_unit[record[0]]
|
||||||
|
time_unit[record[0]] = {
|
||||||
|
time_unit: 0,
|
||||||
|
agent_id: record[2],
|
||||||
|
}
|
||||||
|
end
|
||||||
|
time_unit[record[0]][:time_unit] += record[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
organizations = {}
|
||||||
|
time_unit.each { |ticket_id, local_time_unit|
|
||||||
|
ticket = Ticket.lookup(id: ticket_id)
|
||||||
|
next if !ticket
|
||||||
|
next if !ticket.organization_id
|
||||||
|
if !organizations[ticket.organization_id]
|
||||||
|
organizations[ticket.organization_id] = {
|
||||||
|
organization: Organization.lookup(id: ticket.organization_id).attributes,
|
||||||
|
time_unit: local_time_unit[:time_unit],
|
||||||
|
}
|
||||||
|
next
|
||||||
|
end
|
||||||
|
organizations[ticket.organization_id][:time_unit] += local_time_unit[:time_unit]
|
||||||
|
}
|
||||||
|
results = []
|
||||||
|
organizations.each { |_customer_id, content|
|
||||||
|
results.push content
|
||||||
|
}
|
||||||
|
render json: results
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
23
app/models/ticket/time_accounting.rb
Normal file
23
app/models/ticket/time_accounting.rb
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
|
||||||
|
class Ticket::TimeAccounting < ApplicationModel
|
||||||
|
|
||||||
|
after_create :ticket_time_unit_update
|
||||||
|
after_update :ticket_time_unit_update
|
||||||
|
|
||||||
|
def ticket_time_unit_update
|
||||||
|
exists = false
|
||||||
|
time_units = 0
|
||||||
|
Ticket::TimeAccounting.where(ticket_id: ticket_id).each { |record|
|
||||||
|
time_units += record.time_unit
|
||||||
|
exists = true
|
||||||
|
}
|
||||||
|
return false if exists == false
|
||||||
|
ticket = Ticket.lookup(id: ticket_id)
|
||||||
|
return false if !ticket
|
||||||
|
return false if ticket.time_unit == time_units
|
||||||
|
ticket.time_unit = time_units
|
||||||
|
ticket.save!
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
8
config/routes/time_accounting.rb
Normal file
8
config/routes/time_accounting.rb
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
Zammad::Application.routes.draw do
|
||||||
|
api_path = Rails.configuration.api_path
|
||||||
|
|
||||||
|
match api_path + '/time_accounting/log/by_ticket/:year/:month', to: 'time_accountings#by_ticket', via: :get
|
||||||
|
match api_path + '/time_accounting/log/by_customer/:year/:month', to: 'time_accountings#by_customer', via: :get
|
||||||
|
match api_path + '/time_accounting/log/by_organization/:year/:month', to: 'time_accountings#by_organization', via: :get
|
||||||
|
|
||||||
|
end
|
|
@ -31,6 +31,7 @@ class CreateTicket < ActiveRecord::Migration
|
||||||
t.timestamps limit: 3, null: false
|
t.timestamps limit: 3, null: false
|
||||||
end
|
end
|
||||||
add_index :ticket_priorities, [:name], unique: true
|
add_index :ticket_priorities, [:name], unique: true
|
||||||
|
|
||||||
create_table :tickets do |t|
|
create_table :tickets do |t|
|
||||||
t.references :group, null: false
|
t.references :group, null: false
|
||||||
t.references :priority, null: false
|
t.references :priority, null: false
|
||||||
|
@ -61,6 +62,7 @@ class CreateTicket < ActiveRecord::Migration
|
||||||
t.column :escalation_at, :timestamp, limit: 3, null: true
|
t.column :escalation_at, :timestamp, limit: 3, null: true
|
||||||
t.column :pending_time, :timestamp, limit: 3, null: true
|
t.column :pending_time, :timestamp, limit: 3, null: true
|
||||||
t.column :type, :string, limit: 100, null: true
|
t.column :type, :string, limit: 100, null: true
|
||||||
|
t.column :time_unit, :decimal, precision: 6, scale: 2, null: false
|
||||||
t.column :preferences, :text, limit: 500.kilobytes + 1, null: true
|
t.column :preferences, :text, limit: 500.kilobytes + 1, null: true
|
||||||
t.column :updated_by_id, :integer, null: false
|
t.column :updated_by_id, :integer, null: false
|
||||||
t.column :created_by_id, :integer, null: false
|
t.column :created_by_id, :integer, null: false
|
||||||
|
@ -93,6 +95,7 @@ class CreateTicket < ActiveRecord::Migration
|
||||||
add_index :tickets, [:created_by_id]
|
add_index :tickets, [:created_by_id]
|
||||||
add_index :tickets, [:pending_time]
|
add_index :tickets, [:pending_time]
|
||||||
add_index :tickets, [:type]
|
add_index :tickets, [:type]
|
||||||
|
add_index :tickets, [:time_unit]
|
||||||
|
|
||||||
create_table :ticket_flags do |t|
|
create_table :ticket_flags do |t|
|
||||||
t.references :tickets, null: false
|
t.references :tickets, null: false
|
||||||
|
@ -106,16 +109,17 @@ class CreateTicket < ActiveRecord::Migration
|
||||||
add_index :ticket_flags, [:tickets_id]
|
add_index :ticket_flags, [:tickets_id]
|
||||||
add_index :ticket_flags, [:created_by_id]
|
add_index :ticket_flags, [:created_by_id]
|
||||||
|
|
||||||
create_table :ticket_time_accounting do |t|
|
create_table :ticket_time_accountings do |t|
|
||||||
t.references :tickets, null: false
|
t.references :ticket, null: false
|
||||||
t.references :ticket_articles, null: true
|
t.references :ticket_article, null: true
|
||||||
t.column :time_unit, :decimal, precision: 6, scale: 2, null: false
|
t.column :time_unit, :decimal, precision: 6, scale: 2, null: false
|
||||||
t.column :created_by_id, :integer, null: false
|
t.column :created_by_id, :integer, null: false
|
||||||
t.timestamps limit: 3, null: false
|
t.timestamps limit: 3, null: false
|
||||||
end
|
end
|
||||||
add_index :ticket_time_accounting, [:tickets_id]
|
add_index :ticket_time_accountings, [:ticket_id]
|
||||||
add_index :ticket_time_accounting, [:ticket_articles_id]
|
add_index :ticket_time_accountings, [:ticket_article_id]
|
||||||
add_index :ticket_time_accounting, [:created_by_id]
|
add_index :ticket_time_accountings, [:created_by_id]
|
||||||
|
add_index :ticket_time_accountings, [:time_unit]
|
||||||
|
|
||||||
create_table :ticket_article_types do |t|
|
create_table :ticket_article_types do |t|
|
||||||
t.column :name, :string, limit: 250, null: false
|
t.column :name, :string, limit: 250, null: false
|
||||||
|
|
25
db/migrate/20170116000001_add_ticket_time_accounting_373.rb
Normal file
25
db/migrate/20170116000001_add_ticket_time_accounting_373.rb
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
class AddTicketTimeAccounting373 < ActiveRecord::Migration
|
||||||
|
def up
|
||||||
|
|
||||||
|
# return if it's a new setup
|
||||||
|
return if !Setting.find_by(name: 'system_init_done')
|
||||||
|
|
||||||
|
drop_table :ticket_time_accountings
|
||||||
|
create_table :ticket_time_accountings do |t|
|
||||||
|
t.references :ticket, null: false
|
||||||
|
t.references :ticket_article, null: true
|
||||||
|
t.column :time_unit, :decimal, precision: 6, scale: 2, null: false
|
||||||
|
t.column :created_by_id, :integer, null: false
|
||||||
|
t.timestamps limit: 3, null: false
|
||||||
|
end
|
||||||
|
add_index :ticket_time_accountings, [:ticket_id]
|
||||||
|
add_index :ticket_time_accountings, [:ticket_article_id]
|
||||||
|
add_index :ticket_time_accountings, [:created_by_id]
|
||||||
|
add_index :ticket_time_accountings, [:time_unit]
|
||||||
|
|
||||||
|
add_column :tickets, :time_unit, :decimal, precision: 6, scale: 2, null: true
|
||||||
|
add_index :tickets, [:time_unit]
|
||||||
|
|
||||||
|
Cache.clear
|
||||||
|
end
|
||||||
|
end
|
45
db/seeds.rb
45
db/seeds.rb
|
@ -2034,6 +2034,51 @@ Setting.create_if_not_exists(
|
||||||
frontend: false
|
frontend: false
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Setting.create_if_not_exists(
|
||||||
|
title: 'Time Accounting',
|
||||||
|
name: 'time_accounting',
|
||||||
|
area: 'Web::Base',
|
||||||
|
description: 'Enable time accounting.',
|
||||||
|
options: {
|
||||||
|
form: [
|
||||||
|
{
|
||||||
|
display: '',
|
||||||
|
null: true,
|
||||||
|
name: 'time_accounting',
|
||||||
|
tag: 'boolean',
|
||||||
|
options: {
|
||||||
|
true => 'yes',
|
||||||
|
false => 'no',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
preferences: {
|
||||||
|
authentication: true,
|
||||||
|
permission: ['admin.time_accounting'],
|
||||||
|
},
|
||||||
|
state: false,
|
||||||
|
frontend: true
|
||||||
|
)
|
||||||
|
|
||||||
|
Setting.create_if_not_exists(
|
||||||
|
title: 'Time Accounting Selector',
|
||||||
|
name: 'time_accounting_selector',
|
||||||
|
area: 'Web::Base',
|
||||||
|
description: 'Enable time accounting for this tickets.',
|
||||||
|
options: {
|
||||||
|
form: [
|
||||||
|
{},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
preferences: {
|
||||||
|
authentication: true,
|
||||||
|
permission: ['admin.time_accounting'],
|
||||||
|
},
|
||||||
|
state: {},
|
||||||
|
frontend: true
|
||||||
|
)
|
||||||
|
|
||||||
Setting.create_if_not_exists(
|
Setting.create_if_not_exists(
|
||||||
title: 'New Tags',
|
title: 'New Tags',
|
||||||
name: 'tag_new',
|
name: 'tag_new',
|
||||||
|
|
Loading…
Reference in a new issue