Revision c19b1e03
Von Moritz Bunkus vor mehr als 10 Jahren hinzugefügt
SL/Controller/Helper/RequirementSpec.pm | ||
---|---|---|
1 |
package SL::Controller::Helper::RequirementSpec; |
|
2 |
|
|
3 |
use strict; |
|
4 |
|
|
5 |
use Exporter qw(import); |
|
6 |
our @EXPORT = qw(init_visible_section); |
|
7 |
|
|
8 |
use SL::DB::Manager::RequirementSpecItem; |
|
9 |
|
|
10 |
sub init_visible_section { |
|
11 |
my ($self) = @_; |
|
12 |
|
|
13 |
my $content_id = $::form->{current_content_id}; |
|
14 |
my $content_type = $::form->{current_content_type}; |
|
15 |
|
|
16 |
return undef unless $content_id; |
|
17 |
return undef unless $content_type =~ m/section|function-block/; |
|
18 |
|
|
19 |
$self->visible_item(SL::DB::Manager::RequirementSpecItem->find_by(id => $content_id)); |
|
20 |
return undef unless $self->visible_item; |
|
21 |
|
|
22 |
return $self->visible_section($self->visible_item->section); |
|
23 |
} |
|
24 |
|
|
25 |
1; |
SL/Controller/RequirementSpec.pm | ||
---|---|---|
10 | 10 |
use SL::Controller::Helper::Sorted; |
11 | 11 |
use SL::Controller::Helper::ParseFilter; |
12 | 12 |
use SL::Controller::Helper::ReportGenerator; |
13 |
use SL::Controller::Helper::RequirementSpec; |
|
13 | 14 |
use SL::DB::Customer; |
14 | 15 |
use SL::DB::Project; |
16 |
use SL::DB::RequirementSpecComplexity; |
|
17 |
use SL::DB::RequirementSpecRisk; |
|
15 | 18 |
use SL::DB::RequirementSpecStatus; |
16 | 19 |
use SL::DB::RequirementSpecType; |
17 | 20 |
use SL::DB::RequirementSpec; |
... | ... | |
20 | 23 |
|
21 | 24 |
use Rose::Object::MakeMethods::Generic |
22 | 25 |
( |
23 |
scalar => [ qw(requirement_spec requirement_spec_item customers projects types statuses db_args flat_filter is_template) ], |
|
26 |
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) ], |
|
24 | 28 |
); |
25 | 29 |
|
26 | 30 |
__PACKAGE__->run_before('setup'); |
27 |
__PACKAGE__->run_before('load_requirement_spec', only => [ qw( ajax_edit update show destroy) ]); |
|
28 |
__PACKAGE__->run_before('load_select_options', only => [ qw(new ajax_edit create update list) ]); |
|
29 |
__PACKAGE__->run_before('load_search_select_options', only => [ qw( list) ]); |
|
31 |
__PACKAGE__->run_before('load_select_options', only => [ qw(new ajax_edit create update list) ]); |
|
30 | 32 |
|
31 | 33 |
__PACKAGE__->get_models_url_params('flat_filter'); |
32 | 34 |
__PACKAGE__->make_paginated( |
... | ... | |
53 | 55 |
# actions |
54 | 56 |
# |
55 | 57 |
|
58 |
|
|
56 | 59 |
sub action_list { |
57 | 60 |
my ($self) = @_; |
58 | 61 |
|
... | ... | |
79 | 82 |
$self->render('requirement_spec/_form', { layout => 0 }, submit_as => 'ajax'); |
80 | 83 |
} |
81 | 84 |
|
85 |
sub action_ajax_show_time_and_cost_estimate { |
|
86 |
my ($self) = @_; |
|
87 |
|
|
88 |
$self->render('requirement_spec/_show_time_and_cost_estimate', { layout => 0 }); |
|
89 |
} |
|
90 |
|
|
91 |
sub action_ajax_cancel_time_and_cost_estimate { |
|
92 |
my ($self) = @_; |
|
93 |
|
|
94 |
my $html = $self->render('requirement_spec/_show_time_and_cost_estimate', { output => 0 }); |
|
95 |
|
|
96 |
SL::ClientJS->new |
|
97 |
->replaceWith('#time_cost_estimate', $html) |
|
98 |
->render($self); |
|
99 |
} |
|
100 |
|
|
101 |
sub action_ajax_edit_time_and_cost_estimate { |
|
102 |
my ($self) = @_; |
|
103 |
|
|
104 |
my $html = $self->render('requirement_spec/_edit_time_and_cost_estimate', { output => 0 }); |
|
105 |
|
|
106 |
SL::ClientJS->new |
|
107 |
->replaceWith('#time_cost_estimate', $html) |
|
108 |
->render($self); |
|
109 |
} |
|
110 |
|
|
111 |
sub action_ajax_save_time_and_cost_estimate { |
|
112 |
my ($self) = @_; |
|
113 |
|
|
114 |
$self->requirement_spec->db->do_transaction(sub { |
|
115 |
# Make Emacs happy |
|
116 |
1; |
|
117 |
foreach my $attributes (@{ $::form->{requirement_spec_items} || [] }) { |
|
118 |
SL::DB::RequirementSpecItem |
|
119 |
->new(id => delete $attributes->{id}) |
|
120 |
->load |
|
121 |
->update_attributes(%{ $attributes }); |
|
122 |
} |
|
123 |
|
|
124 |
1; |
|
125 |
}); |
|
126 |
|
|
127 |
my $html = $self->render('requirement_spec/_show_time_and_cost_estimate', { output => 0 }); |
|
128 |
my $js = SL::ClientJS->new->replaceWith('#time_cost_estimate', $html); |
|
129 |
|
|
130 |
if ($self->visible_section) { |
|
131 |
$html = $self->render('requirement_spec_item/_section', { output => 0 }, requirement_spec_item => $self->visible_section); |
|
132 |
$js->html('#column-content', $html); |
|
133 |
} |
|
134 |
|
|
135 |
$js->render($self); |
|
136 |
} |
|
137 |
|
|
82 | 138 |
sub action_show { |
83 | 139 |
my ($self) = @_; |
84 | 140 |
|
... | ... | |
131 | 187 |
$::request->{layout}->use_stylesheet("${_}.css") for qw(jquery.contextMenu requirement_spec); |
132 | 188 |
$::request->{layout}->use_javascript("${_}.js") for qw(jquery.jstree jquery/jquery.contextMenu client_js requirement_spec); |
133 | 189 |
$self->is_template($::form->{is_template} ? 1 : 0); |
190 |
$self->init_visible_section; |
|
134 | 191 |
|
135 | 192 |
return 1; |
136 | 193 |
} |
137 | 194 |
|
138 |
sub load_requirement_spec { |
|
195 |
sub init_complexities { |
|
196 |
my ($self) = @_; |
|
197 |
return SL::DB::Manager::RequirementSpecComplexity->get_all_sorted; |
|
198 |
} |
|
199 |
|
|
200 |
sub init_risks { |
|
201 |
my ($self) = @_; |
|
202 |
return SL::DB::Manager::RequirementSpecRisk->get_all_sorted; |
|
203 |
} |
|
204 |
|
|
205 |
sub init_projects { |
|
206 |
my ($self) = @_; |
|
207 |
$self->projects(SL::DB::Manager::Project->get_all_sorted); |
|
208 |
} |
|
209 |
|
|
210 |
sub init_requirement_spec { |
|
139 | 211 |
my ($self) = @_; |
140 |
$self->requirement_spec(SL::DB::RequirementSpec->new(id => $::form->{id})->load || die "No such requirement spec"); |
|
212 |
$self->requirement_spec(SL::DB::RequirementSpec->new(id => $::form->{id})->load || die "No such requirement spec") if $::form->{id};
|
|
141 | 213 |
} |
142 | 214 |
|
143 | 215 |
sub load_select_options { |
144 | 216 |
my ($self) = @_; |
145 | 217 |
|
146 | 218 |
my @filter = ('!obsolete' => 1); |
147 |
if ($self->requirement_spec && $self->requirement_spec->customer_id) { |
|
148 |
@filter = ( or => [ @filter, id => $self->requirement_spec->customer_id ] ); |
|
149 |
} |
|
219 |
@filter = ( or => [ @filter, id => $self->requirement_spec->customer_id ] ) if $self->requirement_spec && $self->requirement_spec->customer_id; |
|
150 | 220 |
|
151 | 221 |
$self->customers(SL::DB::Manager::Customer->get_all_sorted(where => \@filter)); |
152 | 222 |
$self->statuses( SL::DB::Manager::RequirementSpecStatus->get_all_sorted); |
153 | 223 |
$self->types( SL::DB::Manager::RequirementSpecType->get_all_sorted); |
154 | 224 |
} |
155 | 225 |
|
156 |
sub load_search_select_options { |
|
157 |
my ($self) = @_; |
|
158 |
|
|
159 |
$self->projects(SL::DB::Manager::Project->get_all_sorted); |
|
160 |
} |
|
161 |
|
|
162 | 226 |
# |
163 | 227 |
# helpers |
164 | 228 |
# |
SL/Controller/RequirementSpecItem.pm | ||
---|---|---|
10 | 10 |
use Time::HiRes (); |
11 | 11 |
|
12 | 12 |
use SL::Clipboard; |
13 |
use SL::Controller::Helper::RequirementSpec; |
|
13 | 14 |
use SL::DB::RequirementSpec; |
14 | 15 |
use SL::DB::RequirementSpecComplexity; |
15 | 16 |
use SL::DB::RequirementSpecItem; |
... | ... | |
490 | 491 |
return join "\n", (split m/\n/, $@)[0..4]; |
491 | 492 |
} |
492 | 493 |
|
493 |
sub init_visible_section { |
|
494 |
my ($self) = @_; |
|
495 |
|
|
496 |
my $content_id = $::form->{current_content_id}; |
|
497 |
my $content_type = $::form->{current_content_type}; |
|
498 |
|
|
499 |
return undef unless $content_id; |
|
500 |
return undef unless $content_type =~ m/section|function-block/; |
|
501 |
|
|
502 |
$self->visible_item(SL::DB::Manager::RequirementSpecItem->find_by(id => $content_id)); |
|
503 |
return undef unless $self->visible_item; |
|
504 |
|
|
505 |
return $self->visible_section($self->visible_item->section); |
|
506 |
} |
|
507 |
|
|
508 | 494 |
sub init_complexities { |
509 | 495 |
my ($self) = @_; |
510 | 496 |
|
SL/Controller/Test.pm | ||
---|---|---|
1 |
package SL::Controller::Test; |
|
2 |
|
|
3 |
use strict; |
|
4 |
|
|
5 |
use parent qw(SL::Controller::Base); |
|
6 |
|
|
7 |
use Data::Dumper; |
|
8 |
use SL::ClientJS; |
|
9 |
|
|
10 |
sub action_dump_form { |
|
11 |
my ($self) = @_; |
|
12 |
|
|
13 |
my $output = Dumper($::form); |
|
14 |
$self->render(\$output, { type => 'text' }); |
|
15 |
} |
|
16 |
|
|
17 |
1; |
css/requirement_spec.css | ||
---|---|---|
43 | 43 |
/* Special things that apply to the context menu */ |
44 | 44 |
/* ------------------------------------------------------------ */ |
45 | 45 |
|
46 |
.context-menu-item.icon-flag { background-image: url("../image/flag-red.png"); } |
|
47 |
|
|
46 |
.context-menu-item.icon-flag { background-image: url("../image/flag-red.png"); } |
|
47 |
.context-menu-item.icon-close { background-image: url("../image/document-close.png"); } |
|
48 |
.context-menu-item.icon-save { background-image: url("../image/document-save.png"); } |
|
48 | 49 |
|
49 | 50 |
/* ------------------------------------------------------------ */ |
50 | 51 |
/* Sections & function blocks */ |
js/locale/de.js | ||
---|---|---|
5 | 5 |
"Add sub function block":"Unterfunktionsblock hinzufügen", |
6 | 6 |
"Add text block":"Textblock erfassen", |
7 | 7 |
"Are you sure?":"Sind Sie sicher?", |
8 |
"Copy":"Kopieren", |
|
8 | 9 |
"Database Connection Test":"Test der Datenbankverbindung", |
10 |
"Delete text block":"Textblock löschen", |
|
11 |
"Delete":"Löschen", |
|
12 |
"Do you really want to cancel?":"Wollen Sie wirklich abbrechen?", |
|
9 | 13 |
"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 |
"Edit text block":"Textblock bearbeiten", |
|
15 |
"Edit":"Bearbeiten", |
|
10 | 16 |
"Enter longdescription":"Langtext eingeben", |
11 | 17 |
"Map":"Karte", |
12 | 18 |
"Part picker":"Artikelauswahl", |
19 |
"Paste":"Einfügen", |
|
20 |
"Save":"Speichern", |
|
13 | 21 |
"The description is missing.":"Die Beschreibung fehlt.", |
14 | 22 |
"The name is missing.":"Der Name fehlt.", |
15 | 23 |
"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.", |
16 | 24 |
"The option field is empty.":"Das Optionsfeld ist leer.", |
17 | 25 |
"The selected database is still configured for client \"#1\". If you delete the database that client will stop working until you re-configure it. Do you still want to delete the database?":"Die auswählte Datenbank ist noch für Mandant \"#1\" konfiguriert. Wenn Sie die Datenbank löschen, wird der Mandanten nicht mehr funktionieren, bis er anders konfiguriert wurde. Wollen Sie die Datenbank trotzdem löschen?" |
18 |
"Copy":"Kopieren", |
|
19 |
"Delete":"Löschen", |
|
20 |
"Delete text block":"Textblock löschen", |
|
21 |
"Edit":"Bearbeiten", |
|
22 |
"Edit text block":"Textblock bearbeiten", |
|
23 |
"Paste":"Einfügen", |
|
24 | 26 |
"Toggle marker":"Markierung umschalten" |
25 | 27 |
}); |
js/requirement_spec.js | ||
---|---|---|
268 | 268 |
return handle_item_popup_menu_markings(opt, false); |
269 | 269 |
} |
270 | 270 |
|
271 |
// ------------------------------------------------------------------------- |
|
272 |
// -------------------------- time/cost estimate --------------------------- |
|
273 |
// ------------------------------------------------------------------------- |
|
274 |
|
|
275 |
function standard_time_cost_estimate_ajax_call(key, opt) { |
|
276 |
if ((key == 'cancel') && !confirm(kivi.t8('Do you really want to cancel?'))) |
|
277 |
return true; |
|
278 |
|
|
279 |
var data = "action=RequirementSpec/ajax_" + key + "_time_and_cost_estimate&"; |
|
280 |
|
|
281 |
if (key == 'save') |
|
282 |
data += $('#edit_time_cost_estimate_form').serialize() |
|
283 |
+ '&' + $('#current_content_type').serialize() |
|
284 |
+ '&' + $('#current_content_id').serialize(); |
|
285 |
else |
|
286 |
data += 'id=' + encodeURIComponent($('#requirement_spec_id').val()); |
|
287 |
|
|
288 |
$.post("controller.pl", data, eval_json_result); |
|
289 |
|
|
290 |
return true; |
|
291 |
} |
|
292 |
|
|
293 |
// ------------------------------------------------------------------------- |
|
294 |
// ----------------------------- context menus ----------------------------- |
|
295 |
// ------------------------------------------------------------------------- |
|
296 |
|
|
271 | 297 |
function create_requirement_spec_context_menus() { |
272 | 298 |
var events = { |
273 | 299 |
show: requirement_spec_text_block_popup_menu_shown, |
... | ... | |
330 | 356 |
, paste: { name: kivi.t8('Paste'), icon: "paste", callback: standard_item_ajax_call } |
331 | 357 |
} |
332 | 358 |
}); |
359 |
|
|
360 |
$.contextMenu({ |
|
361 |
selector: '.time-cost-estimate-context-menu', |
|
362 |
events: events, |
|
363 |
items: { edit: { name: kivi.t8('Edit'), icon: "edit", callback: standard_time_cost_estimate_ajax_call } } |
|
364 |
}); |
|
365 |
|
|
366 |
$.contextMenu({ |
|
367 |
selector: '.edit-time-cost-estimate-context-menu', |
|
368 |
events: events, |
|
369 |
items: { |
|
370 |
save: { name: kivi.t8('Save'), icon: "save", callback: standard_time_cost_estimate_ajax_call } |
|
371 |
, cancel: { name: kivi.t8('Cancel'), icon: "close", callback: standard_time_cost_estimate_ajax_call } |
|
372 |
} |
|
373 |
}); |
|
333 | 374 |
} |
locale/de/all | ||
---|---|---|
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 | 516 |
'Correct taxkey' => 'Richtiger Steuerschlüssel', |
517 |
'Cost' => 'Kosten', |
|
517 | 518 |
'Costs' => 'Kosten', |
518 | 519 |
'Could not load class #1 (#2): "#3"' => 'Konnte Klasse #1 (#2) nicht laden: "#3"', |
519 | 520 |
'Could not load class #1, #2' => 'Konnte Klasse #1 nicht laden: "#2"', |
... | ... | |
765 | 766 |
'Do not change the tax rate of taxkey 0.' => 'Ändern Sie nicht den Steuersatz vom Steuerschlüssel 0.', |
766 | 767 |
'Do not check for duplicates' => 'Nicht nach Dubletten suchen', |
767 | 768 |
'Do not set default buchungsgruppe' => 'Nie Standardbuchungsgruppe setzen', |
769 |
'Do you really want to cancel?' => 'Wollen Sie wirklich abbrechen?', |
|
768 | 770 |
'Do you really want to close the following SEPA exports? No payment will be recorded for bank collections that haven\'t been marked as executed yet.' => 'Wollen Sie wirklich die folgenden SEPA-Exporte abschließen? Für Überweisungen, die noch nicht gebucht wurden, werden dann keine Zahlungen verbucht.', |
769 | 771 |
'Do you really want to close the following SEPA exports? No payment will be recorded for bank transfers that haven\'t been marked as executed yet.' => 'Wollen Sie wirklich die folgenden SEPA-Exporte abschließen? Für Überweisungen, die noch nicht gebucht wurden, werden dann keine Zahlungen verbucht.', |
770 | 772 |
'Do you really want to delete AP transaction #1?' => 'Wollen Sie wirklich die Kreditorenbuchung #1 löschen?', |
... | ... | |
1064 | 1066 |
'Full Access' => 'Vollzugriff', |
1065 | 1067 |
'Full Preview' => 'Alles', |
1066 | 1068 |
'Full access to all functions' => 'Vollzugriff auf alle Funktionen', |
1069 |
'Function block' => 'Funktionsblock', |
|
1067 | 1070 |
'Function/position' => 'Funktion/Position', |
1068 | 1071 |
'Fwd' => 'Vorwärts', |
1069 | 1072 |
'GL Transaction' => 'Dialogbuchung', |
... | ... | |
1395 | 1398 |
'Name and Street' => 'Name und Straße', |
1396 | 1399 |
'National Expenses' => 'Aufwand Inland', |
1397 | 1400 |
'National Revenues' => 'Erlöse Inland', |
1401 |
'Neither sections nor function blocks have been created yet.' => 'Es wurden bisher weder Abschnitte noch Funktionsblöcke angelegt.', |
|
1398 | 1402 |
'Net Income Statement' => 'Einnahmenüberschußrechnung', |
1399 | 1403 |
'Net amount' => 'Nettobetrag', |
1400 | 1404 |
'Net amount (for verification)' => 'Nettobetrag (zur Überprüfung)', |
... | ... | |
2091 | 2095 |
'Such entries cannot be exported into the DATEV format and have to be fixed as well.' => 'Solche Einträge sind aber nicht DATEV-exportiertbar und müssen ebenfalls korrigiert werden.', |
2092 | 2096 |
'Sum Credit' => 'Summe Haben', |
2093 | 2097 |
'Sum Debit' => 'Summe Soll', |
2098 |
'Sum for #1' => 'Summe für #1', |
|
2099 |
'Sum for section' => 'Summe für Abschnitt', |
|
2094 | 2100 |
'Sum for' => 'Summe für', |
2095 | 2101 |
'Sum open amount' => 'Summierter offener Betrag', |
2096 | 2102 |
'Sum per' => 'Summe per', |
... | ... | |
2459 | 2465 |
'Three Options:' => 'Drei Optionen:', |
2460 | 2466 |
'Time Format' => 'Uhrzeitformat', |
2461 | 2467 |
'Time Tracking' => 'Zeiterfassung', |
2468 |
'Time and cost estimate' => 'Zeit- und Kostenschätzung', |
|
2462 | 2469 |
'Time estimate' => 'Zeitschätzung', |
2463 | 2470 |
'Time period for the analysis:' => 'Analysezeitraum:', |
2464 | 2471 |
'Timestamp' => 'Uhrzeit', |
templates/webpages/requirement_spec/_edit_time_and_cost_estimate.html | ||
---|---|---|
1 |
[%- USE LxERP -%][%- USE L -%][%- USE HTML -%][%- USE P -%] |
|
2 |
[%- DEFAULT id_prefix = 'time_and_cost_estimate_form' %] |
|
3 |
|
|
4 |
<div id="time_cost_estimate" class="edit-time-cost-estimate-context-menu"> |
|
5 |
[%- IF !SELF.requirement_spec.sections.size %] |
|
6 |
<p>[%- LxERP.t8("Neither sections nor function blocks have been created yet.") %]</p> |
|
7 |
|
|
8 |
[%- ELSE %] |
|
9 |
|
|
10 |
[%- SET at_least_one_function_block = 0 %] |
|
11 |
|
|
12 |
<form method="post" id="edit_time_cost_estimate_form"> |
|
13 |
[%- L.hidden_tag('id', SELF.requirement_spec.id, id=id_prefix _ '_id') -%] |
|
14 |
|
|
15 |
[%# time-cost-estimate-context-menu %] |
|
16 |
<table class="time-cost-estimate"> |
|
17 |
<tbody> |
|
18 |
[%- FOREACH section = SELF.requirement_spec.sections %] |
|
19 |
<tr class="listheading"> |
|
20 |
<th>[%- LxERP.t8("Function block") %]</th> |
|
21 |
<th>[%- LxERP.t8("Complexity") %]</th> |
|
22 |
<th>[%- LxERP.t8("Risk") %]</th> |
|
23 |
<th align="right">[%- LxERP.t8("Time estimate") %]</th> |
|
24 |
</tr> |
|
25 |
|
|
26 |
<tr class="listrow section"> |
|
27 |
<td colspan="5">[%- HTML.escape(section.fb_number) %]: [%- HTML.escape(section.title) %]</td> |
|
28 |
</tr> |
|
29 |
|
|
30 |
[%- IF section.children.size %] |
|
31 |
[%- SET at_least_one_function_block = 1 %] |
|
32 |
[%- FOREACH child = section.children %] |
|
33 |
[%- INCLUDE 'requirement_spec/_edit_time_and_cost_estimate_item.html' |
|
34 |
id_prefix = id_prefix |
|
35 |
item = child |
|
36 |
level = 1 %] |
|
37 |
[%- END %] |
|
38 |
[%- END -%] |
|
39 |
[%- END -%] |
|
40 |
</tbody> |
|
41 |
</table> |
|
42 |
</form> |
|
43 |
[%- END %] |
|
44 |
</div> |
templates/webpages/requirement_spec/_edit_time_and_cost_estimate_item.html | ||
---|---|---|
1 |
[%- USE HTML -%][%- USE LxERP -%][%- USE P -%][%- USE L -%] |
|
2 |
<tr class="listrow"> |
|
3 |
[% L.hidden_tag("requirement_spec_items[+].id", item.id, id = id_prefix _ '_item_id') %] |
|
4 |
|
|
5 |
<td style="padding-left: [%- level * 50 -%]px"> |
|
6 |
[%- P.simple_format(item.fb_number _ ": " _ item.description) -%] |
|
7 |
</td> |
|
8 |
<td> |
|
9 |
[%- L.select_tag('requirement_spec_items[].complexity_id', SELF.complexities, id=id_prefix _ '_complexity_id', title_key='description', default=item.complexity_id, style="width: 100%") %]<br> |
|
10 |
</td> |
|
11 |
<td> |
|
12 |
[%- L.select_tag('requirement_spec_items[].risk_id', SELF.risks, id=id_prefix _ '_risk_id', title_key='description', default=item.risk_id, style="width: 100%") %]<br> |
|
13 |
</td> |
|
14 |
[%- IF !item.children.size -%] |
|
15 |
<td align="right" nowrap>[%- P.man_days_tag('requirement_spec_items[].time_estimation', item, id=id_prefix _ '_time_estimation') %]</td> |
|
16 |
[%- ELSE -%] |
|
17 |
<td> </td> |
|
18 |
[%- END -%] |
|
19 |
</tr> |
|
20 |
|
|
21 |
[%- IF item.children.size -%] |
|
22 |
[%- FOREACH child = item.children -%] |
|
23 |
[%- INCLUDE 'requirement_spec/_edit_time_and_cost_estimate_item.html' |
|
24 |
id_prefix = id_prefix |
|
25 |
item = child |
|
26 |
level = level + 1 -%] |
|
27 |
[%- END -%] |
|
28 |
[%- END -%] |
templates/webpages/requirement_spec/_form.html | ||
---|---|---|
29 | 29 |
|
30 | 30 |
<tr> |
31 | 31 |
<td>[% LxERP.t8("Hourly Rate") %]</td> |
32 |
<td>[% L.input_tag(form_prefix _ "hourly_rate_as_number", SELF.requirement_spec.hourly_rate_as_number, id=id_prefix _ '_hourly_rate_as_number') %]</td>
|
|
32 |
<td>[% L.input_tag("requirement_spec.hourly_rate_as_number", SELF.requirement_spec.hourly_rate_as_number, id=id_prefix _ '_hourly_rate_as_number') %]</td>
|
|
33 | 33 |
</tr> |
34 | 34 |
|
35 | 35 |
</table> |
templates/webpages/requirement_spec/_show_time_and_cost_estimate.html | ||
---|---|---|
1 |
[%- USE LxERP -%][%- USE L -%][%- USE HTML -%][%- USE P -%] |
|
2 |
[%- DEFAULT id_prefix = 'time_and_cost_estimate_form' %] |
|
3 |
|
|
4 |
<div id="time_cost_estimate"> |
|
5 |
[%- IF !SELF.requirement_spec.sections.size %] |
|
6 |
<p>[%- LxERP.t8("Neither sections nor function blocks have been created yet.") %]</p> |
|
7 |
|
|
8 |
[%- ELSE %] |
|
9 |
|
|
10 |
[%- SET at_least_one_function_block = 0 %] |
|
11 |
|
|
12 |
<table class="time-cost-estimate time-cost-estimate-context-menu"> |
|
13 |
<tbody> |
|
14 |
[%- FOREACH section = SELF.requirement_spec.sections %] |
|
15 |
<tr class="listheading"> |
|
16 |
<th>[%- LxERP.t8("Function block") %]</th> |
|
17 |
<th>[%- LxERP.t8("Complexity") %]</th> |
|
18 |
<th>[%- LxERP.t8("Risk") %]</th> |
|
19 |
<th align="right">[%- LxERP.t8("Time estimate") %]</th> |
|
20 |
<th align="right">[%- LxERP.t8("Cost") %]</th> |
|
21 |
</tr> |
|
22 |
|
|
23 |
<tr class="listrow section"> |
|
24 |
<td colspan="5">[%- HTML.escape(section.fb_number) %]: [%- HTML.escape(section.title) %]</td> |
|
25 |
</tr> |
|
26 |
|
|
27 |
[%- IF section.children.size %] |
|
28 |
[%- SET at_least_one_function_block = 1 %] |
|
29 |
[%- FOREACH child = section.children %] |
|
30 |
[%- INCLUDE 'requirement_spec/_show_time_and_cost_estimate_item.html' |
|
31 |
item = child |
|
32 |
level = 1 %] |
|
33 |
[%- END %] |
|
34 |
|
|
35 |
<tr class="listrow subtotal"> |
|
36 |
<td style="padding-left: 50px" colspan="3" class="sum">[%- LxERP.t8("Sum for section") -%]:</td> |
|
37 |
<td align="right" nowrap>[%- P.format_man_days(section.time_estimation, 'skip_zero'=1) -%]</td> |
|
38 |
<td align="right" nowrap>[%- LxERP.format_amount(section.time_estimation * SELF.requirement_spec.hourly_rate, 2) -%] EUR</td> |
|
39 |
</tr> |
|
40 |
[%- END -%] |
|
41 |
[%- END -%] |
|
42 |
</tbody> |
|
43 |
|
|
44 |
<tfoot> |
|
45 |
<tr> |
|
46 |
<td colspan="3">[%- LxERP.t8("Sum for #1", SELF.requirement_spec.type.description) -%]:</td> |
|
47 |
<td align="right" nowrap>[%- P.format_man_days(SELF.requirement_spec.time_estimation) -%]</td> |
|
48 |
<td align="right" nowrap>[%- LxERP.format_amount(SELF.requirement_spec.time_estimation * SELF.requirement_spec.hourly_rate, 2) -%] EUR</td> |
|
49 |
</tr> |
|
50 |
</tfoot> |
|
51 |
</table> |
|
52 |
[%- END %] |
|
53 |
</div> |
templates/webpages/requirement_spec/_show_time_and_cost_estimate_item.html | ||
---|---|---|
1 |
[%- USE HTML -%][%- USE LxERP -%][%- USE P -%] |
|
2 |
<tr class="listrow"> |
|
3 |
<td style="padding-left: [%- level * 50 -%]px"> |
|
4 |
[%- P.simple_format(item.fb_number _ ": " _ item.description) -%] |
|
5 |
</td> |
|
6 |
<td>[%- HTML.escape(item.complexity.description) -%]</td> |
|
7 |
<td>[%- HTML.escape(item.risk.description) -%]</td> |
|
8 |
[%- IF !item.children.size -%] |
|
9 |
<td align="right" nowrap>[%- P.format_man_days(item.time_estimation, skip_zero=1) -%]</td> |
|
10 |
<td align="right" nowrap>[%- LxERP.format_amount(item.time_estimation * SELF.requirement_spec.hourly_rate, 2) -%] EUR</td> |
|
11 |
[%- ELSE -%] |
|
12 |
<td> </td> |
|
13 |
<td> </td> |
|
14 |
[%- END -%] |
|
15 |
</tr> |
|
16 |
|
|
17 |
[%- IF item.children.size -%] |
|
18 |
[%- FOREACH child = item.children -%] |
|
19 |
[%- INCLUDE 'requirement_spec/_show_time_and_cost_estimate_item.html' |
|
20 |
item = child |
|
21 |
level = level + 1 -%] |
|
22 |
[%- END -%] |
|
23 |
|
|
24 |
<tr class="listrow subtotal"> |
|
25 |
<td style="padding-left: [%- (level + 1) * 50 -%]px" colspan="3">[%- LxERP.t8("Sum for #1", item.fb_number) -%]:</td> |
|
26 |
<td align="right" nowrap>[%- P.format_man_days(item.time_estimation, skip_zero=1) -%]</td> |
|
27 |
<td align="right" nowrap>[%- LxERP.format_amount(item.time_estimation * SELF.requirement_spec.hourly_rate, 2) -%] EUR</td> |
|
28 |
</tr> |
|
29 |
[%- END -%] |
templates/webpages/requirement_spec/show.html | ||
---|---|---|
10 | 10 |
<ul> |
11 | 11 |
<li><a href="#function-blocks-tab">[%- LxERP.t8("Content") %]</a></li> |
12 | 12 |
<li><a href="controller.pl?action=RequirementSpec/ajax_edit&id=[% HTML.url(SELF.requirement_spec.id) %]">[%- LxERP.t8("Basic settings") %]</a></li> |
13 |
<li><a href="controller.pl?action=RequirementSpec/ajax_show_time_and_cost_estimate&id=[% HTML.url(SELF.requirement_spec.id) %]">[%- LxERP.t8("Time and cost estimate") %]</a></li> |
|
13 | 14 |
</ul> |
14 | 15 |
|
15 | 16 |
<div id="function-blocks-tab"> |
Auch abrufbar als: Unified diff
Pflichtenhefte: Zeit- und Kostenschätzungsmaske