working : Work time tracking

The lino_xl.lib.working adds functionality for managing work time tracking.

This is a tested document. The following instructions are used for initialization:

>>> from lino import startup
>>> startup('lino_book.projects.noi1e.settings.demo')
>>> from lino.api.doctest import *

Note that the demo data is on fictive demo date May 23, 2015:

>>> dd.today()
datetime.date(2015, 5, 23)

Overview

work session

A database record expressing the fact that a given user works (or has been working) on a given "ticket" for a given lapse of time.

See Work sessions below.

service report

A document used in various discussions with a stakeholder.

See Service reports below.

The ticket_model defines what a ticket actually is. It can be any model that implements Workable. In Lino Noi this points to tickets.Ticket.

Work sessions

Extreme case example of a work session:

  • I start to work on an existing ticket #1 at 9:23. A customer phones at 10:17 with a question. I create #2 for this. That call is interrupted several times by the customer himself. During the first interruption another customer calls, with another problem (ticket #3) which we solve together within 5 minutes. During the second interruption of #2 (which lasts 7 minutes) I make a coffee break.

    During the third interruption I continue to analyze the customer's problem. When ticket #2 is solved, I decided that it's not worth to keep track of each interruption and that the overall session time for this ticket can be estimated to 0:40.

    Ticket start end    Pause  Duration
    #1      9:23 13:12  0:45
    #2     10:17 11:12  0:12       0:43
    #3     10:23 10:28             0:05
    

A site manager can see all sessions of the demo project:

>>> rt.show(working.Sessions, limit=15)
... 
================================================= ========= ============ ============ ============ ========== ============ ========= =========== ===================
 Ticket                                            Worker    Start date   Start time   End Date     End Time   Break Time   Summary   Duration    Ticket #
------------------------------------------------- --------- ------------ ------------ ------------ ---------- ------------ --------- ----------- -------------------
 #1 (⚹ Föö fails to bar when baz)                  Luc       23/05/2015   09:00:00                                                                `#1 <Detail>`__
 #5 (☾ Cannot create Foo)                          Jean      23/05/2015   09:00:00                                                                `#5 <Detail>`__
 #69 (☾ Default account in invoices per partner)   Mathieu   23/05/2015   09:00:00                                                                `#69 <Detail>`__
 #4 (⚒ Foo and bar don't baz)                      Luc       22/05/2015   09:00:00     22/05/2015   11:18:00                          2:18        `#4 <Detail>`__
 #21 (☾ Irritating message when bar)               Jean      22/05/2015   09:00:00     22/05/2015   12:29:00                          3:29        `#21 <Detail>`__
 #85 (☾ How can I see where bar?)                  Mathieu   22/05/2015   09:00:00     22/05/2015   12:53:00                          3:53        `#85 <Detail>`__
 #11 (☉ Class-based Foos and Bars?)                Mathieu   20/05/2015   09:05:00     20/05/2015   09:17:00                          0:12        `#11 <Detail>`__
 #12 (⚒ Foo cannot bar)                            Luc       20/05/2015   09:00:00     20/05/2015   10:30:00                          1:30        `#12 <Detail>`__
 #37 (☾ Cannot delete foo)                         Jean      20/05/2015   09:00:00     20/05/2015   09:37:00                          0:37        `#37 <Detail>`__
 #101 (☾ Why is foo so bar)                        Mathieu   20/05/2015   09:00:00     20/05/2015   09:05:00                          0:05        `#101 <Detail>`__
 #14 (☐ Bar cannot baz)                            Luc       19/05/2015   09:00:00     19/05/2015   09:10:00                          0:10        `#14 <Detail>`__
 #53 (☾ Foo never bars)                            Jean      19/05/2015   09:00:00     19/05/2015   10:02:00                          1:02        `#53 <Detail>`__
 #27 (☉ No more foo when bar is gone)              Mathieu   19/05/2015   09:00:00     19/05/2015   11:18:00                          2:18        `#27 <Detail>`__
 **Total (13 rows)**                                                                                                                  **15:34**
================================================= ========= ============ ============ ============ ========== ============ ========= =========== ===================

Some sessions are on private tickets:

>>> from django.db.models import Q
>>> rt.show(working.Sessions, column_names="ticket user duration", filter=Q(ticket__private=True))
... 
============================== ======== ==========
 Ticket                         Worker   Duration
------------------------------ -------- ----------
 #4 (⚒ Foo and bar don't baz)   Luc      2:18
 #12 (⚒ Foo cannot bar)         Luc      1:30
 **Total (2 rows)**                      **3:48**
============================== ======== ==========

Worked hours

