source: timingandestimationplugin/branches/trac1.0/timingandestimationplugin/ticket_daemon.py

Last change on this file was 17107, checked in by Russ Tyndall, 5 years ago

1.5.9(b) - fixed Decimal cast in ticket_daemon re #13405

File size: 6.2 KB
Line 
1from trac.ticket import ITicketChangeListener, Ticket, ITicketManipulator
2from trac.core import *
3import datetime
4import dbhelper
5
6def identity(x):
7    return x;
8
9def convertfloat(x):
10    "some european countries use , as the decimal separator"
11    if not x:
12        return 0.0
13    if isinstance(x, float) or isinstance(x, int):
14        return x
15    x = str(x).strip()
16    if len(x) > 0:
17        return float(x.replace(',','.'))
18    else:
19        return 0.0
20
21
22try:
23    import trac.util.datefmt
24    to_timestamp = trac.util.datefmt.to_utimestamp
25except Exception:
26    to_timestamp = identity
27
28
29def save_custom_field_value( db, ticket_id, field, value ):
30    cursor = db.cursor()
31    cursor.execute("UPDATE ticket_custom SET value=%s "
32                   "WHERE ticket=%s AND name=%s", (value, ticket_id, field))
33
34    try:
35        if cursor.rowcount == 0:
36            cursor.execute("INSERT INTO ticket_custom (ticket,name, "
37                           "value) VALUES(%s,%s,%s)",
38                           (ticket_id, field, value))
39    # MySQLdb is not returning the correct rowcount?
40    except:
41        pass
42
43   
44def update_hours_to_floats(db, ticket_id):
45    cursor = db.cursor()
46    cursor.execute("SELECT time, newvalue FROM ticket_change"
47                   " WHERE newvalue like '%,%' AND  ticket=%s AND field='hours'", 
48                   (ticket_id,))
49    data = list(cursor.fetchall())
50    for (time, newvalue) in data:
51        cursor.execute("UPDATE ticket_change SET newvalue=%s "
52                       "WHERE ticket=%s AND time=%s AND field='hours'",
53                       (str(convertfloat(newvalue)), ticket_id, time))
54
55def update_totalhours_custom( db, ticket_id):
56    cursor = db.cursor()
57    sumSql = """
58       (SELECT SUM( CASE WHEN newvalue = '' OR newvalue IS NULL THEN 0
59                         ELSE CAST( newvalue AS DECIMAL(10,2) ) END ) as total
60          FROM ticket_change
61         WHERE ticket=%s and field='hours')  """
62    cursor.execute("UPDATE ticket_custom SET value="+sumSql+
63                   "WHERE ticket=%s AND name='totalhours'",
64               (ticket_id,ticket_id))
65    try:
66        if cursor.rowcount==0:
67            cursor.execute("INSERT INTO ticket_custom (name, value, ticket) "+
68                           "VALUES('totalhours',"+sumSql+",%s)",
69                           (ticket_id,ticket_id))
70    # MySQLdb is not returning the correct rowcount?
71    except:
72        pass
73
74def insert_totalhours_changes( db, ticket_id):
75    sql = """
76       INSERT INTO ticket_change (ticket, author, time, field, oldvalue, newvalue)
77       SELECT ticket, author, time, 'totalhours', 
78               (SELECT SUM( CASE WHEN newvalue = '' OR newvalue IS NULL THEN 0
79                           ELSE CAST( newvalue AS DECIMAL(10,2) ) END ) as total
80               FROM ticket_change as guts
81               WHERE guts.ticket = ticket_change.ticket AND guts.field='hours'
82                 AND guts.time < ticket_change.time
83              ) as oldvalue,
84              (SELECT SUM( CASE WHEN newvalue = '' OR newvalue IS NULL THEN 0
85                           ELSE CAST( newvalue AS DECIMAL(10,2) ) END ) as total
86               FROM ticket_change as guts
87               WHERE guts.ticket = ticket_change.ticket AND guts.field='hours'
88                 AND guts.time <= ticket_change.time
89              ) as newvalue
90          FROM ticket_change
91         WHERE ticket=%s and field='hours'
92           AND NOT EXISTS( SELECT ticket
93                             FROM ticket_change as guts
94                            WHERE guts.ticket=ticket_change.ticket
95                              AND guts.author=ticket_change.author
96                              AND guts.time=ticket_change.time
97                              AND field='totalhours')
98    """
99    cursor = db.cursor()
100    cursor.execute(sql, (ticket_id,))
101
102
103class TimeTrackingTicketObserver(Component):
104    implements(ITicketChangeListener)
105    def __init__(self):
106        pass
107
108    def watch_hours(self, ticket):
109        ticket_id = ticket.id
110        with self.env.db_transaction as db:
111            update_hours_to_floats(db, ticket_id)
112            save_custom_field_value( db, ticket_id, "hours", '0')
113            insert_totalhours_changes( db, ticket_id )
114            update_totalhours_custom ( db, ticket_id )
115
116    def ticket_created(self, ticket):
117        """Called when a ticket is created."""
118        hours = convertfloat(ticket['hours'])
119        # makes the hours a ticket change like all the other hours records
120        if hours > 0:
121            self.watch_hours(ticket) # clears the hours
122            t = Ticket (self.env, ticket.id)
123            t['hours']=str(hours);
124            t.save_changes(ticket['reporter'])
125
126    def ticket_changed(self, ticket, comment, author, old_values):
127        """Called when a ticket is modified."""
128        self.watch_hours(ticket)
129
130    def ticket_change_deleted(self, ticket, cdate, changes):
131        """called when a ticket change is deleted"""
132        self.watch_hours(ticket)
133
134    def ticket_deleted(self, ticket):
135        """Called when a ticket is deleted."""
136        pass
137
138
139class TimeTrackingTicketValidator(Component):
140    implements(ITicketManipulator)
141
142    def __init__(self):
143        pass
144
145    def prepare_ticket(req, ticket, fields, actions):
146        """not currently called"""
147
148    def validate_ticket(self, req, ticket):
149        """Validate a ticket after it's been populated from user input.
150
151        Must return a list of `(field, message)` tuples, one for each problem
152        detected. `field` can be `None` to indicate an overall problem with the
153        ticket. Therefore, a return value of `[]` means everything is OK."""
154        errors = []
155        #some european countries use , as the decimal separator
156        try:
157            convertfloat(ticket.values['hours'])
158        except KeyError:
159            self.log.exception("The hours field was not submitted")
160        except ValueError:
161            errors.append(('Add Hours to Ticket', 'Value must be a number'))
162        try:
163            convertfloat(ticket.values['estimatedhours'])
164        except KeyError:
165            self.log.exception("The estimatedhours field was not submitted")
166        except ValueError:
167            errors.append(('Estimated Number of Hours', 'Value must be a number'))
168        return errors
Note: See TracBrowser for help on using the repository browser.