From 33823a7743de188f6e37802716ee5bd877a3ec5f Mon Sep 17 00:00:00 2001 From: =?utf8?q?Bernd=20Ble=C3=9Fmann?= Date: Wed, 28 Apr 2021 14:33:19 +0200 Subject: [PATCH] =?utf8?q?Zeiterfassung:=20Datum/Dauer=20statt=20Start/End?= =?utf8?q?e=20w=C3=A4hlbar=20(Benutzereinstellung)?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- SL/AM.pm | 8 +++ SL/Controller/TimeRecording.pm | 50 ++++++++++----- SL/DB/TimeRecording.pm | 5 +- SL/Helper/UserPreferences/TimeRecording.pm | 69 +++++++++++++++++++++ bin/mozilla/am.pl | 1 + locale/de/all | 7 ++- locale/en/all | 7 ++- templates/webpages/am/config.html | 7 +++ templates/webpages/time_recording/form.html | 16 ++++- 9 files changed, 148 insertions(+), 22 deletions(-) create mode 100644 SL/Helper/UserPreferences/TimeRecording.pm diff --git a/SL/AM.pm b/SL/AM.pm index b7c60033b..57e6b445a 100644 --- a/SL/AM.pm +++ b/SL/AM.pm @@ -54,6 +54,7 @@ use SL::DB; 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; @@ -546,6 +547,10 @@ sub positions_show_update_button { 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(); @@ -583,6 +588,9 @@ sub save_preferences { 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(); diff --git a/SL/Controller/TimeRecording.pm b/SL/Controller/TimeRecording.pm index 821512252..ed1d9b93e 100644 --- a/SL/Controller/TimeRecording.pm +++ b/SL/Controller/TimeRecording.pm @@ -14,13 +14,15 @@ use SL::DB::Employee; 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) ], ); @@ -65,6 +67,12 @@ sub action_edit { $::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; @@ -84,6 +92,11 @@ sub action_edit { 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 '
', @errors)); @@ -107,25 +120,30 @@ sub action_delete { } 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; @@ -178,6 +196,10 @@ sub init_all_time_recording_articles { return $res; } +sub init_use_duration { + return SL::Helper::UserPreferences::TimeRecording->new()->get_use_duration(); +} + sub check_auth { $::auth->assert('time_recording'); } @@ -255,8 +277,8 @@ sub make_filter_summary { 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') ], diff --git a/SL/DB/TimeRecording.pm b/SL/DB/TimeRecording.pm index 62143b309..9b1a2e351 100644 --- a/SL/DB/TimeRecording.pm +++ b/SL/DB/TimeRecording.pm @@ -33,7 +33,6 @@ sub validate { 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; @@ -109,6 +108,10 @@ sub is_time_in_wrong_order { return; } +sub is_duration_used { + return !$_[0]->start_time; +} + sub displayable_times { my ($self) = @_; diff --git a/SL/Helper/UserPreferences/TimeRecording.pm b/SL/Helper/UserPreferences/TimeRecording.pm new file mode 100644 index 000000000..7686a7cd5 --- /dev/null +++ b/SL/Helper/UserPreferences/TimeRecording.pm @@ -0,0 +1,69 @@ +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 Ebernd@kivitendo-premium.deE + +=cut diff --git a/bin/mozilla/am.pl b/bin/mozilla/am.pl index d04156187..0b35fc7d9 100644 --- a/bin/mozilla/am.pl +++ b/bin/mozilla/am.pl @@ -664,6 +664,7 @@ sub config { $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(); diff --git a/locale/de/all b/locale/de/all index 8b28550ed..f88cbf52c 100755 --- a/locale/de/all +++ b/locale/de/all @@ -1542,7 +1542,6 @@ $self->{texts} = { '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.', @@ -3137,7 +3136,6 @@ $self->{texts} = { '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', @@ -3694,6 +3692,8 @@ $self->{texts} = { '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.', @@ -3770,7 +3770,6 @@ $self->{texts} = { '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', @@ -3945,6 +3944,7 @@ $self->{texts} = { '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', @@ -4269,6 +4269,7 @@ $self->{texts} = { 'http' => 'http', 'https' => 'https', 'imported' => 'Importiert', + 'in minutes' => 'in Minuten', 'inactive' => 'inaktiv', 'income' => 'Einnahmen-Überschuß-Rechnung', 'internal error (see details)' => 'Interner Fehler (siehe Details)!', diff --git a/locale/en/all b/locale/en/all index 4921c29bf..1f5bfeaad 100644 --- a/locale/en/all +++ b/locale/en/all @@ -1542,7 +1542,6 @@ $self->{texts} = { 'Fristsetzung' => '', 'From' => '', 'From Date' => '', - 'From Start' => '', 'From bin' => '', 'From shop "#1" : #2 ' => '', 'From shop #1 : #2 shoporders have been fetched.' => '', @@ -3136,7 +3135,6 @@ $self->{texts} = { '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' => '', @@ -3692,6 +3690,8 @@ $self->{texts} = { '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.' => '', @@ -3768,7 +3768,6 @@ $self->{texts} = { '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' => '', @@ -3943,6 +3942,7 @@ $self->{texts} = { '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' => '', @@ -4267,6 +4267,7 @@ $self->{texts} = { 'http' => '', 'https' => '', 'imported' => '', + 'in minutes' => '', 'inactive' => '', 'income' => 'GUV and BWA', 'internal error (see details)' => '', diff --git a/templates/webpages/am/config.html b/templates/webpages/am/config.html index 6e65c4d57..6901cba8c 100644 --- a/templates/webpages/am/config.html +++ b/templates/webpages/am/config.html @@ -87,6 +87,13 @@ + + [% 'Use date and duration for time recordings' | $T8 %] + + [% L.yes_no_tag('time_recording_use_duration', time_recording_use_duration) %] + + + diff --git a/templates/webpages/time_recording/form.html b/templates/webpages/time_recording/form.html index 32207b538..99e9c0f00 100644 --- a/templates/webpages/time_recording/form.html +++ b/templates/webpages/time_recording/form.html @@ -15,8 +15,13 @@ + [%- IF SELF.use_duration %] + + + [%- ELSE %] - + + [%- END %] @@ -26,6 +31,14 @@ + [%- IF SELF.use_duration %] + + + [%- ELSE %] + [%- END %] -- 2.20.1
[% 'Date' | $T8 %][% 'Duration' | $T8 %] ([% 'in minutes' | $T8 %])[% 'Start' | $T8 %][% 'End' | $T8 %][% 'End' | $T8 %][% 'Customer' | $T8 %] [% 'Article' | $T8 %] [% 'Project' | $T8 %]
+ [% P.date_tag('time_recording.date_as_date', SELF.time_recording.date_as_date, "data-validate"="required", "data-title"=LxERP.t8('Date')) %]
+
+ [% P.input_tag('time_recording.duration', SELF.time_recording.duration, size=15) %] + [% P.date_tag('start_date', SELF.start_date, "data-validate"="required", "data-title"=LxERP.t8('Start date'), onchange='kivi.TimeRecording.set_end_date()') %]
[% P.input_tag('start_time', SELF.start_time, type="time", "data-validate"="required", "data-title"=LxERP.t8('Start time')) %] @@ -36,6 +49,7 @@ [% P.input_tag('end_time', SELF.end_time, type="time") %] [% P.button_tag('kivi.TimeRecording.set_current_date_time("end")', LxERP.t8('now')) %]
[% 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')) %] [% 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') %] [% P.project.picker('time_recording.project_id', SELF.time_recording.project_id, style='width: 300px') %]