The WorkedHours table is useful to manually edit your working times or to see on which tickets you have been working recently. It is shown in your dashboard (unless you configured your dashboard to hide it).

class lino_xl.lib.working.WorkedHours

Shows a summary of your work sessions of the last seven days, one row per day.

>>> rt.login('jean').show(working.WorkedHours)
... 
============================= ================== ========= ======= ========== ==========
 Description                   Worked tickets     Regular   Extra   Free       Total
----------------------------- ------------------ --------- ------- ---------- ----------
 `Sat 23/05/2015 <Detail>`__   `#5 <Detail>`__                      0:01       0:01
 `Fri 22/05/2015 <Detail>`__   `#21 <Detail>`__                     3:29       3:29
 `Thu 21/05/2015 <Detail>`__                                                   0:00
 `Wed 20/05/2015 <Detail>`__   `#37 <Detail>`__                     0:37       0:37
 `Tue 19/05/2015 <Detail>`__   `#53 <Detail>`__                     1:02       1:02
 `Mon 18/05/2015 <Detail>`__                                                   0:00
 `Sun 17/05/2015 <Detail>`__                                                   0:00
 **Total (7 rows)**                                                 **5:09**   **5:09**
============================= ================== ========= ======= ========== ==========

To manually edit your work sessions, click on a date in the Description column to open MySessionsByDate.

In the Worked tickets column you see a list of the tickets on which you worked that day. This is a convenient way to continue some work you started some days ago.

Service reports

A service report is a document used in various discussions with a stakeholder. It reports about the working time invested during a given date range. This report can serve as a base for writing invoices.

It can be addressed to a recipient (a user) and in that case will consider only the tickets for which this user has specified interest.

Database model: ServiceReport.

A service report currently contains three tables:

  • a list of work sessions

  • a list of the tickets mentioned in the work sessions and their invested time

  • a list of sites mentioned in the work sessions and their invested time

class lino_xl.lib.working.ServiceReport

Django model representing a service report.

Database fields:

user

This can be empty and will then show the working time of all users.

start_date
end_date
interesting_for

Show only tickets on sites assigned to this partner.

ticket_state

Show only tickets having this state.

printed

See lino_xl.lib.exerpts.Certifiable.printed

>>> obj = working.ServiceReport.objects.get(pk=1)
>>> obj.printed_by.build_method
<printing.BuildMethods.weasy2html:weasy2html>
>>> obj.interesting_for
Partner #100 ('Rumma & Ko OÜ')
>>> rt.show(working.SessionsByReport, obj)
... 
==================== ============ ========== ============ ================== ========== ======= ======
 Start date           Start time   End Time   Break Time   Description        Regular    Extra   Free
-------------------- ------------ ---------- ------------ ------------------ ---------- ------- ------
 23/05/2015           09:00:00                             `#1 <Detail>`__    0:01
 22/05/2015           09:00:00     12:29:00                `#11 <Detail>`__   3:29
 20/05/2015           09:00:00     09:05:00                `#6 <Detail>`__    0:05
 **Total (3 rows)**                                                           **3:35**
==================== ============ ========== ============ ================== ========== ======= ======

Note that sessions on #1 have actually no duration because they are active. But in the report they are shown with one minute. That's a bug (TODO: fix it). It's not an urgent bug because that's not any normal situation (you are not going to write reports for a date range when there are still active session).

>>> rt.login("robin").show(working.TicketsByReport, obj)
... 
==== =============================================== =============== ========= ========= ========== ======= ======
 ID   Ticket                                          End user        Project   State     Regular    Extra   Free
---- ----------------------------------------------- --------------- --------- --------- ---------- ------- ------
 1    `#1 (⚹ Föö fails to bar when baz) <Detail>`__   Andreas Arens   welket    New       0:01
 4    `#4 (⚒ Foo and bar don't baz) <Detail>`__       Andreas Arens   welket    Working   2:18
 12   `#12 (⚒ Foo cannot bar) <Detail>`__             Andreas Arens   welket    Working   1:30
 14   `#14 (☐ Bar cannot baz) <Detail>`__             Andreas Arens   welket    Ready     0:10
                                                                                          **3:59**
==== =============================================== =============== ========= ========= ========== ======= ======

Reporting type

The reporting_type of a site indicates how the client is going to pay for the work done.

The default implementation offers three choices "Worker", "Employer" and "Customer". "Worker" is for volunteer work and "private fun" where the worker does not get paid by anybody. "Employer" is when working time should be reported to the employer (but no customer is going to pay for it directly). "Customer" is when working time should be reported to the customer.

>>> rt.show(working.ReportingTypes)
======= ========= =========
 value   name      text
