Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision c553e92f

Von Bernd Bleßmann vor fast 4 Jahren hinzugefügt

  • ID c553e92f9fd443e0178d475851d25cb682089212
  • Vorgänger d01aad90
  • Nachfolger 22c6c1ec

Zeiterfassung: Controller

Unterschiede anzeigen:

SL/Controller/TimeRecording.pm
1
package SL::Controller::TimeRecording;
2

  
3
use strict;
4
use parent qw(SL::Controller::Base);
5

  
6
use DateTime;
7
use English qw(-no_match_vars);
8
use POSIX qw(strftime);
9

  
10
use SL::Controller::Helper::GetModels;
11
use SL::Controller::Helper::ReportGenerator;
12
use SL::DB::Customer;
13
use SL::DB::Employee;
14
use SL::DB::TimeRecording;
15
use SL::Locale::String qw(t8);
16
use SL::ReportGenerator;
17

  
18
use Rose::Object::MakeMethods::Generic
19
(
20
# scalar                  => [ qw() ],
21
 'scalar --get_set_init' => [ qw(time_recording models all_time_recording_types all_employees) ],
22
);
23

  
24

  
25
# safety
26
#__PACKAGE__->run_before('check_auth');
27

  
28
#
29
# actions
30
#
31

  
32
my %sort_columns = (
33
  start_time   => t8('Start'),
34
  end_time     => t8('End'),
35
  customer     => t8('Customer'),
36
  type         => t8('Type'),
37
  project      => t8('Project'),
38
  description  => t8('Description'),
39
  staff_member => t8('Mitarbeiter'),
40
  duration     => t8('Duration'),
41
);
42

  
43
sub action_list {
44
  my ($self, %params) = @_;
45

  
46
  $self->setup_list_action_bar;
47
  $self->make_filter_summary;
48
  $self->prepare_report;
49

  
50
  $self->report_generator_list_objects(report => $self->{report}, objects => $self->models->get);
51
}
52

  
53
sub action_edit {
54
  my ($self) = @_;
55

  
56
  $::request->{layout}->use_javascript("${_}.js") for qw(kivi.TimeRecording ckeditor/ckeditor ckeditor/adapters/jquery kivi.Validator);
57

  
58
  if ($self->time_recording->start_time) {
59
    $self->{start_date} = $self->time_recording->start_time->to_kivitendo;
60
    $self->{start_time} = $self->time_recording->start_time->to_kivitendo_time;
61
  }
62
  if ($self->time_recording->end_time) {
63
    $self->{end_date}   = $self->time_recording->end_time->to_kivitendo;
64
    $self->{end_time}   = $self->time_recording->end_time->to_kivitendo_time;
65
  }
66

  
67
  $self->setup_edit_action_bar;
68

  
69
  $self->render('time_recording/form',
70
                title  => t8('Time Recording'),
71
  );
72
}
73

  
74
sub action_save {
75
  my ($self) = @_;
76

  
77
  my @errors = $self->time_recording->validate;
78
  if (@errors) {
79
    $::form->error(t8('Saving the time recording entry failed: #1', join '<br>', @errors));
80
    return;
81
  }
82

  
83
  if ( !eval { $self->time_recording->save; 1; } ) {
84
    $::form->error(t8('Saving the time recording entry failed: #1', $EVAL_ERROR));
85
    return;
86
  }
87

  
88
  $self->redirect_to(safe_callback());
89
}
90

  
91
sub action_delete {
92
  my ($self) = @_;
93

  
94
  $self->time_recording->delete;
95

  
96
  $self->redirect_to(safe_callback());
97
}
98

  
99
sub init_time_recording {
100
  my $time_recording = ($::form->{id}) ? SL::DB::TimeRecording->new(id => $::form->{id})->load
101
                                       : SL::DB::TimeRecording->new(start_time => DateTime->now_local);
102

  
103
  my %attributes = %{ $::form->{time_recording} || {} };
104

  
105
  foreach my $type (qw(start end)) {
106
    if ($::form->{$type . '_date'}) {
107
      my $date = DateTime->from_kivitendo($::form->{$type . '_date'});
108
      $attributes{$type . '_time'} = $date->clone;
109
      if ($::form->{$type . '_time'}) {
110
        my ($hour, $min) = split ':', $::form->{$type . '_time'};
111
        $attributes{$type . '_time'}->set_hour($hour)  if $hour;
112
        $attributes{$type . '_time'}->set_minute($min) if $min;
113
      }
114
    }
115
  }
116

  
117
  $attributes{staff_member_id} = $attributes{employee_id} = SL::DB::Manager::Employee->current->id;
118

  
119
  $time_recording->assign_attributes(%attributes);
120

  
121
  return $time_recording;
122
}
123

  
124
sub init_models {
125
  SL::Controller::Helper::GetModels->new(
126
    controller     => $_[0],
127
    sorted         => \%sort_columns,
128
    disable_plugin => 'paginated',
129
    with_objects   => [ 'customer', 'type', 'project', 'staff_member', 'employee' ],
130
  );
131
}
132

  
133
sub init_all_time_recording_types {
134
  SL::DB::Manager::TimeRecordingType->get_all_sorted(query => [obsolete => 0]);
135
}
136

  
137
sub init_all_employees {
138
  SL::DB::Manager::Employee->get_all_sorted;
139
}
140

  
141
sub prepare_report {
142
  my ($self) = @_;
143

  
144
  my $report      = SL::ReportGenerator->new(\%::myconfig, $::form);
145
  $self->{report} = $report;
146

  
147
  my @columns  = qw(start_time end_time customer type project description staff_member duration);
148

  
149
  my %column_defs = (
150
    start_time   => { text => t8('Start'),        sub => sub { $_[0]->start_time_as_timestamp },
151
                      obj_link => sub { $self->url_for(action => 'edit', 'id' => $_[0]->id, callback => $self->models->get_callback) }  },
152
    end_time     => { text => t8('End'),          sub => sub { $_[0]->end_time_as_timestamp },
153
                      obj_link => sub { $self->url_for(action => 'edit', 'id' => $_[0]->id, callback => $self->models->get_callback) }  },
154
    customer     => { text => t8('Customer'),     sub => sub { $_[0]->customer->displayable_name } },
155
    type         => { text => t8('Type'),         sub => sub { $_[0]->type && $_[0]->type->abbreviation } },
156
    project      => { text => t8('Project'),      sub => sub { $_[0]->project && $_[0]->project->displayable_name } },
157
    description  => { text => t8('Description'),  sub => sub { $_[0]->description_as_stripped_html },
158
                      raw_data => sub { $_[0]->description_as_restricted_html }, # raw_data only used for html(?)
159
                      obj_link => sub { $self->url_for(action => 'edit', 'id' => $_[0]->id, callback => $self->models->get_callback) }  },
160
    staff_member => { text => t8('Mitarbeiter'),  sub => sub { $_[0]->staff_member->safe_name } },
161
    duration     => { text => t8('Duration'),     sub => sub { $_[0]->duration_as_duration_string },
162
                      align => 'right'},
163
  );
164

  
165
  $report->set_options(
166
    controller_class      => 'TimeRecording',
167
    std_column_visibility => 1,
168
    output_format         => 'HTML',
169
    title                 => t8('Time Recordings'),
170
    allow_pdf_export      => 1,
171
    allow_csv_export      => 1,
172
  );
173

  
174
  $report->set_columns(%column_defs);
175
  $report->set_column_order(@columns);
176
  $report->set_export_options(qw(list filter));
177
  $report->set_options_from_form;
178

  
179
  $self->models->disable_plugin('paginated') if $report->{options}{output_format} =~ /^(pdf|csv)$/i;
180
  #$self->models->add_additional_url_params();
181
  $self->models->finalize;
182
  $self->models->set_report_generator_sort_options(report => $report, sortable_columns => [keys %sort_columns]);
183

  
184
  $report->set_options(
185
    raw_top_info_text    => $self->render('time_recording/report_top',    { output => 0 }),
186
    raw_bottom_info_text => $self->render('time_recording/report_bottom', { output => 0 }, models => $self->models),
187
    attachment_basename  => t8('time_recordings') . strftime('_%Y%m%d', localtime time),
188
  );
189
}
190

  
191
sub make_filter_summary {
192
  my ($self) = @_;
193

  
194
  my $filter = $::form->{filter} || {};
195
  my @filter_strings;
196

  
197
  my $staff_member = $filter->{staff_member_id} ? SL::DB::Employee->new(id => $filter->{staff_member_id})->load->safe_name : '';
198

  
199
  my @filters = (
200
    [ $filter->{"start_time:date::ge"},                        t8('From Start')      ],
201
    [ $filter->{"start_time:date::le"},                        t8('To Start')        ],
202
    [ $filter->{"customer"}->{"name:substr::ilike"},           t8('Customer')        ],
203
    [ $filter->{"customer"}->{"customernumber:substr::ilike"}, t8('Customer Number') ],
204
    [ $staff_member,                                           t8('Mitarbeiter')     ],
205
  );
206

  
207
  for (@filters) {
208
    push @filter_strings, "$_->[1]: $_->[0]" if $_->[0];
209
  }
210

  
211
  $self->{filter_summary} = join ', ', @filter_strings;
212
}
213

  
214
sub setup_list_action_bar {
215
  my ($self) = @_;
216

  
217
  for my $bar ($::request->layout->get('actionbar')) {
218
    $bar->add(
219
      action => [
220
        t8('Update'),
221
        submit    => [ '#filter_form', { action => 'TimeRecording/list' } ],
222
        accesskey => 'enter',
223
      ],
224
      action => [
225
        t8('Add'),
226
        link => $self->url_for(action => 'edit', callback => $self->models->get_callback),
227
      ],
228
    );
229
  }
230
}
231

  
232
sub setup_edit_action_bar {
233
  my ($self) = @_;
234

  
235
  for my $bar ($::request->layout->get('actionbar')) {
236
    $bar->add(
237
      action => [
238
        t8('Save'),
239
        submit => [ '#form', { action => 'TimeRecording/save' } ],
240
        checks => [ 'kivi.validate_form' ],
241
      ],
242
      action => [
243
        t8('Delete'),
244
        submit  => [ '#form', { action => 'TimeRecording/delete' } ],
245
        only_if => $self->time_recording->id,
246
      ],
247
      action => [
248
        t8('Cancel'),
249
        link  => $self->url_for(safe_callback()),
250
      ],
251
    );
252
  }
253
}
254

  
255
sub safe_callback {
256
  $::form->{callback} || (action => 'list')
257
}
258

  
259
1;
SL/DB/Manager/TimeRecording.pm
7 7

  
8 8
use parent qw(SL::DB::Helper::Manager);
9 9

  
10
use SL::DB::Helper::Sorted;
11

  
10 12
sub object_class { 'SL::DB::TimeRecording' }
11 13

  
12 14
__PACKAGE__->make_manager_methods;
13 15

  
16

  
17
sub _sort_spec {
18
  return ( default => [ 'start_time', 1 ],
19
           columns => { SIMPLE    => 'ALL' ,
20
                        customer  => [ 'lower(customer.name)', ],
21
           }
22
  );
23
}
24

  
25

  
14 26
1;
SL/DB/TimeRecording.pm
5 5

  
6 6
use strict;
7 7

  
8
use SL::Locale::String qw(t8);
9

  
10
use SL::DB::Helper::AttrDuration;
11
use SL::DB::Helper::AttrHTML;
12

  
8 13
use SL::DB::MetaSetup::TimeRecording;
9 14
use SL::DB::Manager::TimeRecording;
10 15

  
11 16
__PACKAGE__->meta->initialize;
12 17

  
18
__PACKAGE__->attr_duration_minutes(qw(duration));
19

  
20
__PACKAGE__->attr_html('description');
21

  
22
__PACKAGE__->before_save('_before_save_check_valid');
23

  
24
sub _before_save_check_valid {
25
  my ($self) = @_;
26

  
27
  my @errors = $self->validate;
28
  return (scalar @errors == 0);
29
}
30

  
31
sub validate {
32
  my ($self) = @_;
33

  
34
  my @errors;
35

  
36
  push @errors, t8('Start time must not be empty.')                            if !$self->start_time;
37
  push @errors, t8('Customer must not be empty.')                              if !$self->customer_id;
38
  push @errors, t8('Staff member must not be empty.')                          if !$self->staff_member_id;
39
  push @errors, t8('Employee must not be empty.')                              if !$self->employee_id;
40
  push @errors, t8('Description must not be empty.')                           if !$self->description;
41
  push @errors, t8('Start time must be earlier than end time.')                if $self->is_time_in_wrong_order;
42

  
43
  my $conflict = $self->is_time_overlapping;
44
  push @errors, t8('Entry overlaps with "#1".', $conflict->displayable_times)  if $conflict;
45

  
46
  return @errors;
47
}
48

  
49
sub is_time_overlapping {
50
  my ($self) = @_;
51

  
52
  # Do not allow overlapping time periods.
53
  # Start time can be equal to another end time
54
  # (an end time can be equal to another start time)
55

  
56
  # We cannot check if no staff member is given.
57
  return if !$self->staff_member_id;
58

  
59
  # If no start time and no end time are given, there is no overlapping.
60
  return if !($self->start_time || $self->end_time);
61

  
62
  my $conflicting;
63

  
64
  # Start time or end time can be undefined.
65
  if (!$self->start_time) {
66
    $conflicting = SL::DB::Manager::TimeRecording->get_all(where  => [ and => [ '!id'           => $self->id,
67
                                                                                staff_member_id => $self->staff_member_id,
68
                                                                                start_time      => {lt => $self->end_time},
69
                                                                                end_time        => {ge => $self->end_time} ] ],
70
                                                           sort_by => 'start_time DESC',
71
                                                           limit   => 1);
72
  } elsif (!$self->end_time) {
73
    $conflicting = SL::DB::Manager::TimeRecording->get_all(where  => [ and => [ '!id'           => $self->id,
74
                                                                                staff_member_id => $self->staff_member_id,
75
                                                                                or              => [ and => [start_time => {le => $self->start_time},
76
                                                                                                             end_time   => {gt => $self->start_time} ],
77
                                                                                                     start_time => $self->start_time,
78
                                                                                ],
79
                                                                       ],
80
                                                           ],
81
                                                           sort_by => 'start_time DESC',
82
                                                           limit   => 1);
83
  } else {
84
    $conflicting = SL::DB::Manager::TimeRecording->get_all(where  => [ and => [ '!id'           => $self->id,
85
                                                                                staff_member_id => $self->staff_member_id,
86
                                                                                or              => [ and => [ start_time => {lt => $self->end_time},
87
                                                                                                              end_time   => {gt => $self->start_time} ] ,
88
                                                                                                     or  => [ start_time => $self->start_time,
89
                                                                                                              end_time   => $self->end_time, ],
90
                                                                                ]
91
                                                                       ]
92
                                                           ],
93
                                                           sort_by => 'start_time DESC',
94
                                                           limit   => 1);
95
  }
96

  
97
  return $conflicting->[0] if @$conflicting;
98
  return;
99
}
100

  
101
sub is_time_in_wrong_order {
102
  my ($self) = @_;
103

  
104
  if ($self->start_time && $self->end_time
105
      && $self->start_time >= $self->end_time) {
106
    return 1;
107
  }
108

  
109
  return;
110
}
111

  
112
sub displayable_times {
113
  my ($self) = @_;
114

  
115
  # placeholder
116
  my $ph = $::locale->format_date_object(DateTime->new(year => 1111, month => 11, day => 11, hour => 11, minute => 11), precision => 'minute');
117
  $ph =~ s{1}{-}g;
118

  
119
  return ($self->start_time_as_timestamp||$ph) . ' - ' . ($self->end_time_as_timestamp||$ph);
120
}
121

  
122
sub duration {
123
  my ($self) = @_;
124

  
125
  if ($self->start_time && $self->end_time) {
126
    return ($self->end_time->subtract_datetime_absolute($self->start_time))->seconds/60.0;
127
  } else {
128
    return;
129
  }
130
}
131

  
13 132
1;
SL/Dev/ALL.pm
9 9
use SL::Dev::Record;
10 10
use SL::Dev::Payment;
11 11
use SL::Dev::Shop;
12
use SL::Dev::TimeRecording;
12 13

  
13 14
sub import {
14 15
  no strict "refs";
15
  for (qw(Part CustomerVendor Inventory Record Payment Shop)) {
16
  for (qw(Part CustomerVendor Inventory Record Payment Shop TimeRecording)) {
16 17
    Exporter::export_to_level("SL::Dev::$_", 1, @_);
17 18
  }
18 19
}
SL/Dev/TimeRecording.pm
1
package SL::Dev::TimeRecording;
2

  
3
use strict;
4
use base qw(Exporter);
5
our @EXPORT_OK = qw(new_time_recording);
6
our %EXPORT_TAGS = (ALL => \@EXPORT_OK);
7

  
8
use DateTime;
9

  
10
use SL::DB::TimeRecording;
11

  
12
use SL::DB::Employee;
13
use SL::Dev::CustomerVendor qw(new_customer);
14

  
15

  
16
sub new_time_recording {
17
  my (%params) = @_;
18

  
19
  my $customer = delete $params{customer} // new_customer(name => 'Testcustomer')->save;
20
  die "illegal customer" unless defined $customer && ref($customer) eq 'SL::DB::Customer';
21

  
22
  my $employee     = $params{employee}     // SL::DB::Manager::Employee->current;
23
  my $staff_member = $params{staff_member} // $employee;
24

  
25
  my $now = DateTime->now_local;
26

  
27
  my $time_recording = SL::DB::TimeRecording->new(
28
    start_time   => $now,
29
    end_time     => $now->add(hours => 1),
30
    customer     => $customer,
31
    description  => '<p>this and that</p>',
32
    staff_member => $staff_member,
33
    employee     => $employee,
34
    %params,
35
  );
36

  
37
  return $time_recording;
38
}
39

  
40

  
41
1;
js/kivi.TimeRecording.js
1
namespace('kivi.TimeRecording', function(ns) {
2
  'use strict';
3

  
4
  ns.set_end_date = function() {
5
    if ($('#start_date').val() !== '' && $('#end_date').val() === '') {
6
      var kivi_start_date  = kivi.format_date(kivi.parse_date($('#start_date').val()));
7
      $('#end_date').val(kivi_start_date);
8
    }
9
  };
10

  
11
  ns.set_current_date_time = function(what) {
12
    if (what !== 'start' && what !== 'end') return;
13

  
14
    var $date = $('#' + what + '_date');
15
    var $time = $('#' + what + '_time');
16
    var date = new Date();
17

  
18
    $date.val(kivi.format_date(date));
19
    $time.val(kivi.format_time(date));
20
  };
21

  
22
});
menus/user/10-time-recording.yaml
6 6
  params:
7 7
    action: SimpleSystemSetting/list
8 8
    type: time_recording_type
9
- parent: productivity
10
  id: productivity_time_recording
11
  name: Time Recording
12
  order: 350
13
  params:
14
    action: TimeRecording/edit
15
- parent: productivity_reports
16
  id: productivity_reports_time_recording
17
  name: Time Recording
18
  order: 300
19
  params:
20
    action: TimeRecording/list
t/db/time_recordig.t
1
use Test::More tests => 40;
2

  
3
use strict;
4

  
5
use lib 't';
6
use utf8;
7

  
8
use Support::TestSetup;
9
use Test::Exception;
10
use DateTime;
11

  
12
use_ok 'SL::DB::TimeRecording';
13

  
14
use SL::Dev::ALL qw(:ALL);
15

  
16
Support::TestSetup::login();
17

  
18
my @time_recordings;
19
my ($s1, $e1, $s2, $e2);
20

  
21
sub clear_up {
22
  foreach (qw(TimeRecording Customer)) {
23
    "SL::DB::Manager::${_}"->delete_all(all => 1);
24
  }
25
  SL::DB::Manager::Employee->delete_all(where => [ '!login' => 'unittests' ]);
26
};
27

  
28
########################################
29

  
30
$s1 = DateTime->now_local;
31
$e1 = $s1->clone;
32

  
33
clear_up;
34

  
35
@time_recordings = ();
36
push @time_recordings, new_time_recording(start_time => $s1, end_time => $e1);
37

  
38
ok( $time_recordings[0]->is_time_in_wrong_order, 'same start and end detected' );
39
ok( !$time_recordings[0]->is_time_overlapping, 'not overlapping if only one time recording entry in db' );
40

  
41
###
42
$time_recordings[0]->end_time(undef);
43
ok( !$time_recordings[0]->is_time_in_wrong_order, 'order ok if no end' );
44

  
45
########################################
46
# ------------s1-----e1-----
47
# --s2---e2-----------------
48
# -> does not overlap
49
$s1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 12, minute => 0);
50
$e1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 15, minute => 0);
51
$s2 = DateTime->new(year => 2020, month => 11, day => 15, hour => 10, minute => 0);
52
$e2 = DateTime->new(year => 2020, month => 11, day => 15, hour => 11, minute => 0);
53

  
54
clear_up;
55

  
56
@time_recordings = ();
57
push @time_recordings, new_time_recording(start_time => $s1, end_time => $e1)->save;
58
push @time_recordings, new_time_recording(start_time => $s2, end_time => $e2)->save;
59

  
60
ok( !$time_recordings[0]->is_time_overlapping, 'not overlapping: completely before 1' );
61
ok( !$time_recordings[1]->is_time_overlapping, 'not overlapping: completely before 2' );
62

  
63

  
64
# -------s1-----e1----------
65
# --s2---e2-----------------
66
# -> does not overlap
67
$s1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 12, minute => 0);
68
$e1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 15, minute => 0);
69
$s2 = DateTime->new(year => 2020, month => 11, day => 15, hour => 10, minute => 0);
70
$e2 = DateTime->new(year => 2020, month => 11, day => 15, hour => 12, minute => 0);
71

  
72
clear_up;
73

  
74
@time_recordings = ();
75
push @time_recordings, new_time_recording(start_time => $s1, end_time => $e1)->save;
76
push @time_recordings, new_time_recording(start_time => $s2, end_time => $e2)->save;
77

  
78
ok( !$time_recordings[0]->is_time_overlapping, 'not overlapping: before 1' );
79
ok( !$time_recordings[1]->is_time_overlapping, 'not overlapping: before 2' );
80

  
81
# ---s1-----e1--------------
82
# ---------------s2---e2----
83
# -> does not overlap
84
$s1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 10, minute => 0);
85
$e1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 12, minute => 0);
86
$s2 = DateTime->new(year => 2020, month => 11, day => 15, hour => 13, minute => 0);
87
$e2 = DateTime->new(year => 2020, month => 11, day => 15, hour => 14, minute => 0);
88

  
89
clear_up;
90

  
91
@time_recordings = ();
92
push @time_recordings, new_time_recording(start_time => $s1, end_time => $e1)->save;
93
push @time_recordings, new_time_recording(start_time => $s2, end_time => $e2)->save;
94

  
95
ok( !$time_recordings[0]->is_time_overlapping, 'not overlapping: completely after 1' );
96
ok( !$time_recordings[1]->is_time_overlapping, 'not overlapping: completely after 2' );
97

  
98
# ---s1-----e1--------------
99
# ----------s2---e2---------
100
# -> does not overlap
101
$s1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 10, minute => 0);
102
$e1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 12, minute => 0);
103
$s2 = DateTime->new(year => 2020, month => 11, day => 15, hour => 12, minute => 0);
104
$e2 = DateTime->new(year => 2020, month => 11, day => 15, hour => 14, minute => 0);
105

  
106
clear_up;
107

  
108
@time_recordings = ();
109
push @time_recordings, new_time_recording(start_time => $s1, end_time => $e1)->save;
110
push @time_recordings, new_time_recording(start_time => $s2, end_time => $e2)->save;
111

  
112
ok( !$time_recordings[0]->is_time_overlapping, 'not overlapping: after 1' );
113
ok( !$time_recordings[1]->is_time_overlapping, 'not overlapping: after 2' );
114

  
115
# -------s1-----e1----------
116
# ---s2-----e2--------------
117
# -> overlaps
118
$s1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 10, minute => 0);
119
$e1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 15, minute => 0);
120
$s2 = DateTime->new(year => 2020, month => 11, day => 15, hour =>  9, minute => 0);
121
$e2 = DateTime->new(year => 2020, month => 11, day => 15, hour => 14, minute => 0);
122

  
123
clear_up;
124

  
125
@time_recordings = ();
126
push @time_recordings, new_time_recording(start_time => $s1, end_time => $e1)->save;
127
push @time_recordings, new_time_recording(start_time => $s2, end_time => $e2);
128

  
129
ok( $time_recordings[1]->is_time_overlapping, 'overlapping: start before, end inbetween' );
130

  
131
# -------s1-----e1----------
132
# -----------s2-----e2------
133
# -> overlaps
134
$s1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 10, minute => 0);
135
$e1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 15, minute => 0);
136
$s2 = DateTime->new(year => 2020, month => 11, day => 15, hour => 12, minute => 0);
137
$e2 = DateTime->new(year => 2020, month => 11, day => 15, hour => 17, minute => 0);
138

  
139
clear_up;
140

  
141
@time_recordings = ();
142
push @time_recordings, new_time_recording(start_time => $s1, end_time => $e1)->save;
143
push @time_recordings, new_time_recording(start_time => $s2, end_time => $e2);
144

  
145
ok( $time_recordings[1]->is_time_overlapping, 'overlapping: start inbetween, end after' );
146

  
147
# ---s1---------e1----------
148
# ------s2---e2-------------
149
# -> overlaps
150
$s1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 10, minute => 0);
151
$e1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 17, minute => 0);
152
$s2 = DateTime->new(year => 2020, month => 11, day => 15, hour => 12, minute => 0);
153
$e2 = DateTime->new(year => 2020, month => 11, day => 15, hour => 15, minute => 0);
154

  
155
clear_up;
156

  
157
@time_recordings = ();
158
push @time_recordings, new_time_recording(start_time => $s1, end_time => $e1)->save;
159
push @time_recordings, new_time_recording(start_time => $s2, end_time => $e2);
160

  
161
ok( $time_recordings[1]->is_time_overlapping, 'overlapping: completely inbetween' );
162

  
163

  
164
# ------s1---e1-------------
165
# ---s2---------e2----------
166
# -> overlaps
167
$s1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 12, minute => 0);
168
$e1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 14, minute => 0);
169
$s2 = DateTime->new(year => 2020, month => 11, day => 15, hour => 10, minute => 0);
170
$e2 = DateTime->new(year => 2020, month => 11, day => 15, hour => 17, minute => 0);
171

  
172
clear_up;
173

  
174
@time_recordings = ();
175
push @time_recordings, new_time_recording(start_time => $s1, end_time => $e1)->save;
176
push @time_recordings, new_time_recording(start_time => $s2, end_time => $e2);
177

  
178
ok( $time_recordings[1]->is_time_overlapping, 'overlapping: completely oudside' );
179

  
180

  
181
# ---s1---e1----------------
182
# ---s2---------e2----------
183
# -> overlaps
184
$s1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 10, minute => 0);
185
$e1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 14, minute => 0);
186
$s2 = DateTime->new(year => 2020, month => 11, day => 15, hour => 10, minute => 0);
187
$e2 = DateTime->new(year => 2020, month => 11, day => 15, hour => 17, minute => 0);
188

  
189
clear_up;
190

  
191
@time_recordings = ();
192
push @time_recordings, new_time_recording(start_time => $s1, end_time => $e1)->save;
193
push @time_recordings, new_time_recording(start_time => $s2, end_time => $e2);
194

  
195
ok( $time_recordings[1]->is_time_overlapping, 'overlapping: same start, end outside' );
196

  
197
# ---s1------e1-------------
198
# ------s2---e2-------------
199
# -> overlaps
200
$s1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 10, minute => 0);
201
$e1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 14, minute => 0);
202
$s2 = DateTime->new(year => 2020, month => 11, day => 15, hour => 12, minute => 0);
203
$e2 = DateTime->new(year => 2020, month => 11, day => 15, hour => 14, minute => 0);
204

  
205
clear_up;
206

  
207
@time_recordings = ();
208
push @time_recordings, new_time_recording(start_time => $s1, end_time => $e1)->save;
209
push @time_recordings, new_time_recording(start_time => $s2, end_time => $e2);
210

  
211
ok( $time_recordings[1]->is_time_overlapping, 'overlapping: start after, same end' );
212

  
213
# ---s1------e1-------------
214
# ------s2------------------
215
# e2 undef
216
# -> overlaps
217
$s1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 10, minute => 0);
218
$e1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 14, minute => 0);
219
$s2 = DateTime->new(year => 2020, month => 11, day => 15, hour => 12, minute => 0);
220
$e2 = undef;
221

  
222
clear_up;
223

  
224
@time_recordings = ();
225
push @time_recordings, new_time_recording(start_time => $s1, end_time => $e1)->save;
226
push @time_recordings, new_time_recording(start_time => $s2, end_time => $e2);
227

  
228
ok( $time_recordings[1]->is_time_overlapping, 'overlapping: start inbetween, no end' );
229

  
230
# ---s1------e1-------------
231
# ---s2---------------------
232
# e2 undef
233
# -> overlaps
234
$s1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 10, minute => 0);
235
$e1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 14, minute => 0);
236
$s2 = DateTime->new(year => 2020, month => 11, day => 15, hour => 10, minute => 0);
237
$e2 = undef;
238

  
239
clear_up;
240

  
241
@time_recordings = ();
242
push @time_recordings, new_time_recording(start_time => $s1, end_time => $e1)->save;
243
push @time_recordings, new_time_recording(start_time => $s2, end_time => $e2);
244

  
245
ok( $time_recordings[1]->is_time_overlapping, 'overlapping: same start, no end' );
246

  
247
# -------s1------e1---------
248
# ---s2---------------------
249
# e2 undef
250
# -> does not overlap
251
$s1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 12, minute => 0);
252
$e1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 14, minute => 0);
253
$s2 = DateTime->new(year => 2020, month => 11, day => 15, hour => 10, minute => 0);
254
$e2 = undef;
255

  
256
clear_up;
257

  
258
@time_recordings = ();
259
push @time_recordings, new_time_recording(start_time => $s1, end_time => $e1)->save;
260
push @time_recordings, new_time_recording(start_time => $s2, end_time => $e2);
261

  
262
ok( !$time_recordings[1]->is_time_overlapping, 'not overlapping: start before, no end' );
263

  
264
# -------s1------e1---------
265
# -------------------s2-----
266
# e2 undef
267
# -> does not overlap
268
$s1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 12, minute => 0);
269
$e1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 14, minute => 0);
270
$s2 = DateTime->new(year => 2020, month => 11, day => 15, hour => 16, minute => 0);
271
$e2 = undef;
272

  
273
clear_up;
274

  
275
@time_recordings = ();
276
push @time_recordings, new_time_recording(start_time => $s1, end_time => $e1)->save;
277
push @time_recordings, new_time_recording(start_time => $s2, end_time => $e2);
278

  
279
ok( !$time_recordings[1]->is_time_overlapping, 'not overlapping: start after, no end' );
280

  
281
# -------s1------e1---------
282
# ---------------s2---------
283
# e2 undef
284
# -> does not overlap
285

  
286
$s1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 12, minute => 0);
287
$e1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 14, minute => 0);
288
$s2 = DateTime->new(year => 2020, month => 11, day => 15, hour => 14, minute => 0);
289
$e2 = undef;
290

  
291
clear_up;
292

  
293
@time_recordings = ();
294
push @time_recordings, new_time_recording(start_time => $s1, end_time => $e1)->save;
295
push @time_recordings, new_time_recording(start_time => $s2, end_time => $e2);
296

  
297
ok( !$time_recordings[1]->is_time_overlapping, 'not overlapping: same start as other end, no end' );
298

  
299
# -------s1------e1---------
300
# -----------e2-------------
301
# s2 undef
302
# -> overlaps
303
$s1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 10, minute => 0);
304
$e1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 17, minute => 0);
305
$s2 = undef;
306
$e2 = DateTime->new(year => 2020, month => 11, day => 15, hour => 12, minute => 0);
307

  
308
clear_up;
309

  
310
@time_recordings = ();
311
push @time_recordings, new_time_recording(start_time => $s1, end_time => $e1)->save;
312
push @time_recordings, new_time_recording(start_time => $s2, end_time => $e2);
313

  
314
ok( $time_recordings[1]->is_time_overlapping, 'overlapping: no start, end inbetween' );
315

  
316
# -------s1------e1---------
317
# ---------------e2---------
318
# s2 undef
319
# -> overlaps
320
$s1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 10, minute => 0);
321
$e1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 17, minute => 0);
322
$s2 = undef;
323
$e2 = DateTime->new(year => 2020, month => 11, day => 15, hour => 17, minute => 0);
324

  
325
clear_up;
326

  
327
@time_recordings = ();
328
push @time_recordings, new_time_recording(start_time => $s1, end_time => $e1)->save;
329
push @time_recordings, new_time_recording(start_time => $s2, end_time => $e2);
330

  
331
ok( $time_recordings[1]->is_time_overlapping, 'overlapping: no start, same end' );
332

  
333
# -------s1------e1---------
334
# --e2----------------------
335
# s2 undef
336
# -> does not overlap
337
$s1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 12, minute => 0);
338
$e1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 17, minute => 0);
339
$s2 = undef;
340
$e2 = DateTime->new(year => 2020, month => 11, day => 15, hour => 10, minute => 0);
341

  
342
clear_up;
343

  
344
@time_recordings = ();
345
push @time_recordings, new_time_recording(start_time => $s1, end_time => $e1)->save;
346
push @time_recordings, new_time_recording(start_time => $s2, end_time => $e2);
347

  
348
ok( !$time_recordings[1]->is_time_overlapping, 'not overlapping: no start, end before' );
349

  
350
# -------s1------e1---------
351
# -------------------e2-----
352
# s2 undef
353
# -> does not overlap
354
$s1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 12, minute => 0);
355
$e1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 15, minute => 0);
356
$s2 = undef;
357
$e2 = DateTime->new(year => 2020, month => 11, day => 15, hour => 17, minute => 0);
358

  
359
clear_up;
360

  
361
@time_recordings = ();
362
push @time_recordings, new_time_recording(start_time => $s1, end_time => $e1)->save;
363
push @time_recordings, new_time_recording(start_time => $s2, end_time => $e2);
364

  
365
ok( !$time_recordings[1]->is_time_overlapping, 'not overlapping: no start, end after' );
366

  
367
# -------s1------e1---------
368
# -------e2-----------------
369
# s2 undef
370
# -> does not overlap
371
$s1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 12, minute => 0);
372
$e1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 15, minute => 0);
373
$s2 = undef;
374
$e2 = DateTime->new(year => 2020, month => 11, day => 15, hour => 12, minute => 0);
375

  
376
clear_up;
377

  
378
@time_recordings = ();
379
push @time_recordings, new_time_recording(start_time => $s1, end_time => $e1)->save;
380
push @time_recordings, new_time_recording(start_time => $s2, end_time => $e2);
381

  
382
ok( !$time_recordings[1]->is_time_overlapping, 'not overlapping: no start, same end as other start' );
383

  
384
# ----s1--------------------
385
# ----s2-----e2-------------
386
# e1 undef
387
# -> overlaps
388
$s1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 12, minute => 0);
389
$e1 = undef;
390
$s2 = DateTime->new(year => 2020, month => 11, day => 15, hour => 12, minute => 0);
391
$e2 = DateTime->new(year => 2020, month => 11, day => 15, hour => 14, minute => 0);
392

  
393
clear_up;
394

  
395
@time_recordings = ();
396
push @time_recordings, new_time_recording(start_time => $s1, end_time => $e1)->save;
397
push @time_recordings, new_time_recording(start_time => $s2, end_time => $e2);
398

  
399
ok( $time_recordings[1]->is_time_overlapping, 'overlapping: no end in db, same start' );
400

  
401
# --------s1----------------
402
# ----s2-----e2-------------
403
# e1 undef
404
# -> does not overlap
405
$s1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 12, minute => 0);
406
$e1 = undef;
407
$s2 = DateTime->new(year => 2020, month => 11, day => 15, hour => 10, minute => 0);
408
$e2 = DateTime->new(year => 2020, month => 11, day => 15, hour => 14, minute => 0);
409

  
410
clear_up;
411

  
412
@time_recordings = ();
413
push @time_recordings, new_time_recording(start_time => $s1, end_time => $e1)->save;
414
push @time_recordings, new_time_recording(start_time => $s2, end_time => $e2);
415

  
416
ok( !$time_recordings[1]->is_time_overlapping, 'not overlapping: no end in db, enclosing' );
417

  
418
# ---s1---------------------
419
# ---------s2-----e2--------
420
# e1 undef
421
# -> does not overlap
422
$s1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 10, minute => 0);
423
$e1 = undef;
424
$s2 = DateTime->new(year => 2020, month => 11, day => 15, hour => 12, minute => 0);
425
$e2 = DateTime->new(year => 2020, month => 11, day => 15, hour => 14, minute => 0);
426

  
427
clear_up;
428

  
429
@time_recordings = ();
430
push @time_recordings, new_time_recording(start_time => $s1, end_time => $e1)->save;
431
push @time_recordings, new_time_recording(start_time => $s2, end_time => $e2);
432

  
433
ok( !$time_recordings[1]->is_time_overlapping, 'not overlapping: no end in db, completely after' );
434

  
435
# ---------s1---------------
436
# --------------------------
437
# e1, s2, e2 undef
438
# -> does not overlap
439
$s1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 12, minute => 0);
440
$e1 = undef;
441
$s2 = undef;
442
$e2 = undef;
443

  
444
clear_up;
445

  
446
@time_recordings = ();
447
push @time_recordings, new_time_recording(start_time => $s1, end_time => $e1)->save;
448
push @time_recordings, new_time_recording(start_time => $s2, end_time => $e2);
449

  
450
ok( !$time_recordings[1]->is_time_overlapping, 'not overlapping: no end in db, no times in object' );
451

  
452
# ---------s1---------------
453
# -----s2-------------------
454
# e1, e2 undef
455
# -> does not overlap
456
$s1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 12, minute => 0);
457
$e1 = undef;
458
$s2 = DateTime->new(year => 2020, month => 11, day => 15, hour => 10, minute => 0);
459
$e2 = undef;
460

  
461
clear_up;
462

  
463
@time_recordings = ();
464
push @time_recordings, new_time_recording(start_time => $s1, end_time => $e1)->save;
465
push @time_recordings, new_time_recording(start_time => $s2, end_time => $e2);
466

  
467
ok( !$time_recordings[1]->is_time_overlapping, 'not overlapping: no end in db, start before, no end in object' );
468

  
469
# ---------s1---------------
470
# -------------s2-----------
471
# e1, e2 undef
472
# -> does not overlap
473
$s1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 12, minute => 0);
474
$e1 = undef;
475
$s2 = DateTime->new(year => 2020, month => 11, day => 15, hour => 14, minute => 0);
476
$e2 = undef;
477

  
478
clear_up;
479

  
480
@time_recordings = ();
481
push @time_recordings, new_time_recording(start_time => $s1, end_time => $e1)->save;
482
push @time_recordings, new_time_recording(start_time => $s2, end_time => $e2);
483

  
484
ok( !$time_recordings[1]->is_time_overlapping, 'not overlapping: no end in db, start after, no end in object' );
485

  
486
# ---------s1---------------
487
# ---------s2---------------
488
# e1, e2 undef
489
# -> overlaps
490
$s1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 12, minute => 0);
491
$e1 = undef;
492
$s2 = DateTime->new(year => 2020, month => 11, day => 15, hour => 12, minute => 0);
493
$e2 = undef;
494

  
495
clear_up;
496

  
497
@time_recordings = ();
498
push @time_recordings, new_time_recording(start_time => $s1, end_time => $e1)->save;
499
push @time_recordings, new_time_recording(start_time => $s2, end_time => $e2);
500

  
501
ok( $time_recordings[1]->is_time_overlapping, 'overlapping: no end in db, same start' );
502

  
503
# ---------s1---------------
504
# ---e2---------------------
505
# e1, s2 undef
506
# -> does not overlap
507
$s1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 12, minute => 0);
508
$e1 = undef;
509
$s2 = undef;
510
$e2 = DateTime->new(year => 2020, month => 11, day => 15, hour => 10, minute => 0);
511

  
512
clear_up;
513

  
514
@time_recordings = ();
515
push @time_recordings, new_time_recording(start_time => $s1, end_time => $e1)->save;
516
push @time_recordings, new_time_recording(start_time => $s2, end_time => $e2);
517

  
518
ok( !$time_recordings[1]->is_time_overlapping, 'not overlapping: no end in db, no start in object, end before' );
519

  
520
# ---------s1---------------
521
# ---------------e2---------
522
# e1, s2 undef
523
# -> does not overlap
524
$s1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 12, minute => 0);
525
$e1 = undef;
526
$s2 = undef;
527
$e2 = DateTime->new(year => 2020, month => 11, day => 15, hour => 14, minute => 0);
528

  
529
clear_up;
530

  
531
@time_recordings = ();
532
push @time_recordings, new_time_recording(start_time => $s1, end_time => $e1)->save;
533
push @time_recordings, new_time_recording(start_time => $s2, end_time => $e2);
534

  
535
ok( !$time_recordings[1]->is_time_overlapping, 'not overlapping: no end in db, no start in object, end after' );
536

  
537
# ---------s1---------------
538
# ---------e2---------------
539
# e1, s2 undef
540
# -> does not overlap
541
$s1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 12, minute => 0);
542
$e1 = undef;
543
$s2 = undef;
544
$e2 = DateTime->new(year => 2020, month => 11, day => 15, hour => 12, minute => 0);
545

  
546
clear_up;
547

  
548
@time_recordings = ();
549
push @time_recordings, new_time_recording(start_time => $s1, end_time => $e1)->save;
550
push @time_recordings, new_time_recording(start_time => $s2, end_time => $e2);
551

  
552
ok( !$time_recordings[1]->is_time_overlapping, 'not overlapping: no end in db, no start in object, same end' );
553

  
554
########################################
555
# not overlapping if different staff_member
556
$s1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 10, minute => 0);
557
$e1 = DateTime->new(year => 2020, month => 11, day => 15, hour => 15, minute => 0);
558
$s2 = DateTime->new(year => 2020, month => 11, day => 15, hour => 11, minute => 0);
559
$e2 = DateTime->new(year => 2020, month => 11, day => 15, hour => 14, minute => 0);
560

  
561
clear_up;
562

  
563
@time_recordings = ();
564
push @time_recordings, new_time_recording(start_time => $s1, end_time => $e1)->save;
565
push @time_recordings, new_time_recording(start_time => $s2, end_time => $e2);
566

  
567
ok( $time_recordings[1]->is_time_overlapping, 'overlapping if same staff member' );
568
$time_recordings[1]->update_attributes(staff_member => SL::DB::Employee->new(
569
                                         'login' => 'testuser',
570
                                         'name'  => 'Test User',
571
                                       )->save);
