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__ |