File Coverage

blib/lib/App/bif/show/timesheet.pm
Criterion Covered Total %
statement 24 117 20.5
branch 0 56 0.0
condition 0 14 0.0
subroutine 8 9 88.8
pod 1 1 100.0
total 33 197 16.7


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