572
ok( !$time_recordings[1]->is_time_overlapping, 'not overlapping if different staff member' );
573

  
574
clear_up;
575

  
576
1;
577

  
578

  
579
# set emacs to perl mode
580
# Local Variables:
581
# mode: perl
582
# End:
templates/webpages/time_recording/_filter.html
1
[%- USE T8 %]
2
[%- USE L %]
3
[%- USE LxERP %]
4
[%- USE HTML %]
5
<form action='controller.pl' method='post' id='filter_form'>
6
<div class='filter_toggle'>
7
<a href='#' onClick='javascript:$(".filter_toggle").toggle()'>[% 'Show Filter' | $T8 %]</a>
8
  [% SELF.filter_summary | html %]
9
</div>
10
<div class='filter_toggle' style='display:none'>
11
<a href='#' onClick='javascript:$(".filter_toggle").toggle()'>[% 'Hide Filter' | $T8 %]</a>
12
 <table id='filter_table'>
13
  <tr>
14
   <th align="right">[% 'Start' | $T8 %] [% 'From Date' | $T8 %]</th>
15
   <td>[% L.date_tag('filter.start_time:date::ge', filter.start_time_date__ge) %]</td>
16
  </tr>
17
  <tr>
18
   <th align="right">[% 'Start' | $T8 %] [% 'To Date' | $T8 %]</th>