------- --------- ---------
 10      regular   Regular
 20      extra     Extra
 30      free      Free
======= ========= =========

The local site admin can adapt above list to the site's needs. He also defines a default reporting type:

>>> dd.plugins.working.default_reporting_type
<working.ReportingTypes.regular:10>

Database models

class lino_xl.lib.working.SessionType

The type of a Session.

class lino_xl.lib.working.Session

Django model representing a work session.

start_date

The date when you started to work.

start_time

The time (in hh:mm) when you started working on this session.

This is your local time according to the time zone specified in your preferences.

end_date

Leave this field blank if it is the same date as start_date.

end_time

The time (in hh:mm) when the worker stopped to work.

An empty end_time means that the user is still busy with that session, the session is not yet closed.

end_session() sets this to the current time.

break_time

The time (in hh:mm) to remove from the duration resulting from the difference between start_time and end_time.

faculty

The faculty that has been used during this session. On a new session this defaults to the needed faculty currently specified on the ticket.

site_ref
end_session()

Tell Lino that you stop this session for now. This will simply set the end_time to the current time.

Implemented by EndThisSession.

Tables reference

class lino_xl.lib.working.Sessions
class lino_xl.lib.working.SessionsByTicket

The "Sessions" panel in the detail of a ticket.

slave_summary

This panel shows:

class lino_xl.lib.working.MySessions

Shows all my sessions.

Use the filter button button to filter them. You can export them to Excel.

class lino_xl.lib.working.MySessionsByDate

Shows my sessions of a given day.

Use this to manually edit your work sessions.

class lino_xl.lib.working.SessionsByReport
class lino_xl.lib.working.TicketsReport
class lino_xl.lib.working.SitesByReport

The list of tickets mentioned in a service report.

class lino_xl.lib.working.WorkersByReport

Actions reference

class lino_xl.lib.working.StartTicketSession

The action behind Workable.start_session.

class lino_xl.lib.working.EndSession

Common base for EndThisSession and EndTicketSession.

class lino_xl.lib.working.EndTicketSession

The action behind Workable.end_session.

class lino_xl.lib.working.EndThisSession

The action behind Session.end_session.

The Workable model mixin

class lino_xl.lib.working.Workable

Base class for things that workers can work on.

The model specified in ticket_model must be a subclass of this. For example, in Lino Noi tickets are workable.

is_workable_for()

Return True if the given user can start a work session on this object.

on_worked()

This is automatically called when a work session has been created or modified.

start_session()

Start a work session on this ticket.

See StartTicketSession.

end_session()

Tell Lino that you stop working on this ticket for now. This will simply set the Session.end_time to the current time.

Implemented by EndTicketSession.

Actions reference

class lino_xl.lib.working.ShowMySessionsByDay

Shows your work sessions per day.

class lino_xl.lib.working.TicketHasSessions

Select only tickets for which there has been at least one session during the given period.

This is added as item to lino_xl.lib.tickets.TicketEvents.

class lino_xl.lib.working.ProjectHasSessions

Select only projects for which there has been at least one session during the given period.

This is added as item to lino_xl.lib.tickets.ProjectEvents.

class lino_xl.lib.working.Worker

A user who is candidate for working on a ticket.

Summaries

class lino_xl.lib.working.SiteSummary

An automatically generated record with yearly summary data about a site.

class lino_xl.lib.working.SummariesBySite

Shows the summary records for a given site.

class lino_xl.lib.working.UserSummary

An automatically generated record with monthly summary data about a user.

class lino_xl.lib.working.SummariesByUser

Shows the summary records for a given user.

>>> rt.show(working.SiteSummaries, exclude=dict(regular_hours=""))
... 
==== ====== ======= ========= ================ ================== ========= ======= ======
 ID   Year   Month   Project   Active tickets   Inactive tickets   Regular   Extra   Free
---- ------ ------- --------- ---------------- ------------------ --------- ------- ------
 3    2015           welket    0                0                  3:58
==== ====== ======= ========= ================ ================== ========= ======= ======
>>> rt.show(working.UserSummaries, exclude=dict(regular_hours=""))
... 
==== ====== ======= ====== ========= ======= ======
 ID   Year   Month   User   Regular   Extra   Free
---- ------ ------- ------ --------- ------- ------
 65   2015   5       Luc    3:58
==== ====== ======= ====== ========= ======= ======

Don't read me

>>> working.WorkedHours
lino_xl.lib.working.ui.WorkedHours
>>> print(working.WorkedHours.column_names)
detail_link worked_tickets  vc0:5 vc1:5 vc2:5 vc3:5 *
>>> working.WorkedHours.get_data_elem('detail_link')
lino.core.actors.Actor.detail_link