use SL::GenericTranslations;
use SL::Helper::UserPreferences::PositionsScrollbar;
use SL::Helper::UserPreferences::PartPickerSearch;
+use SL::Helper::UserPreferences::TimeRecording;
use SL::Helper::UserPreferences::UpdatePositions;
use strict;
SL::Helper::UserPreferences::UpdatePositions->new()->get_show_update_button();
}
+sub time_recording_use_duration {
+ SL::Helper::UserPreferences::TimeRecording->new()->get_use_duration();
+}
+
sub save_preferences {
$main::lxdebug->enter_sub();
if (exists $form->{positions_show_update_button}) {
SL::Helper::UserPreferences::UpdatePositions->new()->store_show_update_button($form->{positions_show_update_button})
}
+ if (exists $form->{time_recording_use_duration}) {
+ SL::Helper::UserPreferences::TimeRecording->new()->store_use_duration($form->{time_recording_use_duration})
+ }
$main::lxdebug->leave_sub();
use SL::DB::Part;
use SL::DB::TimeRecording;
use SL::DB::TimeRecordingArticle;
+use SL::Helper::Flash qw(flash);
+use SL::Helper::UserPreferences::TimeRecording;
use SL::Locale::String qw(t8);
use SL::ReportGenerator;
use Rose::Object::MakeMethods::Generic
(
# scalar => [ qw() ],
- 'scalar --get_set_init' => [ qw(time_recording models all_employees all_time_recording_articles can_view_all can_edit_all) ],
+ 'scalar --get_set_init' => [ qw(time_recording models all_employees all_time_recording_articles can_view_all can_edit_all use_duration) ],
);
$::request->{layout}->use_javascript("${_}.js") for qw(kivi.TimeRecording ckeditor/ckeditor ckeditor/adapters/jquery kivi.Validator);
+ if ($self->use_duration) {
+ flash('warning', t8('This entry is using start and end time. This information will be overwritten on saving.')) if !$self->time_recording->is_duration_used;
+ } else {
+ flash('warning', t8('This entry is using date and duration. This information will be overwritten on saving.')) if $self->time_recording->is_duration_used;
+ }
+
if ($self->time_recording->start_time) {
$self->{start_date} = $self->time_recording->start_time->to_kivitendo;
$self->{start_time} = $self->time_recording->start_time->to_kivitendo_time;
sub action_save {
my ($self) = @_;
+ if ($self->use_duration) {
+ $self->time_recording->start_date(undef);
+ $self->time_recording->end_date(undef);
+ }
+
my @errors = $self->time_recording->validate;
if (@errors) {
$::form->error(t8('Saving the time recording entry failed: #1', join '<br>', @errors));
}
sub init_time_recording {
+ my ($self) = @_;
+
my $is_new = !$::form->{id};
- my $time_recording = $is_new ? SL::DB::TimeRecording->new(start_time => DateTime->now_local)
- : SL::DB::TimeRecording->new(id => $::form->{id})->load;
+ my $time_recording = !$is_new ? SL::DB::TimeRecording->new(id => $::form->{id})->load
+ : $self->use_duration ? SL::DB::TimeRecording->new(date => DateTime->today_local)
+ : SL::DB::TimeRecording->new(start_time => DateTime->now_local);
my %attributes = %{ $::form->{time_recording} || {} };
- foreach my $type (qw(start end)) {
- if ($::form->{$type . '_date'}) {
- my $date = DateTime->from_kivitendo($::form->{$type . '_date'});
- $attributes{$type . '_time'} = $date->clone;
- if ($::form->{$type . '_time'}) {
- my ($hour, $min) = split ':', $::form->{$type . '_time'};
- $attributes{$type . '_time'}->set_hour($hour) if $hour;
- $attributes{$type . '_time'}->set_minute($min) if $min;
+ if (!$self->use_duration) {
+ foreach my $type (qw(start end)) {
+ if ($::form->{$type . '_date'}) {
+ my $date = DateTime->from_kivitendo($::form->{$type . '_date'});
+ $attributes{$type . '_time'} = $date->clone;
+ if ($::form->{$type . '_time'}) {
+ my ($hour, $min) = split ':', $::form->{$type . '_time'};
+ $attributes{$type . '_time'}->set_hour($hour) if $hour;
+ $attributes{$type . '_time'}->set_minute($min) if $min;
+ }
}
}
}
- # do not overwright staff member if you do not have the right
+ # do not overwrite staff member if you do not have the right
delete $attributes{staff_member_id} if !$_[0]->can_edit_all;
$attributes{staff_member_id} = SL::DB::Manager::Employee->current->id if $is_new;
return $res;
}
+sub init_use_duration {
+ return SL::Helper::UserPreferences::TimeRecording->new()->get_use_duration();
+}
+
sub check_auth {
$::auth->assert('time_recording');
}
my $staff_member = $filter->{staff_member_id} ? SL::DB::Employee->new(id => $filter->{staff_member_id})->load->safe_name : '';
my @filters = (
- [ $filter->{"start_time:date::ge"}, t8('From Start') ],
- [ $filter->{"start_time:date::le"}, t8('To Start') ],
+ [ $filter->{"date:date::ge"}, t8('From Date') ],
+ [ $filter->{"date:date::le"}, t8('To Date') ],
[ $filter->{"customer"}->{"name:substr::ilike"}, t8('Customer') ],
[ $filter->{"customer"}->{"customernumber:substr::ilike"}, t8('Customer Number') ],
[ $staff_member, t8('Mitarbeiter') ],
my @errors;
- push @errors, t8('Start time must not be empty.') if !$self->start_time;
push @errors, t8('Customer must not be empty.') if !$self->customer_id;
push @errors, t8('Staff member must not be empty.') if !$self->staff_member_id;
push @errors, t8('Employee must not be empty.') if !$self->employee_id;
return;
}
+sub is_duration_used {
+ return !$_[0]->start_time;
+}
+
sub displayable_times {
my ($self) = @_;
--- /dev/null
+package SL::Helper::UserPreferences::TimeRecording;
+
+use strict;
+use parent qw(Rose::Object);
+
+use Carp;
+use List::MoreUtils qw(none);
+
+use SL::Helper::UserPreferences;
+
+use Rose::Object::MakeMethods::Generic (
+ 'scalar --get_set_init' => [ qw(user_prefs) ],
+);
+
+sub get_use_duration {
+ !!$_[0]->user_prefs->get('use_duration');
+}
+
+sub store_use_duration {
+ $_[0]->user_prefs->store('use_duration', $_[1]);
+}
+
+sub init_user_prefs {
+ SL::Helper::UserPreferences->new(
+ namespace => $_[0]->namespace,
+ )
+}
+
+# read only stuff
+sub namespace { 'TimeRecording' }
+sub version { 1 }
+
+1;
+
+__END__
+
+=pod
+
+=encoding utf-8
+
+=head1 NAME
+
+SL::Helper::UserPreferences::TimeRecording - preferences intended
+to store user settings for using the time recording functionality.
+
+=head1 SYNOPSIS
+
+ use SL::Helper::UserPreferences::TimeRecording;
+ my $prefs = SL::Helper::UserPreferences::TimeRecording->new();
+
+ $prefs->store_use_duration(1);
+ my $value = $prefs->get_use_duration;
+
+=head1 DESCRIPTION
+
+This module manages storing the user's choise for settings for
+the time recording controller.
+For now it can be choosen if an entry is done by entering start and
+end time or a date and a duration.
+
+=head1 BUGS
+
+None yet :)
+
+=head1 AUTHOR
+
+Bernd Bleßmann E<lt>bernd@kivitendo-premium.deE<gt>
+
+=cut
$form->{purchase_search_makemodel} = AM->purchase_search_makemodel();
$form->{sales_search_customer_partnumber} = AM->sales_search_customer_partnumber();
$form->{positions_show_update_button} = AM->positions_show_update_button();
+ $form->{time_recording_use_duration} = AM->time_recording_use_duration();
$myconfig{show_form_details} = 1 unless (defined($myconfig{show_form_details}));
$form->{CAN_CHANGE_PASSWORD} = $main::auth->can_change_password();
'Fristsetzung' => 'Fristsetzung',
'From' => 'Von',
'From Date' => 'Von',
- 'From Start' => 'Ab Start',
'From bin' => 'Ausgelagert',
'From shop "#1" : #2 ' => 'Shop #1 : #2',
'From shop #1 : #2 shoporders have been fetched.' => 'Es wurden #2 Bestellungen von #1 geholt.',
'Start the correction assistant' => 'Korrekturassistenten starten',
'Start time' => 'Startzeit',
'Start time must be earlier than end time.' => 'Startzeit muss vor der Endzeit liegen.',
- 'Start time must not be empty.' => 'Startzeit darf nicht leer sein.',
'Startdate method' => 'Methode zur Ermittlung des Startdatums',
'Startdate_coa' => 'Gültig ab',
'Starting Balance' => 'Eröffnungsbilanzwerte',
'This discount is only valid in purchase documents' => 'Dieser Rabatt ist nur in Einkaufsdokumenten gültig',
'This discount is only valid in records with customer or vendor' => 'Dieser Rabatt ist nur in Dokumenten mit Kunde oder Lieferant gültig',
'This discount is only valid in sales documents' => 'Dieser Rabatt ist nur in Verkaufsdokumenten gültig',
+ 'This entry is using date and duration. This information will be overwritten on saving.' => 'Dieser Eintrag verwendet Datum und Dauer. Diese Information wird beim Speichern überschrieben.',
+ 'This entry is using start and end time. This information will be overwritten on saving.' => 'Dieser Eintrag verwendet Start- und End-Zeit. Diese Information wird beim Speichern überschrieben.',
'This export will include all records in the given time range and all supplicant information from checked entities. You will receive a single zip file. Please extract this file onto the data medium requested by your auditor.' => 'Dieser Export umfasst alle Belege im gewählten Zeitrahmen und die dazugehörgen Informationen aus den gewählten Blöcken. Sie erhalten eine einzelne Zip-Datei. Bitte entpacken Sie diese auf das Medium das Ihr Steuerprüfer wünscht.',
'This feature especially prevents mistakes by mixing up prior tax and sales tax.' => 'Dieses Feature vermeidet insbesondere Verwechslungen von Umsatz- und Vorsteuer.',
'This field must not be empty.' => 'Dieses Feld darf nicht leer sein.',
'To (email)' => 'An',
'To (time)' => 'Bis',
'To Date' => 'Bis',
- 'To Start' => 'Bis Start',
'To continue please change the taxkey 0 to another value.' => 'Um fortzufahren, ändern Sie bitte den Steuerschlüssel 0 auf einen anderen Wert.',
'To import' => 'Zu importieren',
'To upload images: Please create shoppart first' => 'Um Bilder hochzuladen bitte Shopartikel zuerst anlegen',
'Use a text field to enter (new) contact titles if enabled. Otherwise, only a drop down box is offered.' => 'Textfeld zusätzlich zur Eingabe (neuer) Titel von Ansprechpersonen verwenden. Sonst wird nur eine Auswahlliste angezeigt.',
'Use a text field to enter (new) greetings if enabled. Otherwise, only a drop down box is offered.' => 'Textfeld zusätzlich zur Eingabe (neuer) Anreden verwenden. Sonst wird nur eine Auswahlliste angezeigt.',
'Use as new' => 'Als neu verwenden',
+ 'Use date and duration for time recordings' => 'Datum und Dauer für Zeiterfassung verwenden',
'Use default booking group because setting is \'all\'' => 'Standardbuchungsgruppe wird verwendet',
'Use default booking group because wanted is missing' => 'Fehlende Buchungsgruppe, deshalb Standardbuchungsgruppe',
'Use default warehouse for assembly transfer' => 'Zum Fertigen Standardlager des Bestandteils verwenden',
'http' => 'http',
'https' => 'https',
'imported' => 'Importiert',
+ 'in minutes' => 'in Minuten',
'inactive' => 'inaktiv',
'income' => 'Einnahmen-Überschuß-Rechnung',
'internal error (see details)' => 'Interner Fehler (siehe Details)!',
'Fristsetzung' => '',
'From' => '',
'From Date' => '',
- 'From Start' => '',
'From bin' => '',
'From shop "#1" : #2 ' => '',
'From shop #1 : #2 shoporders have been fetched.' => '',
'Start the correction assistant' => '',
'Start time' => '',
'Start time must be earlier than end time.' => '',
- 'Start time must not be empty.' => '',
'Startdate method' => '',
'Startdate_coa' => '',
'Starting Balance' => '',
'This discount is only valid in purchase documents' => '',
'This discount is only valid in records with customer or vendor' => '',
'This discount is only valid in sales documents' => '',
+ 'This entry is using date and duration. This information will be overwritten on saving.' => '',
+ 'This entry is using start and end time. This information will be overwritten on saving.' => '',
'This export will include all records in the given time range and all supplicant information from checked entities. You will receive a single zip file. Please extract this file onto the data medium requested by your auditor.' => '',
'This feature especially prevents mistakes by mixing up prior tax and sales tax.' => '',
'This field must not be empty.' => '',
'To (email)' => '',
'To (time)' => '',
'To Date' => '',
- 'To Start' => '',
'To continue please change the taxkey 0 to another value.' => '',
'To import' => '',
'To upload images: Please create shoppart first' => '',
'Use a text field to enter (new) contact titles if enabled. Otherwise, only a drop down box is offered.' => '',
'Use a text field to enter (new) greetings if enabled. Otherwise, only a drop down box is offered.' => '',
'Use as new' => '',
+ 'Use date and duration for time recordings' => '',
'Use default booking group because setting is \'all\'' => '',
'Use default booking group because wanted is missing' => '',
'Use default warehouse for assembly transfer' => '',
'http' => '',
'https' => '',
'imported' => '',
+ 'in minutes' => '',
'inactive' => '',
'income' => 'GUV and BWA',
'internal error (see details)' => '',
</td>
</tr>
+ <tr>
+ <th align="right">[% 'Use date and duration for time recordings' | $T8 %]</th>
+ <td>
+ [% L.yes_no_tag('time_recording_use_duration', time_recording_use_duration) %]
+ </td>
+ </tr>
+
</table>
</div>
<table>
<thead class="listheading">
<tr>
+ [%- IF SELF.use_duration %]
+ <th>[% 'Date' | $T8 %]</th>
+ <th>[% 'Duration' | $T8 %] ([% 'in minutes' | $T8 %])</th>
+ [%- ELSE %]
<th>[% 'Start' | $T8 %]</th>
- <th>[% 'End' | $T8 %]</th>
+ <th>[% 'End' | $T8 %]</th>
+ [%- END %]
<th>[% 'Customer' | $T8 %]</th>
<th>[% 'Article' | $T8 %]</th>
<th>[% 'Project' | $T8 %]</th>
</thead>
<tbody valign="top">
<tr>
+ [%- IF SELF.use_duration %]
+ <td>
+ [% P.date_tag('time_recording.date_as_date', SELF.time_recording.date_as_date, "data-validate"="required", "data-title"=LxERP.t8('Date')) %]<br>
+ </td>
+ <td>
+ [% P.input_tag('time_recording.duration', SELF.time_recording.duration, size=15) %]
+ </td>
+ [%- ELSE %]
<td>
[% P.date_tag('start_date', SELF.start_date, "data-validate"="required", "data-title"=LxERP.t8('Start date'), onchange='kivi.TimeRecording.set_end_date()') %]<br>
[% P.input_tag('start_time', SELF.start_time, type="time", "data-validate"="required", "data-title"=LxERP.t8('Start time')) %]
[% P.input_tag('end_time', SELF.end_time, type="time") %]
[% P.button_tag('kivi.TimeRecording.set_current_date_time("end")', LxERP.t8('now')) %]
</td>
+ [%- END %]
<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>
<td>[% P.select_tag('time_recording.part_id', SELF.all_time_recording_articles, default=SELF.time_recording.part_id, with_empty=1, value_key='id', title_key='description') %]</td>
<td>[% P.project.picker('time_recording.project_id', SELF.time_recording.project_id, style='width: 300px') %]</td>