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