File Coverage

blib/lib/App/bif/show/timesheet.pm
Criterion Covered Total %
statement 24 109 22.0
branch 0 54 0.0
condition 0 11 0.0
subroutine 8 9 88.8
pod 1 1 100.0
total 33 184 17.9


line stmt bran cond sub pod time code
1             package App::bif::show::timesheet;
2 1     1   3777 use strict;
  1         3  
  1         42  
3 1     1   6 use warnings;
  1         3  
  1         52  
4 1     1   7 use utf8;
  1         4  
  1         10  
5 1     1   29 use Bif::Mo;
  1         3  
  1         13  
6 1     1   1159 use DBIx::ThinSQL qw/ sq /;
  1         28336  
  1         10  
7 1     1   84 use Time::Piece;
  1         3  
  1         11  
8 1     1   78 use Time::Seconds;
  1         3  
  1         1714  
9              
10             our $VERSION = '0.1.5_6';
11             extends 'App::bif';
12              
13             my ( $bold, $reset );
14              
15             sub _build {
16 0     0   0 my $id = shift;
17 0         0 my $count = @_;
18              
19 0         0 my @columns;
20             my @select;
21 0         0 my @having;
22 0         0 my $format = ' l l ';
23 0         0 my @headers;
24              
25 0         0 my $i = 1;
26 0         0 foreach my $item (@_) {
27 0         0 my ( $type, $start, $delta, $rollup ) = @{$item};
  0         0  
28 0         0 my $end;
29              
30 0 0       0 if ( $type eq 'year' ) {
    0          
    0          
    0          
31 0         0 $start = Time::Piece->strptime( $start->strftime('%Y'), '%Y' );
32 0         0 $start = $start->add_years($delta);
33 0         0 $end = $start->add_years(1);
34             }
35             elsif ( $type eq 'month' ) {
36 0         0 $start =
37             Time::Piece->strptime( $start->strftime('%Y-%m'), '%Y-%m' );
38 0         0 $start = $start->add_months($delta);
39 0         0 $end = $start->add_months(1);
40             }
41             elsif ( $type eq 'week' ) {
42 0         0 $start =
43             Time::Piece->strptime( $start->strftime('%Y-%m-%d'), '%Y-%m-%d' );
44 0         0 my $wday = $start->_wday - 1;
45 0 0       0 $wday = 6 if $wday < 0;
46 0         0 $start = $start + ( -$wday * ONE_DAY );
47 0         0 $start = $start + ( $delta * 7 * ONE_DAY );
48 0         0 $end = $start + ( 7 * ONE_DAY );
49             }
50             elsif ( $type eq 'day' ) {
51 0         0 $start =
52             Time::Piece->strptime( $start->strftime('%Y-%m-%d'), '%Y-%m-%d' );
53 0         0 $start = $start + ( $delta * ONE_DAY );
54 0         0 $end = $start + (ONE_DAY);
55             }
56             else {
57 0         0 die "unknown type: $type";
58             }
59              
60 0         0 push( @columns,
61             " printf('%d:%0.2d', "
62             . " SUM(col$i) / 3600, "
63             . " (SUM(col$i) - 3600 * (SUM(col$i) / 3600)) / 60 "
64             . " ) AS duration$i" );
65              
66 0 0       0 push( @having, ' OR ' ) if @having;
67 0         0 push( @having, "SUM(col$i) >= 60" );
68              
69 0         0 my @x;
70             my @y;
71 0         0 foreach my $j ( 1 .. $count ) {
72 0 0       0 if ( $i == $j ) {
73 0         0 push( @x, "wd.stop - wd.start AS col$j" );
74 0         0 push( @y, "wb.stop - wb.start AS col$j" );
75             }
76             else {
77 0         0 push( @x, "NULL AS col$j" );
78 0         0 push( @y, "NULL AS col$j" );
79             }
80             }
81              
82             push(
83 0 0       0 @select,
    0          
    0          
    0          
84             [
85             ( $i == 1 ? 'select' : 'union_all_select' ) =>
86             [ 'n.kind AS kind', 'n.path AS path', @x, ],
87             from => 'work_deltas wd',
88             inner_join => 'changes c',
89             on => {
90             'c.id' => \'wd.change_id',
91             $id ? ( 'c.identity_id' => $id, ) : (),
92             },
93             $rollup
94             ? (
95             inner_join => 'nodes_tree nt',
96             on => 'nt.child = wd.node_id',
97             inner_join => 'projects p',
98             on => 'p.id = nt.parent',
99             )
100             : (
101             inner_join => 'nodes n2',
102             on => 'n2.id = wd.node_id',
103             inner_join => 'projects p',
104             on => 'p.id = n2.project_id',
105             ),
106             inner_join => 'nodes n',
107             on => 'n.id = p.id',
108             "where -- $start -> $end" => {
109             'wd.gtime_start >=' => $start->epoch,
110             'wd.gtime_stop <' => $end->epoch,
111             },
112             union_all_select => [
113             'n.kind AS kind',
114             'n.path || " (' . "${bold}unrec$reset" . ')" AS path', @y,
115             ],
116             from => 'bifkv b',
117             inner_join => 'work_buffers wb',
118             "on -- $start -> $end" => {
119             'wb.billable' => 1,
120             'wb.gtime_start >=' => $start->epoch,
121             'wb.gtime_stop <' => $end->epoch,
122             },
123             $rollup
124             ? (
125             inner_join => 'nodes_tree nt',
126             on => 'nt.child = wb.node_id',
127             inner_join => 'projects p',
128             on => 'p.id = nt.parent',
129             )
130             : (
131             inner_join => 'nodes n2',
132             on => 'n2.id = wb.node_id',
133             inner_join => 'projects p',
134             on => 'p.id = n2.project_id',
135             ),
136             inner_join => 'nodes n',
137             on => 'n.id = p.id',
138             where => { key => 'self', identity_id => $id },
139             ]
140             );
141              
142 0         0 $format .= 'r ';
143              
144 0 0       0 if ( $type eq 'year' ) {
    0          
    0          
    0          
145 0         0 push( @headers, $start->strftime('%Y') );
146             }
147             elsif ( $type eq 'month' ) {
148 0         0 push( @headers, $start->strftime('%Y-%m') );
149             }
150             elsif ( $type eq 'week' ) {
151              
152             # Time::Piece's %V does not work under windows, so use the
153             # ->method() calls instead. See
154             # https://rn.cpan.org/Public/Bug/Display.html?id=105507
155 0         0 push( @headers,
156             sprintf( '%0.4d-W%0.2d', $start->year, $start->week ) );
157             }
158             elsif ( $type eq 'day' ) {
159 0         0 push( @headers, $start->strftime('%Y-%m-%d') );
160             }
161              
162 0         0 $i++;
163             }
164              
165 0         0 $format .= ' ';
166              
167 0         0 return \@columns, \@select, \@having, $format, \@headers;
168             }
169              
170             sub run {
171 1     1 1 2 my $self = shift;
172 1         5 my $opts = $self->opts;
173 1         109 my $db = $self->db;
174              
175 0           ( $bold, $reset ) = $self->colours(qw/bold reset/);
176              
177 0           my $id;
178 0 0         if ( my $str = $self->opts->{identity} ) {
179 0 0         unless ( $str eq '-' ) {
180 0           my $iinfo = $self->get_node( $str, 'identity' );
181 0           $id = $iinfo->{id};
182             }
183             }
184             else {
185 0           $id = $self->db->xval(
186             select => 'b.identity_id',
187             from => 'bifkv b',
188             where => { key => 'self' },
189             );
190             }
191              
192 0   0       $opts->{date} //= localtime->strftime('%Y-%m-%d');
193 0           my $dt = eval { Time::Piece->strptime( $opts->{date}, '%Y-%m-%d' ) }
194             or return $self->err( 'InvalidDate', 'invalid date string: %s',
195 0 0         $opts->{date} );
196              
197 0           my ( $col, $select, $having, $format, $header );
198              
199 0 0         if ( $opts->{year} ) {
    0          
    0          
    0          
200 0   0       $opts->{number} ||= 4;
201             ( $col, $select, $having, $format, $header ) = _build( $id,
202 0           map { [ 'year', $dt, -( $opts->{number} - $_ ), $opts->{rollup} ] }
203 0           1 .. $opts->{number} );
204             }
205             elsif ( $opts->{month} ) {
206 0   0       $opts->{number} ||= 4;
207             ( $col, $select, $having, $format, $header ) = _build(
208             $id,
209 0           map { [ 'month', $dt, -( $opts->{number} - $_ ), $opts->{rollup} ] }
210             1 .. $opts->{number}
211 0           );
212             }
213             elsif ( $opts->{week} ) {
214 0   0       $opts->{number} ||= 4;
215             ( $col, $select, $having, $format, $header ) = _build( $id,
216 0           map { [ 'week', $dt, -( $opts->{number} - $_ ), $opts->{rollup} ] }
217 0           1 .. $opts->{number} );
218             }
219             elsif ( $opts->{day} ) {
220 0   0       $opts->{number} ||= 3;
221             ( $col, $select, $having, $format, $header ) = _build( $id,
222 0           map { [ 'day', $dt, -( $opts->{number} - $_ ), $opts->{rollup} ] }
223 0           1 .. $opts->{number} );
224             }
225             else {
226             ( $col, $select, $having, $format, $header ) = _build(
227             $id,
228             [ 'day', $dt, 0, $opts->{rollup} ],
229             [ 'week', $dt, 0, $opts->{rollup} ],
230             [ 'month', $dt, 0, $opts->{rollup} ],
231 0           [ 'year', $dt, 0, $opts->{rollup} ],
232             );
233             }
234              
235             my @data = $db->xarrayrefs(
236             select => [ 'kind', 'path', @$col, ],
237 0           from => sq( map { @$_ } @$select ),
  0            
238             group_by => [qw/kind path/],
239             having => $having,
240             order_by => 'path',
241             );
242              
243 0 0         if ( !@data ) {
244 0           print "Timesheet is empty (or < 0:01) for selected period.\n";
245             }
246             else {
247 0           $self->start_pager;
248 0           print $self->render_table( $format, [ qw/Type Path/, @$header ],
249             \@data );
250             }
251              
252 0 0         if ( $opts->{year} ) {
    0          
    0          
    0          
253 0           return $self->ok('ShowTimesheetYear');
254             }
255             elsif ( $opts->{month} ) {
256 0           return $self->ok('ShowTimesheetMonth');
257             }
258             elsif ( $opts->{week} ) {
259 0           return $self->ok('ShowTimesheetWeek');
260             }
261             elsif ( $opts->{day} ) {
262 0           return $self->ok('ShowTimesheetDay');
263             }
264              
265 0           return $self->ok('ShowTimesheet');
266             }
267              
268             1;
269             __END__