Revision 405a41ef
Von Moritz Bunkus vor mehr als 10 Jahren hinzugefügt
SL/Controller/RequirementSpec.pm | ||
---|---|---|
4 | 4 |
|
5 | 5 |
use parent qw(SL::Controller::Base); |
6 | 6 |
|
7 |
use Rose::DB::Object::Helpers; |
|
8 |
|
|
7 | 9 |
use SL::ClientJS; |
8 | 10 |
use SL::Controller::Helper::GetModels; |
9 | 11 |
use SL::Controller::Helper::Paginated; |
... | ... | |
24 | 26 |
use Rose::Object::MakeMethods::Generic |
25 | 27 |
( |
26 | 28 |
scalar => [ qw(requirement_spec_item customers types statuses db_args flat_filter is_template visible_item visible_section) ], |
27 |
'scalar --get_set_init' => [ qw(requirement_spec complexities risks projects) ], |
|
29 |
'scalar --get_set_init' => [ qw(requirement_spec complexities risks projects copy_source) ],
|
|
28 | 30 |
); |
29 | 31 |
|
30 | 32 |
__PACKAGE__->run_before('setup'); |
31 | 33 |
__PACKAGE__->run_before('load_select_options', only => [ qw(new ajax_edit create update list) ]); |
32 | 34 |
|
35 |
|
|
33 | 36 |
__PACKAGE__->get_models_url_params('flat_filter'); |
34 | 37 |
__PACKAGE__->make_paginated( |
35 | 38 |
MODEL => 'RequirementSpec', |
... | ... | |
73 | 76 |
my ($self) = @_; |
74 | 77 |
|
75 | 78 |
$self->requirement_spec(SL::DB::RequirementSpec->new); |
79 |
|
|
80 |
if ($self->copy_source) { |
|
81 |
$self->requirement_spec->$_($self->copy_source->$_) for qw(type_id status_id customer_id title hourly_rate) |
|
82 |
} |
|
83 |
|
|
76 | 84 |
$self->render('requirement_spec/new', title => t8('Create a new requirement spec')); |
77 | 85 |
} |
78 | 86 |
|
... | ... | |
212 | 220 |
$self->requirement_spec(SL::DB::RequirementSpec->new(id => $::form->{id})->load || die "No such requirement spec") if $::form->{id}; |
213 | 221 |
} |
214 | 222 |
|
223 |
sub init_copy_source { |
|
224 |
my ($self) = @_; |
|
225 |
$self->copy_source(SL::DB::RequirementSpec->new(id => $::form->{copy_source_id})->load) if $::form->{copy_source_id}; |
|
226 |
} |
|
227 |
|
|
215 | 228 |
sub load_select_options { |
216 | 229 |
my ($self) = @_; |
217 | 230 |
|
... | ... | |
245 | 258 |
return; |
246 | 259 |
} |
247 | 260 |
|
248 |
$self->requirement_spec->save; |
|
261 |
my $db = $self->requirement_spec->db; |
|
262 |
if (!$db->do_transaction(sub { |
|
263 |
if ($self->copy_source) { |
|
264 |
$self->requirement_spec($self->copy_source->create_copy(%{ $params })); |
|
265 |
} else { |
|
266 |
$self->requirement_spec->save; |
|
267 |
} |
|
268 |
})) { |
|
269 |
$::lxdebug->message(LXDebug::WARN(), "Error: " . $db->error); |
|
270 |
@errors = ($::locale->text('Saving failed. Erro message from the database: #1'), $db->error); |
|
271 |
return SL::ClientJS->new->error(@errors)->render($self) if $::request->is_ajax; |
|
272 |
|
|
273 |
$self->requirement_spec->id(undef) if $is_new; |
|
274 |
flash('error', @errors); |
|
275 |
return $self->render('requirement_spec/new', title => $title); |
|
276 |
} |
|
249 | 277 |
|
250 | 278 |
if ($::request->is_ajax) { |
251 | 279 |
my $html = $self->render('requirement_spec/_header', { output => 0 }); |
SL/DB/RequirementSpec.pm | ||
---|---|---|
3 | 3 |
use strict; |
4 | 4 |
|
5 | 5 |
use Carp; |
6 |
use Rose::DB::Object::Helpers; |
|
6 | 7 |
|
7 | 8 |
use SL::DB::MetaSetup::RequirementSpec; |
8 | 9 |
use SL::DB::Manager::RequirementSpec; |
... | ... | |
63 | 64 |
return sprintf('%s: "%s"', $self->type->description, $self->title); |
64 | 65 |
} |
65 | 66 |
|
67 |
sub create_copy { |
|
68 |
my ($self, %params) = @_; |
|
69 |
|
|
70 |
return $self->_create_copy(%params) if $self->db->in_transaction; |
|
71 |
|
|
72 |
my $copy; |
|
73 |
if (!$self->db->do_transaction(sub { $copy = $self->_create_copy(%params) })) { |
|
74 |
$::lxdebug->message(LXDebug->WARN(), "create_copy failed: " . join("\n", (split(/\n/, $self->db->error))[0..2])); |
|
75 |
return undef; |
|
76 |
} |
|
77 |
|
|
78 |
return $copy; |
|
79 |
} |
|
80 |
|
|
81 |
sub _create_copy { |
|
82 |
my ($self, %params) = @_; |
|
83 |
|
|
84 |
my $copy = Rose::DB::Object::Helpers::clone_and_reset($self); |
|
85 |
$copy->assign_attributes(%params); |
|
86 |
|
|
87 |
# Clone text blocks. |
|
88 |
$copy->text_blocks(map { Rose::DB::Object::Helpers::clone_and_reset($_) } @{ $self->text_blocks }); |
|
89 |
|
|
90 |
# Save new object -- we need its ID for the items. |
|
91 |
$copy->save; |
|
92 |
|
|
93 |
my %id_to_clone; |
|
94 |
|
|
95 |
# Clone items. |
|
96 |
my $clone_item; |
|
97 |
$clone_item = sub { |
|
98 |
my ($item) = @_; |
|
99 |
my $cloned = Rose::DB::Object::Helpers::clone_and_reset($item); |
|
100 |
$cloned->requirement_spec_id($copy->id); |
|
101 |
$cloned->children(map { $clone_item->($_) } @{ $item->children }); |
|
102 |
|
|
103 |
$id_to_clone{ $item->id } = $cloned; |
|
104 |
|
|
105 |
return $cloned; |
|
106 |
}; |
|
107 |
|
|
108 |
$copy->items(map { $clone_item->($_) } @{ $self->sections }); |
|
109 |
|
|
110 |
# Save the items -- need to do that before setting dependencies. |
|
111 |
$copy->save; |
|
112 |
|
|
113 |
# Set dependencies. |
|
114 |
foreach my $item (@{ $self->items }) { |
|
115 |
next unless @{ $item->dependencies }; |
|
116 |
$id_to_clone{ $item->id }->update_attributes(dependencies => [ map { $id_to_clone{$_->id} } @{ $item->dependencies } ]); |
|
117 |
} |
|
118 |
|
|
119 |
return $copy; |
|
120 |
} |
|
121 |
|
|
66 | 122 |
1; |
js/locale/de.js | ||
---|---|---|
6 | 6 |
"Add text block":"Textblock erfassen", |
7 | 7 |
"Are you sure?":"Sind Sie sicher?", |
8 | 8 |
"Copy":"Kopieren", |
9 |
"Copy requirement spec":"Pflichtenheft kopieren", |
|
9 | 10 |
"Database Connection Test":"Test der Datenbankverbindung", |
10 |
"Delete text block":"Textblock löschen", |
|
11 | 11 |
"Delete":"Löschen", |
12 |
"Delete requirement spec":"Pflichtenheft löschen", |
|
13 |
"Delete text block":"Textblock löschen", |
|
12 | 14 |
"Do you really want to cancel?":"Wollen Sie wirklich abbrechen?", |
13 | 15 |
"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?", |
14 | 16 |
"Edit text block":"Textblock bearbeiten", |
... | ... | |
17 | 19 |
"Map":"Karte", |
18 | 20 |
"Part picker":"Artikelauswahl", |
19 | 21 |
"Paste":"Einfügen", |
22 |
"Requirement spec actions:":"Pflichtenheftaktionen:", |
|
20 | 23 |
"Save":"Speichern", |
21 | 24 |
"The description is missing.":"Die Beschreibung fehlt.", |
22 | 25 |
"The name is missing.":"Der Name fehlt.", |
js/requirement_spec.js | ||
---|---|---|
290 | 290 |
return true; |
291 | 291 |
} |
292 | 292 |
|
293 |
// ------------------------------------------------------------------------- |
|
294 |
// ---------------------------- general actions ---------------------------- |
|
295 |
// ------------------------------------------------------------------------- |
|
296 |
|
|
297 |
function download_reqspec_pdf(key, opt) { |
|
298 |
var data = { |
|
299 |
action: "RequirementSpec/download_pdf", |
|
300 |
id: $('#requirement_spec_id').val() |
|
301 |
}; |
|
302 |
$.download("controller.pl", data); |
|
303 |
} |
|
304 |
|
|
305 |
function copy_reqspec(key, opt) { |
|
306 |
window.location.href = "controller.pl?action=RequirementSpec/new©_source_id=" + encodeURIComponent($('#requirement_spec_id').val()); |
|
307 |
return true; |
|
308 |
} |
|
309 |
|
|
310 |
function delete_reqspec(key, opt) { |
|
311 |
if (confirm(kivi.t8("Are you sure?"))) |
|
312 |
window.location.href = "controller.pl?action=RequirementSpec/destroy&id=" + encodeURIComponent($('#requirement_spec_id').val()); |
|
313 |
return true; |
|
314 |
} |
|
315 |
|
|
293 | 316 |
// ------------------------------------------------------------------------- |
294 | 317 |
// ----------------------------- context menus ----------------------------- |
295 | 318 |
// ------------------------------------------------------------------------- |
... | ... | |
300 | 323 |
hide: requirement_spec_text_block_popup_menu_hidden |
301 | 324 |
}; |
302 | 325 |
|
326 |
var general_actions = { |
|
327 |
sep98: "---------" |
|
328 |
, general_actions: { name: kivi.t8('Requirement spec actions:') } |
|
329 |
, sep99: "---------" |
|
330 |
, copy_reqspec: { name: kivi.t8('Copy requirement spec'), icon: "copy", callback: copy_reqspec } |
|
331 |
, delete_reqspec: { name: kivi.t8('Delete requirement spec'), icon: "delete", callback: delete_reqspec } |
|
332 |
}; |
|
333 |
|
|
303 | 334 |
$.contextMenu({ |
304 | 335 |
selector: '.text-block-context-menu', |
305 | 336 |
events: { |
306 | 337 |
show: requirement_spec_text_block_popup_menu_shown |
307 | 338 |
, hide: requirement_spec_text_block_popup_menu_hidden |
308 | 339 |
}, |
309 |
items: { |
|
310 |
add: { name: kivi.t8('Add text block'), icon: "add", callback: standard_text_block_ajax_call }
|
|
311 |
, edit: { name: kivi.t8('Edit text block'), icon: "edit", callback: standard_text_block_ajax_call, disabled: disable_edit_text_block_commands }
|
|
312 |
, delete: { name: kivi.t8('Delete text block'), icon: "delete", callback: ask_delete_text_block, disabled: disable_edit_text_block_commands }
|
|
313 |
, sep1: "---------" |
|
314 |
, flag: { name: kivi.t8('Toggle marker'), icon: "flag", callback: standard_text_block_ajax_call, disabled: disable_edit_text_block_commands }
|
|
315 |
, sep2: "---------" |
|
316 |
, copy: { name: kivi.t8('Copy'), icon: "copy", callback: standard_text_block_ajax_call, disabled: disable_edit_text_block_commands }
|
|
317 |
, paste: { name: kivi.t8('Paste'), icon: "paste", callback: standard_text_block_ajax_call }
|
|
318 |
} |
|
340 |
items: $.extend({
|
|
341 |
add: { name: kivi.t8('Add text block'), icon: "add", callback: standard_text_block_ajax_call }
|
|
342 |
, edit: { name: kivi.t8('Edit text block'), icon: "edit", callback: standard_text_block_ajax_call, disabled: disable_edit_text_block_commands }
|
|
343 |
, delete: { name: kivi.t8('Delete text block'), icon: "delete", callback: ask_delete_text_block, disabled: disable_edit_text_block_commands }
|
|
344 |
, sep1: "---------"
|
|
345 |
, flag: { name: kivi.t8('Toggle marker'), icon: "flag", callback: standard_text_block_ajax_call, disabled: disable_edit_text_block_commands }
|
|
346 |
, sep2: "---------"
|
|
347 |
, copy: { name: kivi.t8('Copy'), icon: "copy", callback: standard_text_block_ajax_call, disabled: disable_edit_text_block_commands }
|
|
348 |
, paste: { name: kivi.t8('Paste'), icon: "paste", callback: standard_text_block_ajax_call }
|
|
349 |
}, general_actions)
|
|
319 | 350 |
}); |
320 | 351 |
|
321 | 352 |
var events = { |
... | ... | |
326 | 357 |
$.contextMenu({ |
327 | 358 |
selector: '.section-context-menu', |
328 | 359 |
events: events, |
329 |
items: { |
|
360 |
items: $.extend({
|
|
330 | 361 |
add_section: { name: kivi.t8('Add section'), icon: "add", callback: standard_item_ajax_call } |
331 | 362 |
, add_function_block: { name: kivi.t8('Add function block'), icon: "add", callback: standard_item_ajax_call, disabled: disable_add_function_block_command } |
332 | 363 |
, sep1: "---------" |
... | ... | |
337 | 368 |
, sep3: "---------" |
338 | 369 |
, copy: { name: kivi.t8('Copy'), icon: "copy", callback: standard_item_ajax_call, disabled: disable_edit_item_commands } |
339 | 370 |
, paste: { name: kivi.t8('Paste'), icon: "paste", callback: standard_item_ajax_call } |
340 |
} |
|
371 |
}, general_actions)
|
|
341 | 372 |
}); |
342 | 373 |
|
343 | 374 |
$.contextMenu({ |
344 | 375 |
selector: '.function-block-context-menu,.sub-function-block-context-menu', |
345 | 376 |
events: events, |
346 |
items: { |
|
377 |
items: $.extend({
|
|
347 | 378 |
add_function_block: { name: kivi.t8('Add function block'), icon: "add", callback: standard_item_ajax_call } |
348 | 379 |
, add_sub_function_block: { name: kivi.t8('Add sub function block'), icon: "add", callback: standard_item_ajax_call } |
349 | 380 |
, sep1: "---------" |
... | ... | |
354 | 385 |
, sep3: "---------" |
355 | 386 |
, copy: { name: kivi.t8('Copy'), icon: "copy", callback: standard_item_ajax_call, disabled: disable_edit_item_commands } |
356 | 387 |
, paste: { name: kivi.t8('Paste'), icon: "paste", callback: standard_item_ajax_call } |
357 |
} |
|
388 |
}, general_actions)
|
|
358 | 389 |
}); |
359 | 390 |
|
360 | 391 |
$.contextMenu({ |
361 | 392 |
selector: '.time-cost-estimate-context-menu', |
362 | 393 |
events: events, |
363 |
items: { edit: { name: kivi.t8('Edit'), icon: "edit", callback: standard_time_cost_estimate_ajax_call } }
|
|
394 |
items: $.extend({ edit: { name: kivi.t8('Edit'), icon: "edit", callback: standard_time_cost_estimate_ajax_call } }, general_actions)
|
|
364 | 395 |
}); |
365 | 396 |
|
366 | 397 |
$.contextMenu({ |
367 | 398 |
selector: '.edit-time-cost-estimate-context-menu', |
368 | 399 |
events: events, |
369 |
items: { |
|
400 |
items: $.extend({
|
|
370 | 401 |
save: { name: kivi.t8('Save'), icon: "save", callback: standard_time_cost_estimate_ajax_call } |
371 | 402 |
, cancel: { name: kivi.t8('Cancel'), icon: "close", callback: standard_time_cost_estimate_ajax_call } |
372 |
} |
|
403 |
}, general_actions)
|
|
373 | 404 |
}); |
374 | 405 |
} |
locale/de/all | ||
---|---|---|
513 | 513 |
'Copies' => 'Kopien', |
514 | 514 |
'Copy file from #1 to #2 failed: #3' => 'Kopieren der Datei von #1 nach #2 schlug fehl: #3', |
515 | 515 |
'Copy' => 'Kopieren', |
516 |
'Copy requirement spec' => 'Pflichtenheft kopieren', |
|
516 | 517 |
'Correct taxkey' => 'Richtiger Steuerschlüssel', |
517 | 518 |
'Cost' => 'Kosten', |
518 | 519 |
'Costs' => 'Kosten', |
... | ... | |
714 | 715 |
'Delete drafts' => 'Entwürfe löschen', |
715 | 716 |
'Delete links' => 'Verknüpfungen löschen', |
716 | 717 |
'Delete profile' => 'Profil löschen', |
718 |
'Delete requirement spec' => 'Pflichtenheft löschen', |
|
717 | 719 |
'Delete text block' => 'Textblock löschen', |
718 | 720 |
'Delete transaction' => 'Buchung löschen', |
719 | 721 |
'Deleted' => 'Gelöscht', |
... | ... | |
1847 | 1849 |
'Requirement Spec Type' => 'Pflichtenhefttyp', |
1848 | 1850 |
'Requirement Spec Types' => 'Pflichtenhefttypen', |
1849 | 1851 |
'Requirement Specs' => 'Pflichtenhefte', |
1852 |
'Requirement spec actions:' => 'Pflichtenheftaktionen:', |
|
1850 | 1853 |
'Requirement spec function block #1 with #2 sub function blocks; description: "#3"' => 'Pflichtenheft-Funktionsblock #1 mit #2 Unterfunktionsblöcken; Beschreibung: "#3"', |
1851 | 1854 |
'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"', |
1852 | 1855 |
'Requirement spec sub function block #1; description: "#2"' => 'Pflichtenheft-Unterfunktionsblock #1; Beschreibung: "#2"', |
... | ... | |
1925 | 1928 |
'Save draft' => 'Entwurf speichern', |
1926 | 1929 |
'Save profile' => 'Profil speichern', |
1927 | 1930 |
'Save settings as' => 'Einstellungen speichern unter', |
1931 |
'Saving failed. Erro message from the database: #1' => 'Speichern schlug fehl. Fehlermeldung der Datenbank: #1', |
|
1928 | 1932 |
'Saving the file \'%s\' failed. OS error message: %s' => 'Das Speichern der Datei \'%s\' schlug fehl. Fehlermeldung des Betriebssystems: %s', |
1929 | 1933 |
'Screen' => 'Bildschirm', |
1930 | 1934 |
'Search' => 'Suchen', |
... | ... | |
2302 | 2306 |
'The name is missing.' => 'Der Name fehlt.', |
2303 | 2307 |
'The name is not unique.' => 'Der Name ist nicht eindeutig.', |
2304 | 2308 |
'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.', |
2309 |
'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.', |
|
2305 | 2310 |
'The number of days for full payment' => 'Die Anzahl Tage, bis die Rechnung in voller Höhe bezahlt werden muss', |
2306 | 2311 |
'The option field is empty.' => 'Das Optionsfeld ist leer.', |
2307 | 2312 |
'The package name is invalid.' => 'Der Paketname ist ungültig.', |
templates/webpages/requirement_spec/_form.html | ||
---|---|---|
34 | 34 |
|
35 | 35 |
</table> |
36 | 36 |
|
37 |
[%- IF SELF.copy_source %] |
|
38 |
[%- L.hidden_tag('copy_source_id', SELF.copy_source.id) %] |
|
39 |
|
|
40 |
<p> |
|
41 |
[%- LxERP.t8("The new requirement spec will be a copy of '#1' for customer '#2'.", SELF.copy_source.title, SELF.copy_source.customer.name) %] |
|
42 |
</p> |
|
43 |
[%- END %] |
|
44 |
|
|
37 | 45 |
<p> |
38 | 46 |
[% IF submit_as == 'post' %] |
39 | 47 |
[% L.hidden_tag("action", "RequirementSpec/dispatch", id=id_prefix _ '_action') %] |
Auch abrufbar als: Unified diff
Pflichtenhefte: Kopieren (clonen) generell und Löschen aus Kontextmenü implementiert