From 405a41ef0836ac972a3ebb9e99f05de7d13d4275 Mon Sep 17 00:00:00 2001 From: Moritz Bunkus Date: Wed, 17 Apr 2013 17:29:35 +0200 Subject: [PATCH] =?utf8?q?Pflichtenhefte:=20Kopieren=20(clonen)=20generell?= =?utf8?q?=20und=20L=C3=B6schen=20aus=20Kontextmen=C3=BC=20implementiert?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- SL/Controller/RequirementSpec.pm | 32 ++++++++- SL/DB/RequirementSpec.pm | 56 ++++++++++++++++ js/locale/de.js | 5 +- js/requirement_spec.js | 65 ++++++++++++++----- locale/de/all | 5 ++ .../webpages/requirement_spec/_form.html | 8 +++ 6 files changed, 151 insertions(+), 20 deletions(-) diff --git a/SL/Controller/RequirementSpec.pm b/SL/Controller/RequirementSpec.pm index 48af731e5..54518b7c5 100644 --- a/SL/Controller/RequirementSpec.pm +++ b/SL/Controller/RequirementSpec.pm @@ -4,6 +4,8 @@ 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; @@ -24,12 +26,13 @@ use SL::Locale::String; use Rose::Object::MakeMethods::Generic ( scalar => [ qw(requirement_spec_item customers types statuses db_args flat_filter is_template visible_item visible_section) ], - 'scalar --get_set_init' => [ qw(requirement_spec complexities risks projects) ], + 'scalar --get_set_init' => [ qw(requirement_spec complexities risks projects copy_source) ], ); __PACKAGE__->run_before('setup'); __PACKAGE__->run_before('load_select_options', only => [ qw(new ajax_edit create update list) ]); + __PACKAGE__->get_models_url_params('flat_filter'); __PACKAGE__->make_paginated( MODEL => 'RequirementSpec', @@ -73,6 +76,11 @@ sub action_new { my ($self) = @_; $self->requirement_spec(SL::DB::RequirementSpec->new); + + if ($self->copy_source) { + $self->requirement_spec->$_($self->copy_source->$_) for qw(type_id status_id customer_id title hourly_rate) + } + $self->render('requirement_spec/new', title => t8('Create a new requirement spec')); } @@ -212,6 +220,11 @@ sub init_requirement_spec { $self->requirement_spec(SL::DB::RequirementSpec->new(id => $::form->{id})->load || die "No such requirement spec") if $::form->{id}; } +sub init_copy_source { + my ($self) = @_; + $self->copy_source(SL::DB::RequirementSpec->new(id => $::form->{copy_source_id})->load) if $::form->{copy_source_id}; +} + sub load_select_options { my ($self) = @_; @@ -245,7 +258,22 @@ sub create_or_update { return; } - $self->requirement_spec->save; + my $db = $self->requirement_spec->db; + if (!$db->do_transaction(sub { + if ($self->copy_source) { + $self->requirement_spec($self->copy_source->create_copy(%{ $params })); + } else { + $self->requirement_spec->save; + } + })) { + $::lxdebug->message(LXDebug::WARN(), "Error: " . $db->error); + @errors = ($::locale->text('Saving failed. Erro 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; + flash('error', @errors); + return $self->render('requirement_spec/new', title => $title); + } if ($::request->is_ajax) { my $html = $self->render('requirement_spec/_header', { output => 0 }); diff --git a/SL/DB/RequirementSpec.pm b/SL/DB/RequirementSpec.pm index 3a3f4ccf4..f21e5137e 100644 --- a/SL/DB/RequirementSpec.pm +++ b/SL/DB/RequirementSpec.pm @@ -3,6 +3,7 @@ package SL::DB::RequirementSpec; use strict; use Carp; +use Rose::DB::Object::Helpers; use SL::DB::MetaSetup::RequirementSpec; use SL::DB::Manager::RequirementSpec; @@ -63,4 +64,59 @@ sub displayable_name { return sprintf('%s: "%s"', $self->type->description, $self->title); } +sub create_copy { + my ($self, %params) = @_; + + return $self->_create_copy(%params) if $self->db->in_transaction; + + my $copy; + if (!$self->db->do_transaction(sub { $copy = $self->_create_copy(%params) })) { + $::lxdebug->message(LXDebug->WARN(), "create_copy failed: " . join("\n", (split(/\n/, $self->db->error))[0..2])); + return undef; + } + + return $copy; +} + +sub _create_copy { + my ($self, %params) = @_; + + my $copy = Rose::DB::Object::Helpers::clone_and_reset($self); + $copy->assign_attributes(%params); + + # Clone text blocks. + $copy->text_blocks(map { Rose::DB::Object::Helpers::clone_and_reset($_) } @{ $self->text_blocks }); + + # Save new object -- we need its ID for the items. + $copy->save; + + my %id_to_clone; + + # Clone items. + my $clone_item; + $clone_item = sub { + my ($item) = @_; + my $cloned = Rose::DB::Object::Helpers::clone_and_reset($item); + $cloned->requirement_spec_id($copy->id); + $cloned->children(map { $clone_item->($_) } @{ $item->children }); + + $id_to_clone{ $item->id } = $cloned; + + return $cloned; + }; + + $copy->items(map { $clone_item->($_) } @{ $self->sections }); + + # Save the items -- need to do that before setting dependencies. + $copy->save; + + # Set dependencies. + foreach my $item (@{ $self->items }) { + next unless @{ $item->dependencies }; + $id_to_clone{ $item->id }->update_attributes(dependencies => [ map { $id_to_clone{$_->id} } @{ $item->dependencies } ]); + } + + return $copy; +} + 1; diff --git a/js/locale/de.js b/js/locale/de.js index 5da04ec00..f7b85de69 100644 --- a/js/locale/de.js +++ b/js/locale/de.js @@ -6,9 +6,11 @@ namespace("kivi").setupLocale({ "Add text block":"Textblock erfassen", "Are you sure?":"Sind Sie sicher?", "Copy":"Kopieren", +"Copy requirement spec":"Pflichtenheft kopieren", "Database Connection Test":"Test der Datenbankverbindung", -"Delete text block":"Textblock löschen", "Delete":"Löschen", +"Delete requirement spec":"Pflichtenheft löschen", +"Delete text block":"Textblock löschen", "Do you really want to cancel?":"Wollen Sie wirklich abbrechen?", "Do you want to set the account number \"#1\" to \"#2\" and the name \"#3\" to \"#4\"?":"Soll die Kontonummer \"#1\" zu \"#2\" und den Name \"#3\" zu \"#4\" geändert werden?", "Edit text block":"Textblock bearbeiten", @@ -17,6 +19,7 @@ namespace("kivi").setupLocale({ "Map":"Karte", "Part picker":"Artikelauswahl", "Paste":"Einfügen", +"Requirement spec actions:":"Pflichtenheftaktionen:", "Save":"Speichern", "The description is missing.":"Die Beschreibung fehlt.", "The name is missing.":"Der Name fehlt.", diff --git a/js/requirement_spec.js b/js/requirement_spec.js index a259682a1..31807f097 100644 --- a/js/requirement_spec.js +++ b/js/requirement_spec.js @@ -290,6 +290,29 @@ function standard_time_cost_estimate_ajax_call(key, opt) { return true; } +// ------------------------------------------------------------------------- +// ---------------------------- general actions ---------------------------- +// ------------------------------------------------------------------------- + +function download_reqspec_pdf(key, opt) { + var data = { + action: "RequirementSpec/download_pdf", + id: $('#requirement_spec_id').val() + }; + $.download("controller.pl", data); +} + +function copy_reqspec(key, opt) { + window.location.href = "controller.pl?action=RequirementSpec/new©_source_id=" + encodeURIComponent($('#requirement_spec_id').val()); + return true; +} + +function delete_reqspec(key, opt) { + if (confirm(kivi.t8("Are you sure?"))) + window.location.href = "controller.pl?action=RequirementSpec/destroy&id=" + encodeURIComponent($('#requirement_spec_id').val()); + return true; +} + // ------------------------------------------------------------------------- // ----------------------------- context menus ----------------------------- // ------------------------------------------------------------------------- @@ -300,22 +323,30 @@ function create_requirement_spec_context_menus() { hide: requirement_spec_text_block_popup_menu_hidden }; + var general_actions = { + sep98: "---------" + , general_actions: { name: kivi.t8('Requirement spec actions:') } + , sep99: "---------" + , 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 } + }; + $.contextMenu({ selector: '.text-block-context-menu', events: { show: requirement_spec_text_block_popup_menu_shown , hide: requirement_spec_text_block_popup_menu_hidden }, - items: { - add: { name: kivi.t8('Add text block'), icon: "add", callback: standard_text_block_ajax_call } - , edit: { name: kivi.t8('Edit text block'), icon: "edit", callback: standard_text_block_ajax_call, disabled: disable_edit_text_block_commands } - , delete: { name: kivi.t8('Delete text block'), icon: "delete", callback: ask_delete_text_block, disabled: disable_edit_text_block_commands } - , sep1: "---------" - , flag: { name: kivi.t8('Toggle marker'), icon: "flag", callback: standard_text_block_ajax_call, disabled: disable_edit_text_block_commands } - , sep2: "---------" - , copy: { name: kivi.t8('Copy'), icon: "copy", callback: standard_text_block_ajax_call, disabled: disable_edit_text_block_commands } - , paste: { name: kivi.t8('Paste'), icon: "paste", callback: standard_text_block_ajax_call } - } + items: $.extend({ + add: { name: kivi.t8('Add text block'), icon: "add", callback: standard_text_block_ajax_call } + , edit: { name: kivi.t8('Edit text block'), icon: "edit", callback: standard_text_block_ajax_call, disabled: disable_edit_text_block_commands } + , delete: { name: kivi.t8('Delete text block'), icon: "delete", callback: ask_delete_text_block, disabled: disable_edit_text_block_commands } + , sep1: "---------" + , flag: { name: kivi.t8('Toggle marker'), icon: "flag", callback: standard_text_block_ajax_call, disabled: disable_edit_text_block_commands } + , sep2: "---------" + , copy: { name: kivi.t8('Copy'), icon: "copy", callback: standard_text_block_ajax_call, disabled: disable_edit_text_block_commands } + , paste: { name: kivi.t8('Paste'), icon: "paste", callback: standard_text_block_ajax_call } + }, general_actions) }); var events = { @@ -326,7 +357,7 @@ function create_requirement_spec_context_menus() { $.contextMenu({ selector: '.section-context-menu', events: events, - items: { + items: $.extend({ add_section: { name: kivi.t8('Add section'), icon: "add", callback: standard_item_ajax_call } , add_function_block: { name: kivi.t8('Add function block'), icon: "add", callback: standard_item_ajax_call, disabled: disable_add_function_block_command } , sep1: "---------" @@ -337,13 +368,13 @@ function create_requirement_spec_context_menus() { , sep3: "---------" , copy: { name: kivi.t8('Copy'), icon: "copy", callback: standard_item_ajax_call, disabled: disable_edit_item_commands } , paste: { name: kivi.t8('Paste'), icon: "paste", callback: standard_item_ajax_call } - } + }, general_actions) }); $.contextMenu({ selector: '.function-block-context-menu,.sub-function-block-context-menu', events: events, - items: { + items: $.extend({ add_function_block: { name: kivi.t8('Add function block'), icon: "add", callback: standard_item_ajax_call } , add_sub_function_block: { name: kivi.t8('Add sub function block'), icon: "add", callback: standard_item_ajax_call } , sep1: "---------" @@ -354,21 +385,21 @@ function create_requirement_spec_context_menus() { , sep3: "---------" , copy: { name: kivi.t8('Copy'), icon: "copy", callback: standard_item_ajax_call, disabled: disable_edit_item_commands } , paste: { name: kivi.t8('Paste'), icon: "paste", callback: standard_item_ajax_call } - } + }, general_actions) }); $.contextMenu({ selector: '.time-cost-estimate-context-menu', events: events, - items: { edit: { name: kivi.t8('Edit'), icon: "edit", callback: standard_time_cost_estimate_ajax_call } } + items: $.extend({ edit: { name: kivi.t8('Edit'), icon: "edit", callback: standard_time_cost_estimate_ajax_call } }, general_actions) }); $.contextMenu({ selector: '.edit-time-cost-estimate-context-menu', events: events, - items: { + items: $.extend({ save: { name: kivi.t8('Save'), icon: "save", callback: standard_time_cost_estimate_ajax_call } , cancel: { name: kivi.t8('Cancel'), icon: "close", callback: standard_time_cost_estimate_ajax_call } - } + }, general_actions) }); } diff --git a/locale/de/all b/locale/de/all index da52e721b..0a8020493 100755 --- a/locale/de/all +++ b/locale/de/all @@ -513,6 +513,7 @@ $self->{texts} = { 'Copies' => 'Kopien', 'Copy file from #1 to #2 failed: #3' => 'Kopieren der Datei von #1 nach #2 schlug fehl: #3', 'Copy' => 'Kopieren', + 'Copy requirement spec' => 'Pflichtenheft kopieren', 'Correct taxkey' => 'Richtiger Steuerschlüssel', 'Cost' => 'Kosten', 'Costs' => 'Kosten', @@ -714,6 +715,7 @@ $self->{texts} = { 'Delete drafts' => 'Entwürfe löschen', 'Delete links' => 'Verknüpfungen löschen', 'Delete profile' => 'Profil löschen', + 'Delete requirement spec' => 'Pflichtenheft löschen', 'Delete text block' => 'Textblock löschen', 'Delete transaction' => 'Buchung löschen', 'Deleted' => 'Gelöscht', @@ -1847,6 +1849,7 @@ $self->{texts} = { 'Requirement Spec Type' => 'Pflichtenhefttyp', 'Requirement Spec Types' => 'Pflichtenhefttypen', 'Requirement Specs' => 'Pflichtenhefte', + 'Requirement spec actions:' => 'Pflichtenheftaktionen:', 'Requirement spec function block #1 with #2 sub function blocks; description: "#3"' => 'Pflichtenheft-Funktionsblock #1 mit #2 Unterfunktionsblöcken; Beschreibung: "#3"', 'Requirement spec section #1 "#2" with #3 function blocks and a total of #4 sub function blocks; preamble: "#5"' => 'Pflichtenheftabschnitt #1 "#2" mit #3 Funktionsblöcken und insgesamt #4 Unterfunktionsblöcken; Einleitung: "#5"', 'Requirement spec sub function block #1; description: "#2"' => 'Pflichtenheft-Unterfunktionsblock #1; Beschreibung: "#2"', @@ -1925,6 +1928,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 the file \'%s\' failed. OS error message: %s' => 'Das Speichern der Datei \'%s\' schlug fehl. Fehlermeldung des Betriebssystems: %s', 'Screen' => 'Bildschirm', 'Search' => 'Suchen', @@ -2302,6 +2306,7 @@ $self->{texts} = { 'The name is missing.' => 'Der Name fehlt.', 'The name is not unique.' => 'Der Name ist nicht eindeutig.', 'The name must only consist of letters, numbers and underscores and start with a letter.' => 'Der Name darf nur aus Buchstaben (keine Umlaute), Ziffern und Unterstrichen bestehen und muss mit einem Buchstaben beginnen.', + 'The new requirement spec will be a copy of \'#1\' for customer \'#2\'.' => 'Das neue Pflichtenheft wird eine Kopie von \'#1\' für Kunde \'#2\' sein.', 'The number of days for full payment' => 'Die Anzahl Tage, bis die Rechnung in voller Höhe bezahlt werden muss', 'The option field is empty.' => 'Das Optionsfeld ist leer.', 'The package name is invalid.' => 'Der Paketname ist ungültig.', diff --git a/templates/webpages/requirement_spec/_form.html b/templates/webpages/requirement_spec/_form.html index fe3ca3137..3f4e0cf97 100644 --- a/templates/webpages/requirement_spec/_form.html +++ b/templates/webpages/requirement_spec/_form.html @@ -34,6 +34,14 @@ +[%- IF SELF.copy_source %] + [%- L.hidden_tag('copy_source_id', SELF.copy_source.id) %] + +

+ [%- LxERP.t8("The new requirement spec will be a copy of '#1' for customer '#2'.", SELF.copy_source.title, SELF.copy_source.customer.name) %] +

+[%- END %] +

[% IF submit_as == 'post' %] [% L.hidden_tag("action", "RequirementSpec/dispatch", id=id_prefix _ '_action') %] -- 2.20.1