use parent qw(SL::Controller::Base);
+use Rose::DB::Object::Helpers;
+
use SL::ClientJS;
use SL::Controller::Helper::GetModels;
use SL::Controller::Helper::Paginated;
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',
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'));
}
$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) = @_;
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 });
use strict;
use Carp;
+use Rose::DB::Object::Helpers;
use SL::DB::MetaSetup::RequirementSpec;
use SL::DB::Manager::RequirementSpec;
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;
"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",
"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.",
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 -----------------------------
// -------------------------------------------------------------------------
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 = {
$.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: "---------"
, 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: "---------"
, 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)
});
}
'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',
'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',
'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"',
'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',
'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.',
</table>
+[%- IF SELF.copy_source %]
+ [%- L.hidden_tag('copy_source_id', SELF.copy_source.id) %]
+
+ <p>
+ [%- LxERP.t8("The new requirement spec will be a copy of '#1' for customer '#2'.", SELF.copy_source.title, SELF.copy_source.customer.name) %]
+ </p>
+[%- END %]
+
<p>
[% IF submit_as == 'post' %]
[% L.hidden_tag("action", "RequirementSpec/dispatch", id=id_prefix _ '_action') %]