19
   <td>[% L.date_tag('filter.start_time:date::le', filter.start_time_date__le) %]</td>
20
  </tr>
21
  <tr>
22
    <th align="right">[% 'Customer' | $T8 %]</th>
23
    <td>[% L.input_tag('filter.customer.name:substr::ilike', filter.customer.name_substr__ilike, size = 20) %]</td>
24
  </tr>
25
  <tr>
26
    <th align="right">[% 'Customer Number' | $T8 %]</th>
27
    <td>[% L.input_tag('filter.customer.customernumber:substr::ilike', filter.customer.customernumber_substr__ilike, size = 20) %]</td>
28
  </tr>
29
  <tr>
30
   <th align="right">[% 'Mitarbeiter' | $T8 %]</th>
31
   <td>
32
     [% L.select_tag('filter.staff_member_id', SELF.all_employees,
33
                     default    => filter.staff_member_id,
34
                     title_key  => 'name',
35
                     value_key  => 'id',
36
                     with_empty => 1,
37
                     style      => 'width: 200px') %]
38
   </td>
39
  </tr>
40
 </table>
41

  
42
[% L.hidden_tag('sort_by', FORM.sort_by) %]
43
[% L.hidden_tag('sort_dir', FORM.sort_dir) %]
44
[% L.hidden_tag('page', FORM.page) %]
45
[% L.button_tag('$("#filter_form").clearForm()', LxERP.t8('Reset')) %]
46
</div>
47

  
48
</form>
templates/webpages/time_recording/form.html
1
[% USE L %]
2
[% USE P %]
3
[% USE T8 %]
4
[% USE LxERP %]
5

  
6
<h1>[% title %]</h1>
7

  
8
[%- INCLUDE 'common/flash.html' %]
9

  
10
<form method="post" action="controller.pl" id="form">
11
  [% P.hidden_tag('id',       SELF.time_recording.id) %]
