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
|
||||
task_key: @task_key
|
||||
)
|
||||
#if @shown
|
||||
# @attributeBar.start()
|
||||
|
||||
@form_id = App.ControllerForm.formId()
|
||||
|
||||
|
@ -660,7 +662,7 @@ class App.TicketZoom extends App.Controller
|
|||
|
||||
taskAction = @$('.js-secondaryActionButtonLabel').data('type')
|
||||
|
||||
ticketParams = @formParam( @$('.edit') )
|
||||
ticketParams = @formParam(@$('.edit'))
|
||||
|
||||
# validate ticket
|
||||
ticket = App.Ticket.find(@ticket_id)
|
||||
|
@ -745,6 +747,35 @@ class App.TicketZoom extends App.Controller
|
|||
|
||||
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
|
||||
@ajax(
|
||||
id: "ticket_update_#{ticket.id}"
|
||||
|
|
|
@ -42,7 +42,7 @@ class App.TicketZoomSidebar extends App.ObserverController
|
|||
|
||||
render: (ticket) =>
|
||||
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(
|
||||
object_id: ticket.id
|
||||
|
@ -52,7 +52,7 @@ class App.TicketZoomSidebar extends App.ObserverController
|
|||
markForm: @markForm
|
||||
)
|
||||
|
||||
if !@permissionCheck('ticket.customer')
|
||||
if @permissionCheck('ticket.agent')
|
||||
@tagWidget = new App.WidgetTag(
|
||||
el: @el.find('.tags')
|
||||
object_type: 'Ticket'
|
||||
|
@ -66,6 +66,11 @@ class App.TicketZoomSidebar extends App.ObserverController
|
|||
links: @links
|
||||
)
|
||||
|
||||
@timeUnitWidget = new App.TicketZoomTimeUnit(
|
||||
el: @el.find('.js-timeUnit')
|
||||
object_id: ticket.id
|
||||
)
|
||||
|
||||
showTicketHistory = =>
|
||||
new App.TicketHistory(
|
||||
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
|
||||
@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
|
||||
@url: @apiPath + '/ticket_articles'
|
||||
@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
|
||||
end
|
||||
|
||||
# remember time accounting
|
||||
time_unit = params[:time_unit]
|
||||
|
||||
clean_params = Ticket::Article.param_association_lookup(params)
|
||||
clean_params = Ticket::Article.param_cleanup(clean_params, true)
|
||||
|
||||
|
@ -447,6 +450,15 @@ class ApplicationController < ActionController::Base
|
|||
end
|
||||
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
|
||||
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
|
||||
end
|
||||
add_index :ticket_priorities, [:name], unique: true
|
||||
|
||||
create_table :tickets do |t|
|
||||
t.references :group, 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 :pending_time, :timestamp, limit: 3, 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 :updated_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, [:pending_time]
|
||||
add_index :tickets, [:type]
|
||||
add_index :tickets, [:time_unit]
|
||||
|
||||
create_table :ticket_flags do |t|
|
||||
t.references :tickets, null: false
|
||||
|
@ -106,16 +109,17 @@ class CreateTicket < ActiveRecord::Migration
|
|||
add_index :ticket_flags, [:tickets_id]
|
||||
add_index :ticket_flags, [:created_by_id]
|
||||
|
||||
create_table :ticket_time_accounting do |t|
|
||||
t.references :tickets, null: false
|
||||
t.references :ticket_articles, null: true
|
||||
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_accounting, [:tickets_id]
|
||||
add_index :ticket_time_accounting, [:ticket_articles_id]
|
||||
add_index :ticket_time_accounting, [:created_by_id]
|
||||
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]
|
||||
|
||||
create_table :ticket_article_types do |t|
|
||||
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
|
||||
)
|
||||
|
||||
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(
|
||||
title: 'New Tags',
|
||||
name: 'tag_new',
|
||||
|
|
Loading…
Reference in a new issue