keithsalisbury-subtrac 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (139) hide show
  1. data/LICENSE +20 -0
  2. data/README.rdoc +7 -0
  3. data/Rakefile +56 -0
  4. data/VERSION.yml +4 -0
  5. data/bin/subtrac +40 -0
  6. data/lib/subtrac.rb +245 -0
  7. data/lib/subtrac/common/clients/index.wsgi +89 -0
  8. data/lib/subtrac/common/favicon.ico +0 -0
  9. data/lib/subtrac/common/images/trac/banner_bg.jpg +0 -0
  10. data/lib/subtrac/common/images/trac/bar_bg.gif +0 -0
  11. data/lib/subtrac/common/images/trac/footer_back.png +0 -0
  12. data/lib/subtrac/common/images/trac/main_bg.gif +0 -0
  13. data/lib/subtrac/common/images/trac/saint_logo_small.png +0 -0
  14. data/lib/subtrac/common/static/404.html +14 -0
  15. data/lib/subtrac/common/styles/trac.css +222 -0
  16. data/lib/subtrac/common/trac.ini +178 -0
  17. data/lib/subtrac/config/config.yml +54 -0
  18. data/lib/subtrac/passwords +1 -0
  19. data/lib/subtrac/shared/trac.ini +178 -0
  20. data/lib/subtrac/templates/location.erb +16 -0
  21. data/lib/subtrac/templates/projects/blank/svn/branches/README +0 -0
  22. data/lib/subtrac/templates/projects/blank/svn/tags/README +0 -0
  23. data/lib/subtrac/templates/projects/blank/svn/trunk/README +0 -0
  24. data/lib/subtrac/templates/projects/blank/trac/wiki/WikiStart +57 -0
  25. data/lib/subtrac/templates/projects/new/svn/trunk/trac/wiki/WikiStart +46 -0
  26. data/lib/subtrac/templates/projects/new/trac/wiki/WikiStart +23 -0
  27. data/lib/subtrac/templates/projects/trac_theme/svn/trunk/index/index.html +22 -0
  28. data/lib/subtrac/templates/projects/trac_theme/svn/trunk/templates/layout.html +56 -0
  29. data/lib/subtrac/templates/projects/trac_theme/svn/trunk/templates/site.html +27 -0
  30. data/lib/subtrac/templates/projects/trac_theme/svn/trunk/templates/theme.html +86 -0
  31. data/lib/subtrac/templates/projects/trac_theme/trac/wiki/WikiStart +4 -0
  32. data/lib/subtrac/templates/trac.erb +25 -0
  33. data/lib/subtrac/templates/vhost.erb +35 -0
  34. data/lib/subtrac/trac-plugins/advancedticketworkflowplugin/advancedworkflow/__init__.py +0 -0
  35. data/lib/subtrac/trac-plugins/advancedticketworkflowplugin/advancedworkflow/controller.py +419 -0
  36. data/lib/subtrac/trac-plugins/advancedticketworkflowplugin/setup.cfg +3 -0
  37. data/lib/subtrac/trac-plugins/advancedticketworkflowplugin/setup.py +20 -0
  38. data/lib/subtrac/trac-plugins/clientsplugin/clients/__init__.py +0 -0
  39. data/lib/subtrac/trac-plugins/clientsplugin/clients/action.py +28 -0
  40. data/lib/subtrac/trac-plugins/clientsplugin/clients/action_email.py +168 -0
  41. data/lib/subtrac/trac-plugins/clientsplugin/clients/action_zendesk_forum.py +137 -0
  42. data/lib/subtrac/trac-plugins/clientsplugin/clients/admin.py +91 -0
  43. data/lib/subtrac/trac-plugins/clientsplugin/clients/api.py +199 -0
  44. data/lib/subtrac/trac-plugins/clientsplugin/clients/client.py +105 -0
  45. data/lib/subtrac/trac-plugins/clientsplugin/clients/events.py +287 -0
  46. data/lib/subtrac/trac-plugins/clientsplugin/clients/eventsadmin.py +71 -0
  47. data/lib/subtrac/trac-plugins/clientsplugin/clients/htdocs/clients.css +4 -0
  48. data/lib/subtrac/trac-plugins/clientsplugin/clients/model.py +135 -0
  49. data/lib/subtrac/trac-plugins/clientsplugin/clients/processor.py +70 -0
  50. data/lib/subtrac/trac-plugins/clientsplugin/clients/reportmanager.py +142 -0
  51. data/lib/subtrac/trac-plugins/clientsplugin/clients/reports.py +231 -0
  52. data/lib/subtrac/trac-plugins/clientsplugin/clients/summary.py +27 -0
  53. data/lib/subtrac/trac-plugins/clientsplugin/clients/summary_milestone.py +152 -0
  54. data/lib/subtrac/trac-plugins/clientsplugin/clients/summary_ticketchanges.py +160 -0
  55. data/lib/subtrac/trac-plugins/clientsplugin/clients/templates/admin_client_events.html +124 -0
  56. data/lib/subtrac/trac-plugins/clientsplugin/clients/templates/admin_clients.html +134 -0
  57. data/lib/subtrac/trac-plugins/clientsplugin/cron/changes.xslt +132 -0
  58. data/lib/subtrac/trac-plugins/clientsplugin/cron/run-client-event +97 -0
  59. data/lib/subtrac/trac-plugins/clientsplugin/cron/summary.xslt +161 -0
  60. data/lib/subtrac/trac-plugins/clientsplugin/setup.py +43 -0
  61. data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/__init__.py +4 -0
  62. data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/burndownchart.py +273 -0
  63. data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/hoursinplaceeditor.py +44 -0
  64. data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/hoursremaining.py +36 -0
  65. data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/htdocs/jquery-1.2.3.min.js +32 -0
  66. data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/htdocs/jquery.jeditable.js +409 -0
  67. data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/htdocs/jquery.jeditable.mini.js +30 -0
  68. data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/templates/edithours.html +53 -0
  69. data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/tests/burndownchart.py +181 -0
  70. data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/tests/hoursremaining.py +66 -0
  71. data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/tests/workloadchart.py +47 -0
  72. data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/utils.py +93 -0
  73. data/lib/subtrac/trac-plugins/estimationtoolsplugin/estimationtools/workloadchart.py +86 -0
  74. data/lib/subtrac/trac-plugins/estimationtoolsplugin/setup.py +20 -0
  75. data/lib/subtrac/trac-plugins/timingandestimationplugin/scripts/SumRollups.js +23 -0
  76. data/lib/subtrac/trac-plugins/timingandestimationplugin/scripts/adw_tracdb.py +128 -0
  77. data/lib/subtrac/trac-plugins/timingandestimationplugin/scripts/git-post-receive +40 -0
  78. data/lib/subtrac/trac-plugins/timingandestimationplugin/scripts/trac-post-commit.py +285 -0
  79. data/lib/subtrac/trac-plugins/timingandestimationplugin/scripts/trac_billing.py +173 -0
  80. data/lib/subtrac/trac-plugins/timingandestimationplugin/scripts/utils/__init__.py +0 -0
  81. data/lib/subtrac/trac-plugins/timingandestimationplugin/scripts/utils/mail.py +164 -0
  82. data/lib/subtrac/trac-plugins/timingandestimationplugin/setup.py +69 -0
  83. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/__init__.py +1 -0
  84. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/api.py +292 -0
  85. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/blackmagic.py +172 -0
  86. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/dbhelper.py +178 -0
  87. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/billingplugin.css +25 -0
  88. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/field_disabler.js +6 -0
  89. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/formatDate.js +356 -0
  90. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/js/tip_centerwindow.js +100 -0
  91. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/js/tip_followscroll.js +84 -0
  92. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/js/wz_tooltip.js +1149 -0
  93. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/linkifyer.js +119 -0
  94. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/query.js +73 -0
  95. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/htdocs/ticket.js +165 -0
  96. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/query_webui.py +28 -0
  97. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/reportmanager.py +221 -0
  98. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/reports.py +675 -0
  99. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/reports_filter.py +150 -0
  100. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/statuses.py +25 -0
  101. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/tande_filters.py +131 -0
  102. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/templates/billing.cs +84 -0
  103. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/templates/billing.html +104 -0
  104. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/ticket_daemon.py +194 -0
  105. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/ticket_policy.py +62 -0
  106. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/ticket_webui.py +28 -0
  107. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/usermanual.py +127 -0
  108. data/lib/subtrac/trac-plugins/timingandestimationplugin/timingandestimationplugin/webui.py +129 -0
  109. data/lib/subtrac/trac-plugins/worklogplugin/setup.py +29 -0
  110. data/lib/subtrac/trac-plugins/worklogplugin/worklog/__init__.py +1 -0
  111. data/lib/subtrac/trac-plugins/worklogplugin/worklog/api.py +187 -0
  112. data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/jqModal.css +40 -0
  113. data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/jqModal.js +67 -0
  114. data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/jquery.mousewheel.pack.js +12 -0
  115. data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/jquery.timeentry.pack.js +7 -0
  116. data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/tracWorklog.js +40 -0
  117. data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/ui.datepicker.css +208 -0
  118. data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/ui.datepicker.js +1439 -0
  119. data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/work.png +0 -0
  120. data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/work.xcf +0 -0
  121. data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/worklogplugin.css +80 -0
  122. data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/workstart.png +0 -0
  123. data/lib/subtrac/trac-plugins/worklogplugin/worklog/htdocs/workstop.png +0 -0
  124. data/lib/subtrac/trac-plugins/worklogplugin/worklog/manager.py +336 -0
  125. data/lib/subtrac/trac-plugins/worklogplugin/worklog/reports.py +598 -0
  126. data/lib/subtrac/trac-plugins/worklogplugin/worklog/templates/worklog.html +45 -0
  127. data/lib/subtrac/trac-plugins/worklogplugin/worklog/templates/worklog_stop.html +70 -0
  128. data/lib/subtrac/trac-plugins/worklogplugin/worklog/templates/worklog_user.html +40 -0
  129. data/lib/subtrac/trac-plugins/worklogplugin/worklog/templates/worklog_webadminui.html +59 -0
  130. data/lib/subtrac/trac-plugins/worklogplugin/worklog/ticket_daemon.py +33 -0
  131. data/lib/subtrac/trac-plugins/worklogplugin/worklog/ticket_filter.py +153 -0
  132. data/lib/subtrac/trac-plugins/worklogplugin/worklog/timeline_hook.py +96 -0
  133. data/lib/subtrac/trac-plugins/worklogplugin/worklog/usermanual.py +29 -0
  134. data/lib/subtrac/trac-plugins/worklogplugin/worklog/util.py +31 -0
  135. data/lib/subtrac/trac-plugins/worklogplugin/worklog/webadminui.py +47 -0
  136. data/lib/subtrac/trac-plugins/worklogplugin/worklog/webui.py +174 -0
  137. data/lib/subtrac/trac-plugins/worklogplugin/worklog/xmlrpc.py +73 -0
  138. data/lib/subtrac/version.rb +4 -0
  139. metadata +191 -0