12
  [% L.hidden_tag('callback', FORM.callback) %]
13

  
14
  <table>
15
    <thead class="listheading">
16
      <th>[% 'Start' | T8 %]</th>
17
      <th>[% 'End' | T8 %]</th>
18
      <th>[% 'Customer' | T8 %]</th>
19
      <th>[% 'Type' | T8 %]</th>
20
      <th>[% 'Project' | T8 %]</th>
21
      <th>[% 'Description' | T8 %]</th>
22
    </thead>
23
    <tbody valign="top">
24
      <td>
25
        [% P.date_tag('start_date',  SELF.start_date, "data-validate"="required", "data-title"=LxERP.t8('Start date'), onchange='kivi.TimeRecording.set_end_date()') %]<br>
26
        [% P.input_tag('start_time', SELF.start_time, type="time", "data-validate"="required", "data-title"=LxERP.t8('Start time')) %]
27
        [% P.button_tag('kivi.TimeRecording.set_current_date_time("start")', LxERP.t8('now')) %]
28
      </td>
29
      <td>
30
        [% P.date_tag('end_date',  SELF.end_date) %]<br>
31
        [% P.input_tag('end_time', SELF.end_time, type="time") %]
32
        [% P.button_tag('kivi.TimeRecording.set_current_date_time("end")', LxERP.t8('now')) %]
