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
RevLine 
[5162]1from trac.ticket import ITicketChangeListener, Ticket, ITicketManipulator
[1119]2from trac.core import *
[1710]3import datetime
[8140]4import dbhelper
[1119]5
[1710]6def identity(x):
7    return x;
8
[6268]9def convertfloat(x):
10    "some european countries use , as the decimal separator"
[16685]11    if not x:
12        return 0.0
[14405]13    if isinstance(x, float) or isinstance(x, int):
14        return x
[6268]15    x = str(x).strip()
16    if len(x) > 0:
17        return float(x.replace(',','.'))
[16685]18    else:
[6268]19        return 0.0
20
21
[2015]22try:
23    import trac.util.datefmt
[8131]24    to_timestamp = trac.util.datefmt.to_utimestamp
[2015]25except Exception:
26    to_timestamp = identity
[1710]27
[2015]28
[1119]29def save_custom_field_value( db, ticket_id, field, value ):
[8140]30    cursor = db.cursor()
[14398]31    cursor.execute("UPDATE ticket_custom SET value=%s "
32                   "WHERE ticket=%s AND name=%s", (value, ticket_id, field))
33
[14856]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
[5162]42
[14856]43   
[14416]44def update_hours_to_floats(db, ticket_id):
[14417]45    cursor = db.cursor()
[14416]46    cursor.execute("SELECT time, newvalue FROM ticket_change"
47                   " WHERE newvalue like '%,%' AND  ticket=%s AND field='hours'", 
[14418]48                   (ticket_id,))
49    data = list(cursor.fetchall())
[14416]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
[14398]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
[17107]59                         ELSE CAST( newvalue AS DECIMAL(10,2) ) END ) as total
[14398]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))
[14856]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
[4164]73
[14398]74def insert_totalhours_changes( db, ticket_id):
[14400]75    sql = """
[14398]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
[17107]79                           ELSE CAST( newvalue AS DECIMAL(10,2) ) END ) as total
[14398]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
[17107]85                           ELSE CAST( newvalue AS DECIMAL(10,2) ) END ) as total
[14398]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')
[4166]98    """
[14399]99    cursor = db.cursor()
100    cursor.execute(sql, (ticket_id,))
[5162]101
[4164]102
[1119]103class TimeTrackingTicketObserver(Component):
104    implements(ITicketChangeListener)
105    def __init__(self):
106        pass
107
108    def watch_hours(self, ticket):
[4164]109        ticket_id = ticket.id
[16684]110        with self.env.db_transaction as db:
[14416]111            update_hours_to_floats(db, ticket_id)
[14398]112            save_custom_field_value( db, ticket_id, "hours", '0')
113            insert_totalhours_changes( db, ticket_id )
114            update_totalhours_custom ( db, ticket_id )
[5162]115
[1119]116    def ticket_created(self, ticket):
117        """Called when a ticket is created."""
[14404]118        hours = convertfloat(ticket['hours'])
[14408]119        # makes the hours a ticket change like all the other hours records
[14404]120        if hours > 0:
[14416]121            self.watch_hours(ticket) # clears the hours
122            t = Ticket (self.env, ticket.id)
[14408]123            t['hours']=str(hours);
124            t.save_changes(ticket['reporter'])
[1119]125
[1286]126    def ticket_changed(self, ticket, comment, author, old_values):
[14398]127        """Called when a ticket is modified."""
128        self.watch_hours(ticket)
[5162]129
[14863]130    def ticket_change_deleted(self, ticket, cdate, changes):
[14398]131        """called when a ticket change is deleted"""
[1119]132        self.watch_hours(ticket)
133
134    def ticket_deleted(self, ticket):
135        """Called when a ticket is deleted."""
[14398]136        pass
[5162]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'])
[6902]158        except KeyError:
159            self.log.exception("The hours field was not submitted")
[5162]160        except ValueError:
161            errors.append(('Add Hours to Ticket', 'Value must be a number'))
162        try:
163            convertfloat(ticket.values['estimatedhours'])
[6902]164        except KeyError:
165            self.log.exception("The estimatedhours field was not submitted")
[5162]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.