@@ -0,0 +1,80 @@
1
+ #worklogmanual
2
+ {
3
+ display: block;
4
+ float: right;
5
+ }
6
+ #worklog_comment
7
+ {
8
+ font-size: 70%;
9
+ color: green;
10
+ }
11
+ #worklog_time_delta
12
+ {
13
+ font-size: 70%;
14
+ color: green;
15
+ }
16
+ table#worklog_report
17
+ {
18
+ width: 99%;
19
+ clear: both;
20
+ }
21
+ #content.worklog table th
22
+ {
23
+ background: #f7f7f7;
24
+ border-bottom: 1px solid #d7d7d7;
25
+ font-size: 105%;
26
+ font-weight: bold;
27
+ text-align: left;
28
+ padding: 5px;
29
+ }
30
+ #content.worklog table td
31
+ {
32
+ padding: 5px;
33
+ border: 1px solid #eee;
34
+ }
35
+ #content.worklog table td small
36
+ {
37
+ color: #999;
38
+ }
39
+ .message
40
+ {
41
+ font-weight:bold;
42
+ color:#283;
43
+ }
44
+ dt.workstart, dt.workstart a
45
+ {
46
+ background-image: url(workstart.png);
47
+ }
48
+ dt.workstop, dt.workstop a
49
+ {
50
+ background-image: url(workstop.png);
51
+ }
52
+
53
+
54
+ /* Ticket Page */
55
+ #worklogTicketForm
56
+ {
57
+ float: right;
58
+ }
59
+ fieldset.workloginfo ul
60
+ {
61
+ margin: 0;
62
+ padding: 0;
63
+ padding-left: 3em;
64
+ font-size: 11px;
65
+ list-style: url(work.png) disc;
66
+ }
67
+
68
+ html .jqmWindow {
69
+ margin-left: -250px;
70
+ width: 500px;
71
+
72
+ background-color: #fff;
73
+ border: 1px solid black;
74
+ padding: 12px;
75
+ }
76
+
77
+ textarea#worklogComment {
78
+ width: 100%;
79
+ margin: 2px 0;
80
+ }
@@ -0,0 +1,336 @@
1
+ from time import time
2
+ from datetime import tzinfo, timedelta, datetime
3
+ from util import pretty_timedelta
4
+ from trac.ticket.notification import TicketNotifyEmail
5
+ from trac.ticket import Ticket
6
+ from trac.ticket.web_ui import TicketModule
7
+ from trac.util.datefmt import format_date, format_time, to_datetime
8
+
9
+ class WorkLogManager:
10
+ env = None
11
+ config = None
12
+ authname = None
13
+ explanation = None
14
+ now = None
15
+
16
+ def __init__(self, env, config, authname='anonymous'):
17
+ self.env = env
18
+ self.config = config
19
+ self.authname = authname
20
+ self.explanation = ""
21
+ self.now = int(time()) - 1
22
+
23
+ def get_explanation(self):
24
+ return self.explanation
25
+
26
+ def can_work_on(self, ticket):
27
+ # Need to check several things.
28
+ # 1. Is some other user working on this ticket?
29
+ # 2. a) Is the autostopstart setting true? or
30
+ # b) Is the user working on a ticket already?
31
+ # 3. a) Is the autoreassignaccept setting true? or
32
+ # b) Is the ticket assigned to the user?
33
+
34
+ # 0. Are you logged in?
35
+ if self.authname == 'anonymous':
36
+ self.explanation = 'You need to be logged in to work on tickets.'
37
+ return False
38
+
39
+ # 1. Other user working on it?
40
+ who,since = self.who_is_working_on(ticket)
41
+ if who:
42
+ if who != self.authname:
43
+ self.explanation = 'Another user (%s) has been working on ticket #%s since %s' % (who, ticket, since)
44
+ else:
45
+ self.explanation = 'You are already working on ticket #%s' % (ticket,)
46
+ return False
47
+
48
+ # 2. a) Is the autostopstart setting true? or
49
+ # b) Is the user working on a ticket already?
50
+ if not self.config.getbool('worklog', 'autostopstart'):
51
+ active = self.get_active_task()
52
+ if active:
53
+ self.explanation = 'You cannot work on ticket #%s as you are currently working on ticket #%s. You have to chill out.' % (ticket, active['ticket'])
54
+ return False
55
+
56
+ # 3. a) Is the autoreassignaccept setting true? or
57
+ # b) Is the ticket assigned to the user?
58
+ if not self.config.getbool('worklog', 'autoreassignaccept'):
59
+ tckt = Ticket(self.env, ticket)
60
+ if self.authname != tckt['owner']:
61
+ self.explanation = 'You cannot work on ticket #%s as you are not the owner. You should speak to %s.' % (ticket, tckt['owner'])
62
+ return False
63
+
64
+ # If we get here then we know we can start work :)
65
+ return True
66
+
67
+ def save_ticket(self, tckt, db, msg):
68
+ # determine sequence number...
69
+ cnum = 0
70
+ tm = TicketModule(self.env)
71
+ for change in tm.grouped_changelog_entries(tckt, db):
72
+ if change['permanent']:
73
+ cnum += 1
74
+ nowdt = self.now
75
+ nowdt = to_datetime(nowdt)
76
+ tckt.save_changes(self.authname, msg, nowdt, db, cnum+1)
77
+ ## Often the time overlaps and causes a db error,
78
+ ## especially when the trac integration post-commit hook is used.
79
+ ## NOTE TO SELF. I DON'T THINK THIS IS NECESSARY RIGHT NOW...
80
+ #count = 0
81
+ #while count < 10:
82
+ # try:
83
+ # tckt.save_changes(self.authname, msg, self.now, db, cnum+1)
84
+ # count = 42
85
+ # except Exception, e:
86
+ # self.now += 1
87
+ # count += 1
88
+ db.commit()
89
+
90
+ tn = TicketNotifyEmail(self.env)
91
+ tn.notify(tckt, newticket=0, modtime=nowdt)
92
+ # We fudge time as it has to be unique
93
+ self.now += 1
94
+
95
+
96
+ def start_work(self, ticket):
97
+
98
+ if not self.can_work_on(ticket):
99
+ return False
100
+
101
+ # We could just horse all the fields of the ticket to the right values
102
+ # bit it seems more correct to follow the in-build state-machine for
103
+ # ticket modification.
104
+
105
+ # If the ticket is closed, we need to reopen it.
106
+ db = self.env.get_db_cnx()
107
+ tckt = Ticket(self.env, ticket, db)
108
+
109
+ if 'closed' == tckt['status']:
110
+ tckt['status'] = 'reopened'
111
+ tckt['resolution'] = ''
112
+ self.save_ticket(tckt, db, 'Automatically reopening in order to start work.')
113
+
114
+ # Reinitialise for next test
115
+ db = self.env.get_db_cnx()
116
+ tckt = Ticket(self.env, ticket, db)
117
+
118
+
119
+ if self.authname != tckt['owner']:
120
+ tckt['owner'] = self.authname
121
+ if 'new' == tckt['status']:
122
+ tckt['status'] = 'assigned'
123
+ else:
124
+ tckt['status'] = 'new'
125
+ self.save_ticket(tckt, db, 'Automatically reassigning in order to start work.')
126
+
127
+ # Reinitialise for next test
128
+ db = self.env.get_db_cnx()
129
+ tckt = Ticket(self.env, ticket, db)
130
+
131
+
132
+ if 'assigned' != tckt['status']:
133
+ tckt['status'] = 'assigned'
134
+ self.save_ticket(tckt, db, 'Automatically accepting in order to start work.')
135
+
136
+ # There is a chance the user may be working on another ticket at the moment
137
+ # depending on config options
138
+ if self.config.getbool('worklog', 'autostopstart'):
139
+ # Don't care if this fails, as with these arguments the only failure
140
+ # point is if there is no active task... which is the desired scenario :)
141
+ self.stop_work()
142
+ self.explanation = ''
143
+
144
+ cursor = db.cursor()
145
+ cursor.execute('INSERT INTO work_log (worker, ticket, lastchange, starttime, endtime) '
146
+ 'VALUES (%s, %s, %s, %s, %s)',
147
+ (self.authname, ticket, self.now, self.now, 0))
148
+ db.commit()
149
+ return True
150
+
151
+
152
+ def stop_work(self, stoptime=None, comment=''):
153
+ active = self.get_active_task()
154
+ if not active:
155
+ self.explanation = 'You cannot stop working as you appear to be a complete slacker already!'
156
+ return False
157
+
158
+ if stoptime:
159
+ if stoptime <= active['starttime']:
160
+ self.explanation = 'You cannot set your stop time to that value as it is before the start time!'
161
+ return False
162
+ elif stoptime >= self.now:
163
+ self.explanation = 'You cannot set your stop time to that value as it is in the future!'
164
+ return False
165
+ else:
166
+ stoptime = self.now - 1
167
+
168
+ stoptime = float(stoptime)
169
+
170
+ db = self.env.get_db_cnx();
171
+ cursor = db.cursor()
172
+ cursor.execute('UPDATE work_log '
173
+ 'SET endtime=%s, lastchange=%s, comment=%s '
174
+ 'WHERE worker=%s AND lastchange=%s AND endtime=0',
175
+ (stoptime, stoptime, comment, self.authname, active['lastchange']))
176
+ db.commit()
177
+
178
+ message = ''
179
+ # Leave a comment if the user has configured this or if they have entered
180
+ # a work log comment.
181
+ if self.config.getbool('worklog', 'comment') or comment:
182
+ started = datetime.fromtimestamp(active['starttime'])
183
+ finished = datetime.fromtimestamp(stoptime)
184
+ message = '%s worked on this ticket for %s between %s %s and %s %s.' % \
185
+ (self.authname, pretty_timedelta(started, finished), \
186
+ format_date(active['starttime']), format_time(active['starttime']), \
187
+ format_date(stoptime), format_time(stoptime))
188
+ if comment:
189
+ message += "\n[[BR]]\n" + comment
190
+
191
+ if self.config.getbool('worklog', 'timingandestimation') and \
192
+ self.config.get('ticket-custom', 'hours'):
193
+ if not message:
194
+ message = 'Hours recorded automatically by the worklog plugin.'
195
+
196
+ round_delta = float(self.config.getint('worklog', 'roundup') or 1)
197
+
198
+ # Get the delta in minutes
199
+ delta = float(int(stoptime) - int(active['starttime'])) / float(60)
200
+
201
+ # Round up if needed
202
+ delta = int(round((delta / round_delta) + float(0.5))) * int(round_delta)
203
+
204
+ db = self.env.get_db_cnx()
205
+ tckt = Ticket(self.env, active['ticket'], db)
206
+
207
+ # This hideous hack is here because I don't yet know how to do variable-DP rounding in python - sorry!
208
+ # It's meant to round to 2 DP, so please replace it if you know how. Many thanks, MK.
209
+ tckt['hours'] = str(float(int(100 * float(delta) / 60) / 100.0))
210
+ self.save_ticket(tckt, db, message)
211
+ message = ''
212
+
213
+ if message:
214
+ db = self.env.get_db_cnx()
215
+ tckt = Ticket(self.env, active['ticket'], db)
216
+ self.save_ticket(tckt, db, message)
217
+
218
+ return True
219
+
220
+
221
+ def who_is_working_on(self, ticket):
222
+ db = self.env.get_db_cnx()
223
+ cursor = db.cursor()
224
+ cursor.execute('SELECT worker,starttime FROM work_log WHERE ticket=%s AND endtime=0', (ticket,))
225
+ try:
226
+ who,since = cursor.fetchone()
227
+ return who,float(since)
228
+ except:
229
+ pass
230
+ return None,None
231
+
232
+ def who_last_worked_on(self, ticket):
233
+ return "Not implemented"
234
+
235
+ def get_latest_task(self):
236
+ if self.authname == 'anonymous':
237
+ return None
238
+
239
+ db = self.env.get_db_cnx()
240
+ cursor = db.cursor()
241
+ cursor.execute('SELECT MAX(lastchange) FROM work_log WHERE worker=%s', (self.authname,))
242
+ row = cursor.fetchone()
243
+ if not row or not row[0]:
244
+ return None
245
+
246
+ lastchange = row[0]
247
+
248
+ task = {}
249
+ cursor.execute('SELECT wl.worker, wl.ticket, t.summary, wl.lastchange, wl.starttime, wl.endtime, wl.comment '
250
+ 'FROM work_log wl '
251
+ 'LEFT JOIN ticket t ON wl.ticket=t.id '
252
+ 'WHERE wl.worker=%s AND wl.lastchange=%s', (self.authname, lastchange))
253
+
254
+ for user,ticket,summary,lastchange,starttime,endtime,comment in cursor:
255
+ if not comment:
256
+ comment = ''
257
+
258
+ task['user'] = user
259
+ task['ticket'] = ticket
260
+ task['summary'] = summary
261
+ task['lastchange'] = float(lastchange)
262
+ task['starttime'] = float(starttime)
263
+ task['endtime'] = float(endtime)
264
+ task['comment'] = comment
265
+ return task
266
+
267
+ def get_active_task(self):
268
+ task = self.get_latest_task()
269
+ if not task:
270
+ return None
271
+ if not task.has_key('endtime'):
272
+ return None
273
+
274
+ if task['endtime'] > 0:
275
+ return None
276
+
277
+ return task
278
+
279
+ def get_work_log(self, mode='all'):
280
+ db = self.env.get_db_cnx()
281
+ cursor = db.cursor()
282
+ if mode == 'user':
283
+ cursor.execute('SELECT wl.worker, s.value, wl.starttime, wl.endtime, wl.ticket, t.summary, t.status, wl.comment '
284
+ 'FROM work_log wl '
285
+ 'INNER JOIN ticket t ON wl.ticket=t.id '
286
+ 'LEFT JOIN session_attribute s ON wl.worker=s.sid AND s.name=\'name\' '
287
+ 'WHERE wl.worker=%s '
288
+ 'ORDER BY wl.lastchange DESC', (self.authname,))
289
+ elif mode == 'summary':
290
+ cursor.execute('SELECT wl.worker, s.value, wl.starttime, wl.endtime, wl.ticket, t.summary, t.status, wl.comment '
291
+ 'FROM (SELECT worker,MAX(lastchange) AS lastchange FROM work_log GROUP BY worker) wlt '
292
+ 'INNER JOIN work_log wl ON wlt.worker=wl.worker AND wlt.lastchange=wl.lastchange '
293
+ 'INNER JOIN ticket t ON wl.ticket=t.id '
294
+ 'LEFT JOIN session_attribute s ON wl.worker=s.sid AND s.name=\'name\' '
295
+ 'ORDER BY wl.lastchange DESC, wl.worker')
296
+ else:
297
+ cursor.execute('SELECT wl.worker, s.value, wl.starttime, wl.endtime, wl.ticket, t.summary, t.status, wl.comment '
298
+ 'FROM work_log wl '
299
+ 'INNER JOIN ticket t ON wl.ticket=t.id '
300
+ 'LEFT JOIN session_attribute s ON wl.worker=s.sid AND s.name=\'name\' '
301
+ 'ORDER BY wl.lastchange DESC, wl.worker')
302
+
303
+ rv = []
304
+ for user,name,starttime,endtime,ticket,summary,status,comment in cursor:
305
+ starttime = float(starttime)
306
+ endtime = float(endtime)
307
+
308
+ started = datetime.fromtimestamp(starttime)
309
+
310
+ dispname = user
311
+ if name:
312
+ dispname = '%s (%s)' % (name, user)
313
+
314
+ if not endtime == 0:
315
+ finished = datetime.fromtimestamp(endtime)
316
+ delta = 'Worked for %s (between %s %s and %s %s)' % \
317
+ (pretty_timedelta(started, finished),
318
+ format_date(starttime), format_time(starttime),
319
+ format_date(endtime), format_time(endtime))
320
+ else:
321
+ delta = 'Started %s ago (%s %s)' % \
322
+ (pretty_timedelta(started),
323
+ format_date(starttime), format_time(starttime))
324
+
325
+ rv.append({'user': user,
326
+ 'name': name,
327
+ 'dispname': dispname,
328
+ 'starttime': int(starttime),
329
+ 'endtime': int(endtime),
330
+ 'delta': delta,
331
+ 'ticket': ticket,
332
+ 'summary': summary,
333
+ 'status': status,
334
+ 'comment': comment})
335
+ return rv
336
+
@@ -0,0 +1,598 @@
1
+ # IF YOU ADD A NEW SECTION OF REPORTS, You will need to make
2
+ # sure that section is also added to the all_reports hashtable
3
+ # near the bottom
4
+
5
+ #Please try to keep this clean"
6
+
7
+ billing_reports = [
8
+ {
9
+ "title":"Ticket Work Summary",
10
+ "reportnumber":None,
11
+ "version":12,
12
+ "sql":"""
13
+ SELECT __ticket__ as __group__, __style__, __ticket__,
14
+ newvalue as Work_added, author, time, _ord
15
+ FROM(
16
+ SELECT '' as __style__, author, t.id as __ticket__,
17
+ CAST(newvalue as REAL) as newvalue, ticket_change.time as time, 0 as _ord
18
+ FROM ticket_change
19
+ JOIN ticket t on t.id = ticket_change.ticket
20
+ LEFT JOIN ticket_custom as billable on billable.ticket = t.id
21
+ and billable.name = 'billable'
22
+ WHERE field = 'hours' and
23
+ t.status IN ($NEW, $ASSIGNED, $REOPENED, $CLOSED)
24
+ AND billable.value in ($BILLABLE, $UNBILLABLE)
25
+ AND ticket_change.time >= $STARTDATE
26
+ AND ticket_change.time < $ENDDATE
27
+
28
+ UNION
29
+
30
+ SELECT 'background-color:#DFE;' as __style__,
31
+ 'Total work done on the ticket in the selected time period ' as author,
32
+ t.id as __ticket__, sum( CAST(newvalue as real) ) as newvalue,
33
+ NULL as time, 1 as _ord
34
+ FROM ticket_change
35
+ JOIN ticket t on t.id = ticket_change.ticket
36
+ LEFT JOIN ticket_custom as billable on billable.ticket = t.id
37
+ and billable.name = 'billable'
38
+ WHERE field = 'hours' and
39
+ t.status IN ($NEW, $ASSIGNED, $REOPENED, $CLOSED)
40
+ AND billable.value in ($BILLABLE, $UNBILLABLE)
41
+ AND ticket_change.time >= $STARTDATE
42
+ AND ticket_change.time < $ENDDATE
43
+ GROUP By t.id
44
+ ) as tbl
45
+ ORDER BY __ticket__, _ord ASC, time ASC
46
+
47
+ """
48
+ },#END Ticket work summary
49
+ {
50
+ "title":"Milestone Work Summary",
51
+ "reportnumber":None,
52
+ "version":11,
53
+ "sql":"""
54
+
55
+ SELECT
56
+ milestone as __group__, __style__, ticket, summary, newvalue as Work_added,
57
+ time, _ord
58
+ FROM(
59
+ SELECT '' as __style__, t.id as ticket,
60
+ SUM(CAST(newvalue as real)) as newvalue, t.summary as summary,
61
+ MAX(ticket_change.time) as time, t.milestone as milestone, 0 as _ord
62
+ FROM ticket_change
63
+ JOIN ticket t on t.id = ticket_change.ticket
64
+ LEFT JOIN ticket_custom as billable on billable.ticket = t.id
65
+ and billable.name = 'billable'
66
+ WHERE field = 'hours' and
67
+ t.status IN ($NEW, $ASSIGNED, $REOPENED, $CLOSED)
68
+ AND billable.value in ($BILLABLE, $UNBILLABLE)
69
+ AND ticket_change.time >= $STARTDATE
70
+ AND ticket_change.time < $ENDDATE
71
+ GROUP BY t.milestone, t.id, t.summary
72
+
73
+ UNION
74
+
75
+ SELECT 'background-color:#DFE;' as __style__, NULL as ticket,
76
+ sum(CAST(newvalue as real)) as newvalue, 'Total work done' as summary,
77
+ NULL as time, t.milestone as milestone, 1 as _ord
78
+ FROM ticket_change
79
+ JOIN ticket t on t.id = ticket_change.ticket
80
+ LEFT JOIN ticket_custom as billable on billable.ticket = t.id
81
+ and billable.name = 'billable'
82
+ WHERE field = 'hours' and
83
+ t.status IN ($NEW, $ASSIGNED, $REOPENED, $CLOSED)
84
+ AND billable.value in ($BILLABLE, $UNBILLABLE)
85
+ AND ticket_change.time >= $STARTDATE
86
+ AND ticket_change.time < $ENDDATE
87
+ GROUP By t.milestone
88
+ ) as tbl
89
+ ORDER BY milestone, _ord ASC, ticket, time
90
+
91
+
92
+
93
+ """
94
+ },#END Milestone work summary
95
+
96
+ {
97
+ "title":"Developer Work Summary",
98
+ "reportnumber":None,
99
+ "version":11,
100
+ "sql":"""
101
+ SELECT author as __group__,__style__, ticket,
102
+ newvalue as Work_added, time as time, _ord
103
+ FROM(
104
+ SELECT '' as __style__, author, cast(t.id as text) as ticket,
105
+ CAST(newvalue as REAL) as newvalue, ticket_change.time as time, 0 as _ord
106
+ FROM ticket_change
107
+ JOIN ticket t on t.id = ticket_change.ticket
108
+ LEFT JOIN ticket_custom as billable on billable.ticket = t.id
109
+ and billable.name = 'billable'
110
+ WHERE field = 'hours' and
111
+ t.status IN ($NEW, $ASSIGNED, $REOPENED, $CLOSED)
112
+ AND billable.value in ($BILLABLE, $UNBILLABLE)
113
+ AND ticket_change.time >= $STARTDATE
114
+ AND ticket_change.time < $ENDDATE
115
+
116
+ UNION
117
+
118
+ SELECT 'background-color:#DFE;' as __style__, author, NULL as ticket,
119
+ sum(CAST(newvalue as real)) as newvalue, NULL as time, 1 as _ord
120
+ FROM ticket_change
121
+ JOIN ticket t on t.id = ticket_change.ticket
122
+ LEFT JOIN ticket_custom as billable on billable.ticket = t.id
123
+ and billable.name = 'billable'
124
+ WHERE field = 'hours' and
125
+ t.status IN ($NEW, $ASSIGNED, $REOPENED, $CLOSED)
126
+ AND billable.value in ($BILLABLE, $UNBILLABLE)
127
+ AND ticket_change.time >= $STARTDATE
128
+ AND ticket_change.time < $ENDDATE
129
+ GROUP By author
130
+ ) as tbl
131
+ ORDER BY author, _ord ASC, time
132
+
133
+ """
134
+ },#END Hours Per Developer
135
+ ]
136
+ ticket_hours_reports = [
137
+ {
138
+ "title": "Ticket Hours",
139
+ "reportnumber": None,
140
+ "version":8,
141
+ "sql": """
142
+ SELECT __color__, __style__, ticket, summary, component ,version, severity,
143
+ milestone, status, owner, Estimated_work, Total_work, billable,_ord
144
+ FROM (
145
+ SELECT p.value AS __color__,
146
+ '' as __style__,
147
+ t.id AS ticket, summary AS summary, -- ## Break line here
148
+ component,version, severity, milestone, status, owner,
149
+ CAST(EstimatedHours.value as REAL) as Estimated_work,
150
+ CAST(totalhours.value as REAL) as Total_work,
151
+ CASE WHEN billable.value = 1 THEN 'Y' else 'N' END as billable,
152
+ time AS created, changetime AS modified, -- ## Dates are formatted
153
+ description AS _description_, -- ## Uses a full row
154
+ changetime AS _changetime,
155
+ reporter AS _reporter
156
+ ,0 as _ord
157
+
158
+ FROM ticket as t
159
+ JOIN enum as p ON p.name=t.priority AND p.type='priority'
160
+
161
+ LEFT JOIN ticket_custom as EstimatedHours ON EstimatedHours.name='estimatedhours'
162
+ AND EstimatedHours.Ticket = t.Id
163
+ LEFT JOIN ticket_custom as totalhours ON totalhours.name='totalhours'
164
+ AND totalhours.Ticket = t.Id
165
+ LEFT JOIN ticket_custom as billable ON billable.name='billable'
166
+ AND billable.Ticket = t.Id
167
+
168
+ WHERE t.status IN ($NEW, $ASSIGNED, $REOPENED, $CLOSED)
169
+ AND billable.value in ($BILLABLE, $UNBILLABLE)
170
+
171
+
172
+ UNION
173
+
174
+ SELECT '1' AS __color__,
175
+ 'background-color:#DFE;' as __style__,
176
+ NULL as ticket, 'Total' AS summary,
177
+ NULL as component,NULL as version, NULL as severity, NULL as milestone, NULL as status, NULL as owner,
178
+ SUM(CAST(EstimatedHours.value as real)) as Estimated_work,
179
+ SUM(CAST(totalhours.value as real)) as Total_work,
180
+ NULL as billable,
181
+ NULL as created, NULL as modified, -- ## Dates are formatted
182
+
183
+ NULL AS _description_,
184
+ NULL AS _changetime,
185
+ NULL AS _reporter
186
+ ,1 as _ord
187
+ FROM ticket as t
188
+ JOIN enum as p ON p.name=t.priority AND p.type='priority'
189
+
190
+ LEFT JOIN ticket_custom as EstimatedHours ON EstimatedHours.name='estimatedhours'
191
+ AND EstimatedHours.Ticket = t.Id
192
+
193
+ LEFT JOIN ticket_custom as totalhours ON totalhours.name='totalhours'
194
+ AND totalhours.Ticket = t.Id
195
+
196
+ LEFT JOIN ticket_custom as billable ON billable.name='billable'
197
+ AND billable.Ticket = t.Id
198
+
199
+ WHERE t.status IN ($NEW, $ASSIGNED, $REOPENED, $CLOSED)
200
+ AND billable.value in ($BILLABLE, $UNBILLABLE)
201
+ ) as tbl
202
+ ORDER BY _ord ASC, ticket
203
+ """
204
+ },
205
+ #END Ticket Hours
206
+ {
207
+ "title": "Ticket Hours with Description",
208
+ "reportnumber": None,
209
+ "version":9,
210
+ "sql": """
211
+ SELECT __color__, __style__, ticket, summary, component ,version, severity,
212
+ milestone, status, owner, Estimated_work, Total_work, billable
213
+ --,created, modified, -- ## Dates are formatted
214
+ ,_description_
215
+ -- _changetime,
216
+ -- _reporter
217
+ ,_ord
218
+
219
+ FROM (
220
+ SELECT p.value AS __color__,
221
+ '' as __style__,
222
+ t.id AS ticket, summary AS summary, -- ## Break line here
223
+ component,version, severity, milestone, status, owner,
224
+ CAST(EstimatedHours.value as REAL) as Estimated_work,
225
+ CAST(totalhours.value as REAL) as Total_work,
226
+ CASE WHEN billable.value = 1 THEN 'Y'
227
+ else 'N'
228
+ END as billable,
229
+ time AS created, changetime AS modified, -- ## Dates are formatted
230
+ description AS _description_, -- ## Uses a full row
231
+ changetime AS _changetime,
232
+ reporter AS _reporter
233
+ ,0 as _ord
234
+
235
+ FROM ticket as t
236
+ JOIN enum as p ON p.name=t.priority AND p.type='priority'
237
+
238
+ LEFT JOIN ticket_custom as EstimatedHours ON EstimatedHours.name='estimatedhours'
239
+ AND EstimatedHours.Ticket = t.Id
240
+ LEFT JOIN ticket_custom as totalhours ON totalhours.name='totalhours'
241
+ AND totalhours.Ticket = t.Id
242
+ LEFT JOIN ticket_custom as billable ON billable.name='billable'
243
+ AND billable.Ticket = t.Id
244
+
245
+ WHERE t.status IN ($NEW, $ASSIGNED, $REOPENED, $CLOSED)
246
+ AND billable.value in ($BILLABLE, $UNBILLABLE)
247
+
248
+
249
+ UNION
250
+
251
+ SELECT '1' AS __color__,
252
+ 'background-color:#DFE;' as __style__,
253
+ NULL as ticket, 'Total' AS summary,
254
+ NULL as component,NULL as version, NULL as severity, NULL as milestone, NULL as status, NULL as owner,
255
+ SUM(CAST(EstimatedHours.value as real)) as Estimated_work,
256
+ SUM(CAST(totalhours.value as real)) as Total_work,
257
+ NULL as billable,
258
+ NULL as created, NULL as modified, -- ## Dates are formatted
259
+
260
+ NULL AS _description_,
261
+ NULL AS _changetime,
262
+ NULL AS _reporter
263
+ ,1 as _ord
264
+ FROM ticket as t
265
+ JOIN enum as p ON p.name=t.priority AND p.type='priority'
266
+
267
+ LEFT JOIN ticket_custom as EstimatedHours ON EstimatedHours.name='estimatedhours'
268
+ AND EstimatedHours.Ticket = t.Id
269
+
270
+ LEFT JOIN ticket_custom as totalhours ON totalhours.name='totalhours'
271
+ AND totalhours.Ticket = t.Id
272
+
273
+ LEFT JOIN ticket_custom as billable ON billable.name='billable'
274
+ AND billable.Ticket = t.Id
275
+
276
+ WHERE t.status IN ($NEW, $ASSIGNED, $REOPENED, $CLOSED)
277
+ AND billable.value in ($BILLABLE, $UNBILLABLE)
278
+ ) as tbl
279
+ ORDER BY _ord ASC, ticket
280
+ """
281
+ },
282
+ #END Ticket Hours
283
+
284
+ {
285
+ "title":"Ticket Hours Grouped By Component",
286
+ "reportnumber":None,
287
+ "version":8,
288
+ "sql": """
289
+ SELECT __color__, __group__, __style__, ticket, summary, __component__ ,version,
290
+ severity, milestone, status, owner, Estimated_work, total_work, billable,
291
+ _ord
292
+
293
+ FROM (
294
+ SELECT p.value AS __color__,
295
+ t.component AS __group__,
296
+ '' as __style__,
297
+ t.id AS ticket, summary AS summary, -- ## Break line here
298
+ component as __component__,version, severity, milestone, status, owner,
299
+ CAST(EstimatedHours.value as REAL) as Estimated_work,
300
+ CAST(totalhours.value as REAL) as Total_work,
301
+ CASE WHEN billable.value = 1 THEN 'Y'
302
+ else 'N'
303
+ END as billable,
304
+ time AS created, changetime AS modified, -- ## Dates are formatted
305
+ description AS _description_, -- ## Uses a full row
306
+ changetime AS _changetime,
307
+ reporter AS _reporter
308
+ ,0 as _ord
309
+
310
+ FROM ticket as t
311
+ JOIN enum as p ON p.name=t.priority AND p.type='priority'
312
+
313
+ LEFT JOIN ticket_custom as EstimatedHours ON EstimatedHours.name='estimatedhours'
314
+ AND EstimatedHours.Ticket = t.Id
315
+ LEFT JOIN ticket_custom as totalhours ON totalhours.name='totalhours'
316
+ AND totalhours.Ticket = t.Id
317
+ LEFT JOIN ticket_custom as billable ON billable.name='billable'
318
+ AND billable.Ticket = t.Id
319
+
320
+ WHERE t.status IN ($NEW, $ASSIGNED, $REOPENED, $CLOSED)
321
+ AND billable.value in ($BILLABLE, $UNBILLABLE)
322
+
323
+
324
+ UNION
325
+
326
+ SELECT '1' AS __color__,
327
+ t.component AS __group__,
328
+ 'background-color:#DFE;' as __style__,
329
+ NULL as ticket, 'Total work' AS summary,
330
+ t.component as __component__, NULL as version, NULL as severity,
331
+ NULL as milestone, NULL as status,
332
+ NULL as owner,
333
+ SUM(CAST(EstimatedHours.value as real)) as Estimated_work,
334
+ SUM(CAST(totalhours.value as real)) as Total_work,
335
+ NULL as billable,
336
+ NULL as created,
337
+ NULL as modified, -- ## Dates are formatted
338
+
339
+ NULL AS _description_,
340
+ NULL AS _changetime,
341
+ NULL AS _reporter
342
+ ,1 as _ord
343
+ FROM ticket as t
344
+ JOIN enum as p ON p.name=t.priority AND p.type='priority'
345
+
346
+ LEFT JOIN ticket_custom as EstimatedHours ON EstimatedHours.name='estimatedhours'
347
+ AND EstimatedHours.Ticket = t.Id
348
+
349
+ LEFT JOIN ticket_custom as totalhours ON totalhours.name='totalhours'
350
+ AND totalhours.Ticket = t.Id
351
+
352
+ LEFT JOIN ticket_custom as billable ON billable.name='billable'
353
+ AND billable.Ticket = t.Id
354
+
355
+ WHERE t.status IN ($NEW, $ASSIGNED, $REOPENED, $CLOSED)
356
+ AND billable.value in ($BILLABLE, $UNBILLABLE)
357
+ GROUP BY t.component
358
+ ) as tbl
359
+ ORDER BY __component__, _ord ASC,ticket
360
+ """
361
+ },
362
+ # END Ticket Hours GROUPED BY COMPONENT
363
+
364
+ {
365
+ "title":"Ticket Hours Grouped By Component with Description",
366
+ "reportnumber":None,
367
+ "version":7,
368
+ "sql": """
369
+ SELECT __color__, __group__, __style__, ticket, summary, __component__ ,
370
+ version, severity, milestone, status, owner, Estimated_work, Total_work,
371
+ billable, _description_, _ord
372
+
373
+ FROM (
374
+ SELECT p.value AS __color__,
375
+ t.component AS __group__,
376
+ '' as __style__,
377
+ t.id AS ticket, summary AS summary, -- ## Break line here
378
+ component as __component__, version, severity, milestone, status, owner,
379
+ CAST(EstimatedHours.value as REAL) as Estimated_work,
380
+ CAST(totalhours.value as REAL) as Total_work,
381
+ CASE WHEN billable.value = 1 THEN 'Y' else 'N' END as billable,
382
+ time AS created, changetime AS modified, -- ## Dates are formatted
383
+ description AS _description_, -- ## Uses a full row
384
+ changetime AS _changetime,
385
+ reporter AS _reporter
386
+ ,0 as _ord
387
+
388
+ FROM ticket as t
389
+ JOIN enum as p ON p.name=t.priority AND p.type='priority'
390
+
391
+ LEFT JOIN ticket_custom as EstimatedHours ON EstimatedHours.name='estimatedhours'
392
+ AND EstimatedHours.Ticket = t.Id
393
+ LEFT JOIN ticket_custom as totalhours ON totalhours.name='totalhours'
394
+ AND totalhours.Ticket = t.Id
395
+ LEFT JOIN ticket_custom as billable ON billable.name='billable'
396
+ AND billable.Ticket = t.Id
397
+
398
+ WHERE t.status IN ($NEW, $ASSIGNED, $REOPENED, $CLOSED)
399
+ AND billable.value in ($BILLABLE, $UNBILLABLE)
400
+
401
+
402
+ UNION
403
+
404
+ SELECT '1' AS __color__,
405
+ t.component AS __group__,
406
+ 'background-color:#DFE;' as __style__,
407
+ NULL as ticket, 'Total work' AS summary,
408
+ t.component as __component__, NULL as version, NULL as severity,
409
+ NULL as milestone, NULL as status, NULL as owner,
410
+ SUM(CAST(EstimatedHours.value as real)) as Estimated_work,
411
+ SUM(CAST(totalhours.value as real)) as Total_work,
412
+ NULL as billable,
413
+ NULL as created, NULL as modified, -- ## Dates are formatted
414
+
415
+ NULL AS _description_,
416
+ NULL AS _changetime,
417
+ NULL AS _reporter
418
+ ,1 as _ord
419
+ FROM ticket as t
420
+ JOIN enum as p ON p.name=t.priority AND p.type='priority'
421
+
422
+ LEFT JOIN ticket_custom as EstimatedHours ON EstimatedHours.name='estimatedhours'
423
+ AND EstimatedHours.Ticket = t.Id
424
+
425
+ LEFT JOIN ticket_custom as totalhours ON totalhours.name='totalhours'
426
+ AND totalhours.Ticket = t.Id
427
+
428
+ LEFT JOIN ticket_custom as billable ON billable.name='billable'
429
+ AND billable.Ticket = t.Id
430
+
431
+ WHERE t.status IN ($NEW, $ASSIGNED, $REOPENED, $CLOSED)
432
+ AND billable.value in ($BILLABLE, $UNBILLABLE)
433
+ GROUP BY t.component
434
+ ) as tbl
435
+ ORDER BY __component__, _ord ASC, ticket
436
+ """
437
+ },
438
+ # END Ticket Hours Grouped BY Component with Description
439
+ {
440
+ "title":"Ticket Hours Grouped By Milestone",
441
+ "reportnumber":None,
442
+ "version":8,
443
+ "sql": """
444
+ SELECT __color__, __group__, __style__, ticket, summary, component ,version,
445
+ severity, __milestone__, status, owner, Estimated_work, Total_work, billable,
446
+ _ord
447
+
448
+ FROM (
449
+ SELECT p.value AS __color__,
450
+ t.milestone AS __group__,
451
+ '' as __style__,
452
+ t.id AS ticket, summary AS summary, -- ## Break line here
453
+ component,version, severity, milestone as __milestone__, status, owner,
454
+ CAST(EstimatedHours.value as REAL) as Estimated_work,
455
+ CAST(totalhours.value as REAL) as Total_work,
456
+ CASE WHEN billable.value = 1 THEN 'Y'
457
+ else 'N'
458
+ END as billable,
459
+ time AS created, changetime AS modified, -- ## Dates are formatted
460
+ description AS _description_, -- ## Uses a full row
461
+ changetime AS _changetime,
462
+ reporter AS _reporter, 0 as _ord
463
+
464
+ FROM ticket as t
465
+ JOIN enum as p ON p.name=t.priority AND p.type='priority'
466
+
467
+ LEFT JOIN ticket_custom as EstimatedHours ON EstimatedHours.name='estimatedhours'
468
+ AND EstimatedHours.Ticket = t.Id
469
+ LEFT JOIN ticket_custom as totalhours ON totalhours.name='totalhours'
470
+ AND totalhours.Ticket = t.Id
471
+ LEFT JOIN ticket_custom as billable ON billable.name='billable'
472
+ AND billable.Ticket = t.Id
473
+
474
+ WHERE t.status IN ($NEW, $ASSIGNED, $REOPENED, $CLOSED)
475
+ AND billable.value in ($BILLABLE, $UNBILLABLE)
476
+
477
+
478
+ UNION
479
+
480
+ SELECT '1' AS __color__,
481
+ t.milestone AS __group__,
482
+ 'background-color:#DFE;' as __style__,
483
+ NULL as ticket, 'Total work' AS summary,
484
+ NULL as component,NULL as version, NULL as severity,
485
+ t.milestone as __milestone__, NULL as status, NULL as owner,
486
+ SUM(CAST(EstimatedHours.value as real)) as Estimated_work,
487
+ SUM(CAST(totalhours.value as real)) as Total_work,
488
+ NULL as billable,
489
+ NULL as created, NULL as modified, -- ## Dates are formatted
490
+
491
+ NULL AS _description_,
492
+ NULL AS _changetime,
493
+ NULL AS _reporter
494
+ ,1 as _ord
495
+ FROM ticket as t
496
+ JOIN enum as p ON p.name=t.priority AND p.type='priority'
497
+
498
+ LEFT JOIN ticket_custom as EstimatedHours ON EstimatedHours.name='estimatedhours'
499
+ AND EstimatedHours.Ticket = t.Id
500
+
501
+ LEFT JOIN ticket_custom as totalhours ON totalhours.name='totalhours'
502
+ AND totalhours.Ticket = t.Id
503
+
504
+ LEFT JOIN ticket_custom as billable ON billable.name='billable'
505
+ AND billable.Ticket = t.Id
506
+
507
+ WHERE t.status IN ($NEW, $ASSIGNED, $REOPENED, $CLOSED)
508
+ AND billable.value in ($BILLABLE, $UNBILLABLE)
509
+ GROUP BY t.milestone
510
+ ) as tbl
511
+ ORDER BY __milestone__, _ord ASC, ticket
512
+ """
513
+ },
514
+ #END Ticket Hours Grouped By MileStone
515
+ {
516
+ "title":"Ticket Hours Grouped By MileStone with Description",
517
+ "reportnumber":None,
518
+ "version":8,
519
+ "sql": """
520
+ SELECT __color__, __group__, __style__, ticket, summary, component ,version, severity,
521
+ __milestone__, status, owner, Estimated_work, Total_work, billable,
522
+ _description_, _ord
523
+
524
+ FROM (
525
+ SELECT p.value AS __color__,
526
+ t.milestone AS __group__,
527
+ '' as __style__,
528
+ t.id AS ticket, summary AS summary, -- ## Break line here
529
+ component,version, severity, milestone as __milestone__, status, owner,
530
+ CAST(EstimatedHours.value as REAL) as Estimated_work,
531
+ CAST(totalhours.value as REAL) as Total_work,
532
+ CASE WHEN billable.value = 1 THEN 'Y'
533
+ else 'N'
534
+ END as billable,
535
+ time AS created, changetime AS modified, -- ## Dates are formatted
536
+ description AS _description_, -- ## Uses a full row
537
+ changetime AS _changetime,
538
+ reporter AS _reporter
539
+ ,0 as _ord
540
+
541
+ FROM ticket as t
542
+ JOIN enum as p ON p.name=t.priority AND p.type='priority'
543
+
544
+ LEFT JOIN ticket_custom as EstimatedHours ON EstimatedHours.name='estimatedhours'
545
+ AND EstimatedHours.Ticket = t.Id
546
+ LEFT JOIN ticket_custom as totalhours ON totalhours.name='totalhours'
547
+ AND totalhours.Ticket = t.Id
548
+ LEFT JOIN ticket_custom as billable ON billable.name='billable'
549
+ AND billable.Ticket = t.Id
550
+
551
+ WHERE t.status IN ($NEW, $ASSIGNED, $REOPENED, $CLOSED)
552
+ AND billable.value in ($BILLABLE, $UNBILLABLE)
553
+
554
+
555
+ UNION
556
+
557
+ SELECT '1' AS __color__,
558
+ t.milestone AS __group__,
559
+ 'background-color:#DFE;' as __style__,
560
+ NULL as ticket, 'Total work' AS summary,
561
+ NULL as component,NULL as version, NULL as severity,
562
+ t.milestone as __milestone__,
563
+ NULL as status, NULL as owner,
564
+ SUM(CAST(EstimatedHours.value as real)) as Estimated_work,
565
+ SUM(CAST(totalhours.value as real)) as Total_work,
566
+ NULL as billable,
567
+ NULL as created, NULL as modified, -- ## Dates are formatted
568
+ NULL AS _description_,
569
+ NULL AS _changetime,
570
+ NULL AS _reporter, 1 as _ord
571
+ FROM ticket as t
572
+ JOIN enum as p ON p.name=t.priority AND p.type='priority'
573
+
574
+ LEFT JOIN ticket_custom as EstimatedHours ON EstimatedHours.name='estimatedhours'
575
+ AND EstimatedHours.Ticket = t.Id
576
+
577
+ LEFT JOIN ticket_custom as totalhours ON totalhours.name='totalhours'
578
+ AND totalhours.Ticket = t.Id
579
+
580
+ LEFT JOIN ticket_custom as billable ON billable.name='billable'
581
+ AND billable.Ticket = t.Id
582
+
583
+ WHERE t.status IN ($NEW, $ASSIGNED, $REOPENED, $CLOSED)
584
+ AND billable.value in ($BILLABLE, $UNBILLABLE)
585
+ GROUP BY t.milestone
586
+ ) as tbl
587
+ ORDER BY __milestone__, _ord ASC, ticket
588
+ """
589
+ }
590
+ #END Ticket Hours Grouped By MileStone with Description
591
+ ]
592
+
593
+ all_reports = [
594
+ {"title":"Billing Reports",
595
+ "reports":billing_reports},
596
+ {"title":"Ticket/Hour Reports",
597
+ "reports": ticket_hours_reports}
598
+ ]