33
      </td>
34
      <td>[% P.customer_vendor.picker('time_recording.customer_id', SELF.time_recording.customer_id, type='customer', style='width: 300px', "data-validate"="required", "data-title"=LxERP.t8('Customer')) %]</td>
35
      <td>[% P.select_tag('time_recording.type_id', SELF.all_time_recording_types, default=SELF.time_recording.type.id, with_empty=1, title_key='abbreviation') %]</td>
36
      <td>[% P.project.picker('time_recording.project_id', SELF.time_recording.project_id, style='width: 300px') %]</td>
37
      <td>[% L.textarea_tag('time_recording.description', SELF.time_recording.description, wrap="soft", style="width: 350px; height: 150px", class="texteditor", "data-validate"="required", "data-title"=LxERP.t8('Description')) %]</td>
38
    </tbody>
39
  </table>
40

  
41
</form>
templates/webpages/time_recording/report_bottom.html
1
[% USE HTML%]
2
[%- USE T8 %]
3
[%- USE L %][%- USE LxERP -%]
4
 [% L.paginate_controls(models=SELF.models) %]
5
 <input type="hidden" name="rowcount" value="[% HTML.escape(rowcount) %]">
6
 [%- FOREACH item = HIDDEN %]
7
 <input type="hidden" name="[% HTML.escape(item.key) %]" value="[% HTML.escape(item.value) %]">
8
 [%- END %]
9
</form>
templates/webpages/time_recording/report_top.html
1
[%- PROCESS 'time_recording/_filter.html' filter=SELF.models.filtered.laundered %]
2
<hr>

Auch abrufbar als: Unified diff