From 51fec3106e49e1ffc7d6a0a2c577bea8641efbff Mon Sep 17 00:00:00 2001 From: Moritz Bunkus Date: Fri, 19 Apr 2013 11:22:54 +0200 Subject: [PATCH] Pflichtenheftversionen anlegen und auflisten --- SL/Controller/RequirementSpec.pm | 17 +- SL/Controller/RequirementSpecItem.pm | 6 + SL/Controller/RequirementSpecVersion.pm | 183 ++++++++++++++++++ SL/DB/RequirementSpec.pm | 70 ++++++- js/locale/de.js | 1 + js/requirement_spec.js | 29 ++- locale/de/all | 24 ++- .../webpages/requirement_spec/_version.html | 4 +- templates/webpages/requirement_spec/show.html | 1 + .../requirement_spec_version/_form.html | 44 +++++ .../requirement_spec_version/list.html | 36 ++++ .../requirement_spec_version/new.html | 5 + 12 files changed, 396 insertions(+), 24 deletions(-) create mode 100644 SL/Controller/RequirementSpecVersion.pm create mode 100644 templates/webpages/requirement_spec_version/_form.html create mode 100644 templates/webpages/requirement_spec_version/list.html create mode 100644 templates/webpages/requirement_spec_version/new.html diff --git a/SL/Controller/RequirementSpec.pm b/SL/Controller/RequirementSpec.pm index 54518b7c5..68e060c1c 100644 --- a/SL/Controller/RequirementSpec.pm +++ b/SL/Controller/RequirementSpec.pm @@ -4,8 +4,6 @@ use strict; use parent qw(SL::Controller::Base); -use Rose::DB::Object::Helpers; - use SL::ClientJS; use SL::Controller::Helper::GetModels; use SL::Controller::Helper::Paginated; @@ -52,6 +50,8 @@ __PACKAGE__->make_sorted( type => t8('Requirement Spec Type'), status => t8('Requirement Spec Status'), projectnumber => t8('Project Number'), + version => t8('Version'), + mtime => t8('Last modification'), ); # @@ -191,7 +191,7 @@ sub action_reorder { sub setup { my ($self) = @_; - $::auth->assert('config'); + $::auth->assert('sales_quotation_edit'); $::request->{layout}->use_stylesheet("${_}.css") for qw(jquery.contextMenu requirement_spec); $::request->{layout}->use_javascript("${_}.js") for qw(jquery.jstree jquery/jquery.contextMenu client_js requirement_spec); $self->is_template($::form->{is_template} ? 1 : 0); @@ -267,7 +267,7 @@ sub create_or_update { } })) { $::lxdebug->message(LXDebug::WARN(), "Error: " . $db->error); - @errors = ($::locale->text('Saving failed. Erro message from the database: #1'), $db->error); + @errors = ($::locale->text('Saving failed. Error message from the database: #1'), $db->error); return SL::ClientJS->new->error(@errors)->render($self) if $::request->is_ajax; $self->requirement_spec->id(undef) if $is_new; @@ -300,7 +300,8 @@ sub setup_db_args_from_filter { $args{where} = [ and => [ @{ $args{where} || [] }, - is_template => $self->is_template + working_copy_id => undef, + is_template => $self->is_template ]]; $self->db_args(\%args); @@ -314,8 +315,8 @@ sub prepare_report { my $report = SL::ReportGenerator->new(\%::myconfig, $::form); $self->{report} = $report; - my @columns = qw(title customer status type projectnumber); - my @sortable = qw(title customer status type projectnumber); + my @columns = qw(title customer status type projectnumber mtime version); + my @sortable = qw(title customer status type projectnumber mtime); my %column_defs = ( title => { obj_link => sub { $self->url_for(action => 'show', id => $_[0]->id, callback => $callback) } }, @@ -325,6 +326,8 @@ sub prepare_report { sub => sub { $_[0]->project_id ? $_[0]->project->projectnumber : '' } }, status => { sub => sub { $_[0]->status->description } }, type => { sub => sub { $_[0]->type->description } }, + version => { sub => sub { $_[0]->version_id ? $_[0]->version->version_number : t8('Working copy without version') } }, + mtime => { sub => sub { $_[0]->mtime->to_kivitendo(precision => 'minute') } }, ); map { $column_defs{$_}->{text} ||= $::locale->text( $self->get_sort_spec->{$_}->{title} ) } keys %column_defs; diff --git a/SL/Controller/RequirementSpecItem.pm b/SL/Controller/RequirementSpecItem.pm index 7cad81151..0a5463723 100644 --- a/SL/Controller/RequirementSpecItem.pm +++ b/SL/Controller/RequirementSpecItem.pm @@ -25,6 +25,7 @@ use Rose::Object::MakeMethods::Generic 'scalar --get_set_init' => [ qw(complexities risks) ], ); +__PACKAGE__->run_before('check_auth'); __PACKAGE__->run_before('load_requirement_spec_item', only => [ qw(dragged_and_dropped ajax_update ajax_edit ajax_delete ajax_flag ajax_copy) ]); __PACKAGE__->run_before('init_visible_section'); @@ -469,6 +470,11 @@ sub action_ajax_paste { # filters # +sub check_auth { + my ($self) = @_; + $::auth->assert('sales_quotation_edit'); +} + sub load_requirement_spec { my ($self) = @_; $self->requirement_spec(SL::DB::RequirementSpec->new(id => $::form->{requirement_spec_id})->load || die "No such requirement spec"); diff --git a/SL/Controller/RequirementSpecVersion.pm b/SL/Controller/RequirementSpecVersion.pm new file mode 100644 index 000000000..058884709 --- /dev/null +++ b/SL/Controller/RequirementSpecVersion.pm @@ -0,0 +1,183 @@ +package SL::Controller::RequirementSpecVersion; + +use strict; + +use parent qw(SL::Controller::Base); + +use Carp; +use List::MoreUtils qw(any); + +use SL::ClientJS; +use SL::DB::Customer; +use SL::DB::Project; +use SL::DB::RequirementSpec; +use SL::DB::RequirementSpecVersion; +use SL::Helper::Flash; +use SL::Locale::String; + +use Rose::Object::MakeMethods::Generic +( + 'scalar --get_set_init' => [ qw(requirement_spec version js versioned_copies) ], +); + +__PACKAGE__->run_before('check_auth'); + +# +# actions +# + +sub action_list { + my ($self, %params) = @_; + + $self->render('requirement_spec_version/list', { layout => 0 }); +} + +sub action_new { + my ($self) = @_; + + $self->version(SL::DB::RequirementSpecVersion->new); + + my $previous_version = $self->requirement_spec->previous_version; + my %differences = $self->calculate_differences(current => $self->requirement_spec, previous => $previous_version); + + if (!$previous_version) { + $self->version->description(t8('Initial version.')); + + } else { + my @lines; + + my $fb_diff = $differences{function_blocks}; + push @lines, t8('Added sections and function blocks: #1', $::locale->language_join([ map { $_->fb_number } @{ $fb_diff->{additions} } ])) if @{ $fb_diff->{additions} }; + push @lines, t8('Changed sections and function blocks: #1', $::locale->language_join([ map { $_->fb_number } @{ $fb_diff->{changes} } ])) if @{ $fb_diff->{changes} }; + push @lines, t8('Removed sections and function blocks: #1', $::locale->language_join([ map { $_->fb_number } @{ $fb_diff->{removals} } ])) if @{ $fb_diff->{removals} }; + + my $tb_diff = $differences{text_blocks}; + push @lines, t8('Added text blocks: #1', $::locale->language_join([ map { '"' . $_->title . '"' } @{ $tb_diff->{additions} } ])) if @{ $tb_diff->{additions} }; + push @lines, t8('Changed text blocks: #1', $::locale->language_join([ map { '"' . $_->title . '"' } @{ $tb_diff->{changes} } ])) if @{ $tb_diff->{changes} }; + push @lines, t8('Removed text blocks: #1', $::locale->language_join([ map { '"' . $_->title . '"' } @{ $tb_diff->{removals} } ])) if @{ $tb_diff->{removals} }; + + $self->version->description(@lines ? join("\n", @lines) : t8('No changes since previous version.')); + } + + $self->render('requirement_spec_version/new', { layout => 0 }, title => t8('Create a new version')); +} + +sub action_create { + my ($self, %params) = @_; + + my %attributes = %{ delete($::form->{rs_version}) || {} }; + my @errors = SL::DB::RequirementSpecVersion->new(%attributes, version_number => 1)->validate; + + return $self->js->error(@errors)->render($self) if @errors; + + my $db = $self->requirement_spec->db; + my @result = $self->version($self->requirement_spec->create_version(%attributes)); + + if (!@result) { + $::lxdebug->message(LXDebug::WARN(), "Error: " . $db->error); + return $self->js->error($::locale->text('Saving failed. Error message from the database: #1'), $db->error)->render($self); + } + + my $html = $self->render('requirement_spec/_version', { output => 0 }, requirement_spec => $self->requirement_spec); + + $self->js + ->html('#requirement_spec_version', $html) + ->jqmClose('.jqmWindow') + ->render($self); +} + +# +# filters +# + +sub check_auth { + my ($self, %params) = @_; + $::auth->assert('sales_quotation_edit'); +} + +# +# helpers +# + +sub init_requirement_spec { + my ($self) = @_; + $self->requirement_spec(SL::DB::RequirementSpec->new(id => $::form->{requirement_spec_id})->load) if $::form->{requirement_spec_id}; +} + +sub init_version { + my ($self) = @_; + $self->version(SL::DB::RequirementSpecVersion->new(id => $::form->{id})->load) if $::form->{id}; +} + +sub init_js { + my ($self, %params) = @_; + $self->js(SL::ClientJS->new); +} + +sub init_versioned_copies { + my ($self) = @_; + $self->versioned_copies([ + sort { $b->mtime <=> $a->mtime } @{ $self->requirement_spec->versioned_copies } + ]); +} + +sub has_item_changed { + my ($previous, $current) = @_; + croak "Missing previous/current" if !$previous || !$current; + return any { ($previous->$_ || '') ne ($current->$_ || '') } qw(item_type parent_id fb_number title description complexity_id risk_id time_estimation net_sum); +} + +sub has_text_block_changed { + my ($previous, $current) = @_; + croak "Missing previous/current" if !$previous || !$current; + return any { ($previous->$_ || '') ne ($current->$_ || '') } qw(title text); +} + +sub compare_items { + return -1 if ($a->item_type eq 'section') && ($b->item_type ne 'section'); + return +1 if ($a->item_type ne 'section') && ($b->item_type eq 'section'); + return $a->fb_number cmp $b->fb_number; +} + +sub calculate_differences { + my ($self, %params) = @_; + + my %differences = ( + function_blocks => { + additions => [], + changes => [], + removals => [], + }, + text_blocks => { + additions => [], + changes => [], + removals => [], + }, + ); + + return %differences if !$params{previous} || !$params{current}; + + my @previous_items = sort compare_items @{ $params{previous}->items }; + my @current_items = sort compare_items @{ $params{current}->items }; + + my @previous_text_blocks = sort { lc $a->title cmp lc $b->title } @{ $params{previous}->text_blocks }; + my @current_text_blocks = sort { lc $a->title cmp lc $b->title } @{ $params{current}->text_blocks }; + + my %previous_items_map = map { $_->fb_number => $_ } @previous_items; + my %current_items_map = map { $_->fb_number => $_ } @current_items; + + my %previous_text_blocks_map = map { $_->title => $_ } @previous_text_blocks; + my %current_text_blocks_map = map { $_->title => $_ } @current_text_blocks; + + $differences{function_blocks}->{additions} = [ grep { !$previous_items_map{ $_->fb_number } } @current_items ]; + $differences{function_blocks}->{removals} = [ grep { !$current_items_map{ $_->fb_number } } @previous_items ]; + $differences{function_blocks}->{changes} = [ grep { $previous_items_map{ $_->fb_number } && has_item_changed($previous_items_map{ $_->fb_number }, $_) } @current_items ]; + + $differences{text_blocks}->{additions} = [ grep { !$previous_text_blocks_map{ $_->title } } @current_text_blocks ]; + $differences{text_blocks}->{removals} = [ grep { !$current_text_blocks_map{ $_->title } } @previous_text_blocks ]; + $differences{text_blocks}->{changes} = [ grep { $previous_text_blocks_map{ $_->title } && has_text_block_changed($previous_text_blocks_map{ $_->title }, $_) } @current_text_blocks ]; + + return %differences; +} + +1; diff --git a/SL/DB/RequirementSpec.pm b/SL/DB/RequirementSpec.pm index f21e5137e..e2ac8b10f 100644 --- a/SL/DB/RequirementSpec.pm +++ b/SL/DB/RequirementSpec.pm @@ -10,15 +10,20 @@ use SL::DB::Manager::RequirementSpec; use SL::Locale::String; __PACKAGE__->meta->add_relationship( - items => { - type => 'one to many', - class => 'SL::DB::RequirementSpecItem', - column_map => { id => 'requirement_spec_id' }, + items => { + type => 'one to many', + class => 'SL::DB::RequirementSpecItem', + column_map => { id => 'requirement_spec_id' }, }, - text_blocks => { - type => 'one to many', - class => 'SL::DB::RequirementSpecTextBlock', - column_map => { id => 'requirement_spec_id' }, + text_blocks => { + type => 'one to many', + class => 'SL::DB::RequirementSpecTextBlock', + column_map => { id => 'requirement_spec_id' }, + }, + versioned_copies => { + type => 'one to many', + class => 'SL::DB::RequirementSpec', + column_map => { id => 'working_copy_id' }, }, ); @@ -119,4 +124,53 @@ sub _create_copy { return $copy; } +sub previous_version { + my ($self) = @_; + + my $and = $self->version_id ? " AND (version_id <> ?)" : ""; + my $id = $self->db->dbh->selectrow_array(<id, ($self->version_id) x !!$self->version_id); + SELECT MAX(id) + FROM requirement_specs + WHERE (working_copy_id = ?) $and +SQL + + return $id ? SL::DB::RequirementSpec->new(id => $id)->load : undef; +} + +sub is_working_copy { + my ($self) = @_; + + return !$self->working_copy_id; +} + +sub next_version_number { + my ($self) = @_; + my $max_number = $self->db->dbh->selectrow_array(<id); + SELECT COALESCE(MAX(ver.version_number), 0) + FROM requirement_spec_versions ver + JOIN requirement_specs rs ON (rs.version_id = ver.id) + WHERE rs.working_copy_id = ? +SQL + + return $max_number + 1; +} + +sub create_version { + my ($self, %attributes) = @_; + + my ($copy, $version); + my $ok = $self->db->do_transaction(sub { + delete $attributes{version_number}; + + $version = SL::DB::RequirementSpecVersion->new(%attributes, version_number => $self->next_version_number)->save; + $copy = $self->create_copy; + $copy->update_attributes(version_id => $version->id, working_copy_id => $self->id); + $self->update_attributes(version_id => $version->id); + + 1; + }); + + return $ok ? ($copy, $version) : (); +} + 1; diff --git a/js/locale/de.js b/js/locale/de.js index f7b85de69..704abd729 100644 --- a/js/locale/de.js +++ b/js/locale/de.js @@ -7,6 +7,7 @@ namespace("kivi").setupLocale({ "Are you sure?":"Sind Sie sicher?", "Copy":"Kopieren", "Copy requirement spec":"Pflichtenheft kopieren", +"Create new version":"Neue Version anlegen", "Database Connection Test":"Test der Datenbankverbindung", "Delete":"Löschen", "Delete requirement spec":"Pflichtenheft löschen", diff --git a/js/requirement_spec.js b/js/requirement_spec.js index b15700879..4ae545e4b 100644 --- a/js/requirement_spec.js +++ b/js/requirement_spec.js @@ -313,20 +313,34 @@ function delete_reqspec(key, opt) { return true; } +function disable_requirement_spec_commands(key, opt) { + if (key === "create_version") + return ($('#current_version_id').val() || '') == '' ? false : true; + return false; +} + +// ------------------------------------------------------------------------- +// -------------------------------- versions ------------------------------- +// ------------------------------------------------------------------------- + +function create_requirement_spec_version() { + open_jqm_window({ url: 'controller.pl', + data: { action: 'RequirementSpecVersion/new', + requirement_spec_id: $('#requirement_spec_id').val() }, + id: 'new_requirement_spec_version_window' }); + return true; +} + // ------------------------------------------------------------------------- // ----------------------------- context menus ----------------------------- // ------------------------------------------------------------------------- function create_requirement_spec_context_menus() { - var events = { - show: requirement_spec_text_block_popup_menu_shown, - hide: requirement_spec_text_block_popup_menu_hidden - }; - var general_actions = { sep98: "---------" , general_actions: { name: kivi.t8('Requirement spec actions:') } , sep99: "---------" + , create_version: { name: kivi.t8('Create new version'), icon: "new", callback: create_requirement_spec_version, disabled: disable_requirement_spec_commands } , copy_reqspec: { name: kivi.t8('Copy requirement spec'), icon: "copy", callback: copy_reqspec } , delete_reqspec: { name: kivi.t8('Delete requirement spec'), icon: "delete", callback: delete_reqspec } }; @@ -336,6 +350,11 @@ function create_requirement_spec_context_menus() { items: general_actions }); + var events = { + show: requirement_spec_text_block_popup_menu_shown, + hide: requirement_spec_text_block_popup_menu_hidden + }; + $.contextMenu({ selector: '.text-block-context-menu', events: { diff --git a/locale/de/all b/locale/de/all index 0a8020493..61c3d8e41 100755 --- a/locale/de/all +++ b/locale/de/all @@ -179,6 +179,8 @@ $self->{texts} = { 'Add sub function block' => 'Unterfunktionsblock hinzufügen', 'Add text block' => 'Textblock erfassen', 'Add unit' => 'Einheit hinzufügen', + 'Added sections and function blocks: #1' => 'Hinzugefügte Abschnitte und Funktionsblöcke: #1', + 'Added text blocks: #1' => 'Hinzugefügte Textblöcke: #1', 'Address' => 'Adresse', 'Admin' => 'Administration', 'Administration' => 'Administration', @@ -436,6 +438,9 @@ $self->{texts} = { 'Cc E-mail' => 'CC (E-Mail)', 'Change default bin for this parts' => 'Standardlagerplatz für diese Waren ändern', 'Change kivitendo installation settings (most entries in the \'System\' menu)' => 'Verändern der kivitendo-Installationseinstellungen (die meisten Menüpunkte unterhalb von \'System\')', + 'Change representative to' => 'Vertreter ändern in', + 'Changed sections and function blocks: #1' => 'Geänderte Abschnitte und Funktionsblöcke: #1', + 'Changed text blocks: #1' => 'Geänderte Textblöcke: #1', 'Changes in this block are only sensible if the account is NOT a summary account AND there exists one valid taxkey. To select both Receivables and Payables only make sense for Payment / Receipt (i.e. account cash).' => 'Es ist nur sinnvoll Änderungen vorzunehmen, wenn das Konto KEIN Sammelkonto ist und wenn ein gültiger Steuerschlüssel für das Konto existiert. Gleichzeitig Haken bei Forderungen und Verbindlichkeiten zu setzen, macht auch NUR für den Zahlungsein- und Ausgang (bspw. Bank oder Kasse) Sinn.', 'Changes to Receivables and Payables are only possible if no transactions to this account are posted yet.' => 'Änderungen bei Forderungen oder Verbindlichkeiten sind nur möglich, wenn dieses Konto noch nicht bebucht wurde.', 'Charge Number' => 'Chargennummer', @@ -549,6 +554,8 @@ $self->{texts} = { 'Create a new section' => 'Neuen Abschnitt erstellen', 'Create a new user' => 'Einen neuen Benutzer anlegen', 'Create a new user group' => 'Eine neue Benutzergruppe erfassen', + 'Create a new version' => 'Eine neue Version anlegen', + 'Create a standard group' => 'Eine Standard-Benutzergruppe anlegen', 'Create and edit RFQs' => 'Lieferantenanfragen erfassen und bearbeiten', 'Create and edit dunnings' => 'Mahnungen erfassen und bearbeiten', 'Create and edit invoices and credit notes' => 'Rechnungen und Gutschriften erfassen und bearbeiten', @@ -576,6 +583,7 @@ $self->{texts} = { 'Create new payment term' => 'Neue Zahlungsbedingung anlegen', 'Create new project type' => 'Neuen Projekttypen anlegen', 'Create new templates from master templates' => 'Neue Druckvorlagen aus Vorlagensatz erstellen', + 'Create new version' => 'Neue Version anlegen', 'Create one from the context menu by right-clicking on this text.' => 'Erstellen Sie einen aus dem Kontextmenü, indem Sie auf diesen Text rechtsklicken.', 'Create tables' => 'Tabellen anlegen', 'Created by' => 'Erstellt von', @@ -1183,6 +1191,7 @@ $self->{texts} = { 'Increase' => 'Erhöhen', 'Individual Items' => 'Einzelteile', 'Information' => 'Information', + 'Initial version.' => 'Initiale Version.', 'Insert' => 'Einfügen', 'Insert with new customer/vendor number' => 'Mit neuer Kunden-/Lieferantennummer anlegen', 'Insert with new database ID' => 'Neu anlegen mit neuer Datenbank-ID', @@ -1190,6 +1199,8 @@ $self->{texts} = { 'Interest' => 'Zinsen', 'Interest Rate' => 'Zinssatz', 'Internal Notes' => 'Interne Bemerkungen', + 'Internal comment' => 'Interne Bemerkungen', + 'International' => 'Ausland', 'Internet' => 'Internet', 'Introduction of clients' => 'Einführung von Mandanten', 'Inv. Duedate' => 'Rg. Fälligkeit', @@ -1282,6 +1293,7 @@ $self->{texts} = { 'Last Transaction' => 'Letzte Buchung', 'Last Vendor Number' => 'Letzte Lieferantennummer', 'Last command output' => 'Ausgabe des letzten Befehls', + 'Last modification' => 'Letzte Änderung', 'Last opening balance or all transactions' => 'Letzte Eröffnungsbuchung oder alle Buchungen', 'Last opening balance or start of year' => 'Letzte Eröffnungsbuchung oder Jahresanfang', 'Last run at' => 'Letzte Ausführung um', @@ -1439,6 +1451,7 @@ $self->{texts} = { 'No bins have been added to this warehouse yet.' => 'Es wurden zu diesem Lager noch keine Lagerplätze angelegt.', 'No business has been created yet.' => 'Es wurden noch kein Kunden-/Lieferantentyp erfasst.', 'No clients have been created yet.' => 'Es wurden noch keine Mandanten angelegt.', + 'No changes since previous version.' => 'Keine Änderungen seit der letzten Version.', 'No complexities has been created yet.' => 'Es wurden noch keine Komplexitätsgrade angelegt.', 'No contact selected to delete' => 'Keine Ansprechperson zum Löschen ausgewählt', 'No customer has been selected yet.' => 'Es wurde noch kein Kunde ausgewählt.', @@ -1821,7 +1834,9 @@ $self->{texts} = { 'Remove' => 'Entfernen', 'Remove Draft' => 'Entwurf löschen', 'Remove draft when posting' => 'Entwurf beim Buchen löschen', + 'Removed sections and function blocks: #1' => 'Entfernte Abschnitte und Funktionsblöcke: #1', 'Removed spoolfiles!' => 'Druckdateien entfernt!', + 'Removed text blocks: #1' => 'Entfernte Textblöcke: #1', 'Removing marked entries from queue ...' => 'Markierte Einträge werden von der Warteschlange entfernt ...', 'Replace the orphaned currencies by other not orphaned currencies. To do so, please delete the currency in the textfields above and replace it by another currency. You could loose or change unintentionally exchangerates. Go on very carefully since you could destroy transactions.' => 'Ersetze die Währungen durch andere gültige Währungen. Wenn Sie sich hierfür entscheiden, ersetzen Sie bitte alle Währungen, die oben angegeben sind, durch Währungen, die in Ihrem System ordnungsgemäß eingetragen sind. Alle eingetragenen Wechselkurse für die verwaiste Währung werden dabei gelöscht. Bitte gehen Sie sehr vorsichtig vor, denn die betroffenen Buchungen können unter Umständen kaputt gehen.', 'Report Positions' => 'Berichte', @@ -1928,7 +1943,7 @@ $self->{texts} = { 'Save draft' => 'Entwurf speichern', 'Save profile' => 'Profil speichern', 'Save settings as' => 'Einstellungen speichern unter', - 'Saving failed. Erro message from the database: #1' => 'Speichern schlug fehl. Fehlermeldung der Datenbank: #1', + 'Saving failed. Error message from the database: #1' => 'Speichern schlug fehl. Fehlermeldung der Datenbank: #1', 'Saving the file \'%s\' failed. OS error message: %s' => 'Das Speichern der Datei \'%s\' schlug fehl. Fehlermeldung des Betriebssystems: %s', 'Screen' => 'Bildschirm', 'Search' => 'Suchen', @@ -2610,12 +2625,15 @@ $self->{texts} = { 'Vendor missing!' => 'Lieferant fehlt!', 'Vendor not on file or locked!' => 'Dieser Lieferant existiert nicht oder ist gesperrt.', 'Vendor not on file!' => 'Lieferant ist nicht in der Datenbank!', - 'Vendor saved' => 'Lieferant gespeichert', 'Vendor saved!' => 'Lieferant gespeichert!', + 'Vendor saved' => 'Lieferant gespeichert', 'Vendor type' => 'Lieferantentyp', 'Vendors' => 'Lieferanten', 'Verrechnungseinheit' => 'Verrechnungseinheit', + 'Version number' => 'Versionsnummer', 'Version' => 'Version', + 'Versions' => 'Versionen', + 'Vertreter' => 'Vertreter', 'View SEPA export' => 'SEPA-Export-Details ansehen', 'View background job execution result' => 'Verlauf der Hintergrund-Job-Ausführungen anzeigen', 'View background job history' => 'Hintergrund-Job-Verlauf anzeigen', @@ -2652,7 +2670,9 @@ $self->{texts} = { 'Workflow request_quotation' => 'Workflow Preisanfrage', 'Workflow sales_order' => 'Workflow Auftrag', 'Workflow sales_quotation' => 'Workflow Angebot', + 'Working copy identical to version number #1' => 'Mit Versionsnummer #1 identische Arbeitskopie', 'Working copy without version' => 'Arbeitskopie ohne Version', + 'Working copy; no description yet' => 'Arbeitskopie; noch keine Beschreibung', 'Write bin to default bin in part?' => 'Diesen Lagerplatz als Standardlagerplatz im Artikel setzen?', 'Wrong tax keys recorded' => 'Gespeicherte Steuerschlüssel sind falsch', 'Wrong taxes recorded' => 'Gespeicherte Steuern passen nicht zum Steuerschlüssel', diff --git a/templates/webpages/requirement_spec/_version.html b/templates/webpages/requirement_spec/_version.html index 8a73f54ad..f6d9ca35d 100644 --- a/templates/webpages/requirement_spec/_version.html +++ b/templates/webpages/requirement_spec/_version.html @@ -1,9 +1,9 @@ [%- USE L -%][%- USE LxERP -%][%- USE HTML -%] -[% L.stuff %] +[% L.hidden_tag('current_version_id', requirement_spec.version_id) %] [% LxERP.t8("Current version") %]: [% IF !requirement_spec.version_id %] [% LxERP.t8("Working copy without version") %] [% ELSE %] [% LxERP.t8("Version") %] [% HTML.escape(requirement_spec.version.version_number) %] - [% LxERP.t8("dated") %] [% HTML.escape(requirement_spec.version.itime.displayable_date) %] + [% LxERP.t8("dated") %] [% HTML.escape(requirement_spec.version.itime.to_kivitendo(precision='minute')) %] [%- END -%] diff --git a/templates/webpages/requirement_spec/show.html b/templates/webpages/requirement_spec/show.html index 4cae971e5..395e394e5 100644 --- a/templates/webpages/requirement_spec/show.html +++ b/templates/webpages/requirement_spec/show.html @@ -11,6 +11,7 @@
  • [%- LxERP.t8("Content") %]
  • [%- LxERP.t8("Basic settings") %]
  • [%- LxERP.t8("Time and cost estimate") %]
  • +
  • [%- LxERP.t8("Versions") %]
  • diff --git a/templates/webpages/requirement_spec_version/_form.html b/templates/webpages/requirement_spec_version/_form.html new file mode 100644 index 000000000..a100c3383 --- /dev/null +++ b/templates/webpages/requirement_spec_version/_form.html @@ -0,0 +1,44 @@ +[%- USE LxERP -%][%- USE L -%] +[%- DEFAULT id_prefix = 'edit_version_form' + submit_as = 'post' +%] +
    + [% L.hidden_tag("requirement_spec_id", SELF.requirement_spec.id, id=id_prefix _ '_id') %] + [% L.hidden_tag("id", SELF.version.id, id=id_prefix _ '_id') %] + + + + + + + + + + + + + + + + + +
    [% LxERP.t8("Version") %][% SELF.requirement_spec.next_version_number %]
    [% LxERP.t8("Description") %][% L.textarea_tag("rs_version.description", SELF.version.description, cols=80, rows=8, id=id_prefix _ '_description') %]
    [% LxERP.t8("Internal comment") %][% L.textarea_tag("rs_version.comment", SELF.version.comment, cols=80, rows=8, id=id_prefix _ '_comment') %]
    + +

    +[%- IF SELF.version.id %] + [% L.ajax_submit_tag("controller.pl?action=RequirementSpecVersion/update", "#" _ id_prefix, LxERP.t8("Save")) %] + [%- LxERP.t8("Cancel") %] +[%- ELSE %] + [% L.ajax_submit_tag("controller.pl?action=RequirementSpecVersion/create", "#" _ id_prefix, LxERP.t8("Save")) %] + [% LxERP.t8("Cancel") %] +[%- END %] +

    +
    + + diff --git a/templates/webpages/requirement_spec_version/list.html b/templates/webpages/requirement_spec_version/list.html new file mode 100644 index 000000000..7c4f4d5ec --- /dev/null +++ b/templates/webpages/requirement_spec_version/list.html @@ -0,0 +1,36 @@ +[% USE HTML %][% USE L %][% USE LxERP %][%- USE P -%] + + + + + + + + + + + + + + + + + + + + [%- FOREACH versioned = SELF.versioned_copies %] + + + + + + + [%- END %] + +
    [%- LxERP.t8("Version number") %][%- LxERP.t8("Description") %][%- LxERP.t8("Internal comment") %][%- LxERP.t8("Last modification") %]
    + [%- IF SELF.requirement_spec.version_id %] + [%- LxERP.t8("Working copy identical to version number #1", SELF.requirement_spec.version.version_number) %] + [%- ELSE %] + [%- LxERP.t8("Working copy without version") %] + [%- END -%] + [%- LxERP.t8("Working copy; no description yet") %] [% SELF.requirement_spec.mtime.to_kivitendo(precision='minute') %]
    [% HTML.escape(versioned.version.version_number) %][% HTML.escape(P.truncate(versioned.description)) %][% HTML.escape(P.truncate(versioned.comment)) %][% versioned.mtime.to_kivitendo(precision='minute') %]
    diff --git a/templates/webpages/requirement_spec_version/new.html b/templates/webpages/requirement_spec_version/new.html new file mode 100644 index 000000000..ebcca876c --- /dev/null +++ b/templates/webpages/requirement_spec_version/new.html @@ -0,0 +1,5 @@ +

    [% title %]

    + +[%- INCLUDE 'common/flash.html' %] + +[%- INCLUDE 'requirement_spec_version/_form.html' %] -- 2.20.1