Pflichtenhefte: Basisdaten verwalten, Such- und Listfunktion
authorMoritz Bunkus <m.bunkus@linet-services.de>
Fri, 1 Feb 2013 16:08:32 +0000 (17:08 +0100)
committerMoritz Bunkus <m.bunkus@linet-services.de>
Tue, 1 Apr 2014 11:02:24 +0000 (13:02 +0200)
13 files changed:
SL/Controller/RequirementSpec.pm [new file with mode: 0644]
SL/DB/Manager/RequirementSpec.pm [new file with mode: 0644]
SL/DB/Project.pm
SL/DB/RequirementSpec.pm
SL/Presenter/CustomerVendor.pm
SL/Presenter/Project.pm
css/requirement_spec.css [new file with mode: 0644]
locale/de/all
menus/erp.ini
templates/webpages/requirement_spec/_filter.html [new file with mode: 0644]
templates/webpages/requirement_spec/form.html [new file with mode: 0644]
templates/webpages/requirement_spec/report_bottom.html [new file with mode: 0644]
templates/webpages/requirement_spec/report_top.html [new file with mode: 0644]

diff --git a/SL/Controller/RequirementSpec.pm b/SL/Controller/RequirementSpec.pm
new file mode 100644 (file)
index 0000000..b865d29
--- /dev/null
@@ -0,0 +1,237 @@
+package SL::Controller::RequirementSpec;
+
+use strict;
+
+use parent qw(SL::Controller::Base);
+
+use SL::Controller::Helper::GetModels;
+use SL::Controller::Helper::Paginated;
+use SL::Controller::Helper::Sorted;
+use SL::Controller::Helper::ParseFilter;
+use SL::Controller::Helper::ReportGenerator;
+use SL::DB::Customer;
+use SL::DB::Project;
+use SL::DB::RequirementSpecStatus;
+use SL::DB::RequirementSpecType;
+use SL::DB::RequirementSpec;
+use SL::Helper::Flash;
+use SL::Locale::String;
+
+use Rose::Object::MakeMethods::Generic
+(
+ scalar => [ qw(requirement_spec customers projects types statuses db_args flat_filter is_template) ],
+);
+
+__PACKAGE__->run_before('setup');
+__PACKAGE__->run_before('load_requirement_spec',      only => [ qw(    edit        update destroy) ]);
+__PACKAGE__->run_before('load_select_options',        only => [ qw(new edit create update list) ]);
+__PACKAGE__->run_before('load_search_select_options', only => [ qw(                       list) ]);
+
+__PACKAGE__->get_models_url_params('flat_filter');
+__PACKAGE__->make_paginated(
+  MODEL         => 'RequirementSpec',
+  PAGINATE_ARGS => 'db_args',
+  ONLY          => [ qw(list) ],
+);
+
+__PACKAGE__->make_sorted(
+  MODEL         => 'RequirementSpec',
+  ONLY          => [ qw(list) ],
+
+  DEFAULT_BY    => 'customer',
+  DEFAULT_DIR   => 1,
+
+  customer      => t8('Customer'),
+  title         => t8('Title'),
+  type          => t8('Requirement Spec Type'),
+  status        => t8('Requirement Spec Status'),
+  projectnumber => t8('Project Number'),
+);
+
+#
+# actions
+#
+
+sub action_list {
+  my ($self) = @_;
+
+  $self->setup_db_args_from_filter;
+  $self->flat_filter({ map { $_->{key} => $_->{value} } $::form->flatten_variables('filter') });
+
+  $self->prepare_report;
+
+  my $requirement_specs = $self->get_models(%{ $self->db_args });
+
+  $self->report_generator_list_objects(report => $self->{report}, objects => $requirement_specs);
+}
+
+sub action_new {
+  my ($self) = @_;
+
+  $self->{requirement_spec} = SL::DB::RequirementSpec->new;
+  $self->render('requirement_spec/form', title => t8('Create a new requirement spec'));
+}
+
+sub action_edit {
+  my ($self) = @_;
+  $self->render('requirement_spec/form', title => t8('Edit requirement spec'));
+}
+
+sub action_create {
+  my ($self) = @_;
+
+  $self->{requirement_spec} = SL::DB::RequirementSpec->new;
+  $self->create_or_update;
+}
+
+sub action_update {
+  my ($self) = @_;
+  $self->create_or_update;
+}
+
+sub action_destroy {
+  my ($self) = @_;
+
+  if (eval { $self->{requirement_spec}->delete; 1; }) {
+    flash_later('info',  t8('The requirement spec has been deleted.'));
+  } else {
+    flash_later('error', t8('The requirement spec is in use and cannot be deleted.'));
+  }
+
+  $self->redirect_to(action => 'list');
+}
+
+sub action_reorder {
+  my ($self) = @_;
+
+  SL::DB::RequirementSpec->reorder_list(@{ $::form->{requirement_spec_id} || [] });
+
+  $self->render('1;', { type => 'js', inline => 1 });
+}
+
+#
+# filters
+#
+
+sub setup {
+  my ($self) = @_;
+
+  $::auth->assert('config');
+  $::request->{layout}->use_stylesheet("requirement_spec.css");
+  $self->is_template($::form->{is_template} ? 1 : 0);
+
+  return 1;
+}
+
+#
+# helpers
+#
+
+sub create_or_update {
+  my $self   = shift;
+  my $is_new = !$self->{requirement_spec}->id;
+  my $params = delete($::form->{requirement_spec}) || { };
+  my $title  = $is_new ? t8('Create a new requirement spec') : t8('Edit requirement spec');
+
+  $self->{requirement_spec}->assign_attributes(%{ $params });
+
+  my @errors = $self->{requirement_spec}->validate;
+
+  if (@errors) {
+    flash('error', @errors);
+    $self->render('requirement_spec/form', title => $title);
+    return;
+  }
+
+  $self->{requirement_spec}->save;
+
+  flash_later('info', $is_new ? t8('The requirement spec has been created.') : t8('The requirement spec has been saved.'));
+  $self->redirect_to(action => 'list');
+}
+
+sub load_requirement_spec {
+  my ($self) = @_;
+  $self->{requirement_spec} = SL::DB::RequirementSpec->new(id => $::form->{id})->load;
+}
+
+sub load_select_options {
+  my ($self) = @_;
+
+  my @filter = ('!obsolete' => 1);
+  if ($self->requirement_spec && $self->requirement_spec->customer_id) {
+    @filter = ( or => [ @filter, id => $self->requirement_spec->customer_id ] );
+  }
+
+  $self->customers(SL::DB::Manager::Customer->get_all_sorted(where => \@filter));
+  $self->statuses( SL::DB::Manager::RequirementSpecStatus->get_all_sorted);
+  $self->types(    SL::DB::Manager::RequirementSpecType->get_all_sorted);
+}
+
+sub load_search_select_options {
+  my ($self) = @_;
+
+  $self->projects(SL::DB::Manager::Project->get_all_sorted);
+}
+
+sub setup_db_args_from_filter {
+  my ($self) = @_;
+
+  $self->{filter} = {};
+  my %args = parse_filter(
+    $::form->{filter},
+    with_objects => [ 'customer', 'type', 'status', 'project' ],
+    launder_to   => $self->{filter},
+  );
+
+  $args{where} = [
+    and => [
+      @{ $args{where} || [] },
+      is_template => $self->is_template
+    ]];
+
+  $self->db_args(\%args);
+}
+
+sub prepare_report {
+  my ($self)      = @_;
+
+  my $callback    = $self->get_callback;
+
+  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 %column_defs = (
+    title         => { obj_link => sub { $self->url_for(action => 'edit', id => $_[0]->id, callback => $callback) } },
+    customer      => { raw_data => sub { $self->presenter->customer($_[0]->customer, display => 'table-cell', callback => $callback) },
+                       sub      => sub { $_[0]->customer->name } },
+    projectnumber => { raw_data => sub { $self->presenter->project($_[0]->project, display => 'table-cell', callback => $callback) },
+                       sub      => sub { $_[0]->project_id ? $_[0]->project->projectnumber : '' } },
+    status        => { sub      => sub { $_[0]->status->description } },
+    type          => { sub      => sub { $_[0]->type->description } },
+  );
+
+  map { $column_defs{$_}->{text} ||= $::locale->text( $self->get_sort_spec->{$_}->{title} ) } keys %column_defs;
+
+  $report->set_options(
+    std_column_visibility => 1,
+    controller_class      => 'RequirementSpec',
+    output_format         => 'HTML',
+    raw_top_info_text     => $self->render('requirement_spec/report_top',    { output => 0 }),
+    raw_bottom_info_text  => $self->render('requirement_spec/report_bottom', { output => 0 }),
+    title                 => $::locale->text('Requirement Specs'),
+    allow_pdf_export      => 1,
+    allow_csv_export      => 1,
+  );
+  $report->set_columns(%column_defs);
+  $report->set_column_order(@columns);
+  $report->set_export_options(qw(list filter));
+  $report->set_options_from_form;
+  $self->set_report_generator_sort_options(report => $report, sortable_columns => \@sortable);
+
+  $self->disable_pagination if $report->{options}{output_format} =~ /^(pdf|csv)$/i;
+}
+
+1;
diff --git a/SL/DB/Manager/RequirementSpec.pm b/SL/DB/Manager/RequirementSpec.pm
new file mode 100644 (file)
index 0000000..daf9d6b
--- /dev/null
@@ -0,0 +1,28 @@
+package SL::DB::Manager::RequirementSpec;
+
+use strict;
+
+use SL::DB::Helper::Manager;
+use base qw(SL::DB::Helper::Manager);
+
+use SL::DB::Helper::Paginated;
+use SL::DB::Helper::Sorted;
+
+sub object_class { 'SL::DB::RequirementSpec' }
+
+__PACKAGE__->make_manager_methods;
+
+sub _sort_spec {
+  return (
+    default => [ 'title', 1 ],
+    columns => {
+      SIMPLE => 'ALL',
+      customer      => 'lower(customer.name)',
+      type          => 'type.position',
+      status        => 'status.position',
+      projectnumber => 'project.projectnumber',
+      map { ( $_ => "lower(requirement_specs.${_})" ) } qw(title),
+    });
+}
+
+1;
index 73390d8..3f3e88d 100644 (file)
@@ -48,6 +48,28 @@ sub is_projectnumber_unique {
   return !SL::DB::Manager::Project->get_first(where => \@filter);
 }
 
+sub full_description {
+  my ($self, %params) = @_;
+
+  $params{style} ||= 'both';
+  my $description;
+
+  if ($params{style} =~ m/number/) {
+    $description = $self->projectnumber;
+
+  } elsif ($params{style} =~ m/description/) {
+    $description = $self->description;
+
+  } else {
+    $description = $self->projectnumber;
+    if ($self->description && do { my $desc = quotemeta $self->description; $self->projectnumber !~ m/$desc/ }) {
+      $description .= ' (' . $self->description . ')';
+    }
+  }
+
+  return $description;
+}
+
 1;
 
 __END__
@@ -83,6 +105,31 @@ Returns trueish if the project number is not used for any other
 project in the database. Also returns trueish if no project number has
 been set yet.
 
+=item C<full_description %params>
+
+Returns a full description for the project which can consist of the
+project number, its description or both. This is determined by the
+parameter C<style> which defaults to C<both>:
+
+=over 2
+
+=item C<both>
+
+Returns the project's number followed by its description in
+parenthesis (e.g. "12345 (Secret Combinations)"). If the project's
+description is already part of the project's number then it will not
+be appended.
+
+=item C<projectnumber> (or simply C<number>)
+
+Returns only the project's number.
+
+=item C<projectdescription> (or simply C<description>)
+
+Returns only the project's description.
+
+=back
+
 =back
 
 =head1 AUTHOR
index 99bf116..31dcfe8 100644 (file)
@@ -3,6 +3,7 @@ package SL::DB::RequirementSpec;
 use strict;
 
 use SL::DB::MetaSetup::RequirementSpec;
+use SL::DB::Manager::RequirementSpec;
 use SL::Locale::String;
 
 __PACKAGE__->meta->add_relationship(
@@ -18,11 +19,10 @@ __PACKAGE__->meta->add_relationship(
   },
 );
 
-# Creates get_all, get_all_count, get_all_iterator, delete_all and update_all.
-__PACKAGE__->meta->make_manager_class;
-
 __PACKAGE__->meta->initialize;
 
+__PACKAGE__->before_save('_before_save_initialize_not_null_columns');
+
 sub validate {
   my ($self) = @_;
 
@@ -32,4 +32,13 @@ sub validate {
   return @errors;
 }
 
+sub _before_save_initialize_not_null_columns {
+  my ($self) = @_;
+
+  $self->previous_section_number(0) if !defined $self->previous_section_number;
+  $self->previous_fb_number(0)      if !defined $self->previous_fb_number;
+
+  return 1;
+}
+
 1;
index 6d24409..424f0d8 100644 (file)
@@ -26,6 +26,8 @@ sub _customer_vendor {
 
   croak "Unknown display type '$params{display}'" unless $params{display} =~ m/^(?:inline|table-cell)$/;
 
+  my $callback = $params{callback} ? '&callback=' . $::form->escape($params{callback}) : '';
+
   my $text = join '', (
     $params{no_link} ? '' : '<a href="controller.pl?action=CustomerVendor/edit&amp;db=' . $type . '&amp;id=' . $self->escape($cv->id) . '">',
     $self->escape($cv->name),
index 650995c..b8d20a0 100644 (file)
@@ -18,24 +18,11 @@ sub project {
 
   croak "Unknown display type '$params{display}'" unless $params{display} =~ m/^(?:inline|table-cell)$/;
 
-  $params{style} ||= 'both';
-  my $description;
-
-  if ($params{style} =~ m/number/) {
-    $description = $project->projectnumber;
-
-  } elsif ($params{style} =~ m/description/) {
-    $description = $project->description;
-
-  } else {
-    $description = $project->projectnumber;
-    if ($project->description && do { my $desc = quotemeta $project->description; $project->projectnumber !~ m/$desc/ }) {
-      $description .= ' (' . $project->description . ')';
-    }
-  }
+  my $description = $project->full_description(style => $params{style});
+  my $callback    = $params{callback} ? '&callback=' . $::form->escape($params{callback}) : '';
 
   my $text = join '', (
-    $params{no_link} ? '' : '<a href="controller.pl?action=Project/edit&amp;id=' . $self->escape($project->id) . '">',
+    $params{no_link} ? '' : '<a href="controller.pl?action=Project/edit&amp;id=' . $self->escape($project->id) . $callback . '">',
     $self->escape($description),
     $params{no_link} ? '' : '</a>',
   );
diff --git a/css/requirement_spec.css b/css/requirement_spec.css
new file mode 100644 (file)
index 0000000..19faf75
--- /dev/null
@@ -0,0 +1,4 @@
+input.rs_input_field, select.rs_input_field,
+table.rs_input_field input, table.rs_input_field select {
+  width: 300px;
+}
index c58c012..26fdc38 100755 (executable)
@@ -151,6 +151,7 @@ $self->{texts} = {
   'Add Quotation'               => 'Angebot erfassen',
   'Add RFQ'                     => 'Preisanfrage erfassen',
   'Add Request for Quotation'   => 'Anfrage erfassen',
+  'Add Requirement Spec'        => 'Neues Pflichtenheft erfassen',
   'Add Sales Delivery Order'    => 'Lieferschein (Verkauf) erfassen',
   'Add Sales Invoice'           => 'Rechnung erfassen',
   'Add Sales Order'             => 'Auftrag erfassen',
@@ -531,6 +532,7 @@ $self->{texts} = {
   'Create a new predefined text' => 'Einen neuen vordefinierten Textblock anlegen',
   'Create a new project'        => 'Neues Projekt anlegen',
   'Create a new project type'   => 'Einen neuen Projekttypen anlegen',
+  'Create a new requirement spec' => 'Ein neues Pflichtenheft anlegen',
   'Create a new requirement spec status' => 'Einen neuen Pflichtenheftstatus anlegen',
   'Create a new requirement spec type' => 'Einen neuen Pflichtenhefttypen anlegen',
   'Create a new risk level'     => 'Einen neuen Risikograd anlegen',
@@ -872,6 +874,7 @@ $self->{texts} = {
   'Edit project'                => 'Projekt bearbeiten',
   'Edit project #1'             => 'Projekt #1 bearbeiten',
   'Edit project type'           => 'Projekttypen bearbeiten',
+  'Edit requirement spec'       => 'Pflichtenheft bearbeiten',
   'Edit requirement spec status' => 'Pflichtenheftstatus bearbeiten',
   'Edit requirement spec type'  => 'Pflichtenhefttypen bearbeiten',
   'Edit risk level'             => 'Risikograd bearbeiten',
@@ -1096,6 +1099,7 @@ $self->{texts} = {
   'History Search Engine'       => 'Historien Suchmaschine',
   'Homepage'                    => 'Homepage',
   'Host'                        => 'Datenbankcomputer',
+  'Hourly Rate'                 => 'Stundensatz',
   'Hourly rate'                 => 'Stundensatz',
   'However, you can create a new part which will then be selected.' => 'Sie k&ouml;nnen jedoch einen neuen Artikel anlegen, der dann automatisch ausgew&auml;hlt wird.',
   'I'                           => 'I',
@@ -1805,8 +1809,11 @@ $self->{texts} = {
   'Requested execution date to' => 'Gewünschtes Ausführungsdatum bis',
   'Requests for Quotation'      => 'Preisanfragen',
   'Required by'                 => 'Lieferdatum',
+  'Requirement Spec Status'     => 'Pflichtenheftstatus',
   'Requirement Spec Statuses'   => 'Pflichtenheftstatus',
+  'Requirement Spec Type'       => 'Pflichtenhefttyp',
   'Requirement Spec Types'      => 'Pflichtenhefttypen',
+  'Requirement Specs'           => 'Pflichtenhefte',
   'Requirement specs'           => 'Pflichtenhefte',
   'Reset'                       => 'Zurücksetzen',
   'Result'                      => 'Ergebnis',
@@ -2288,6 +2295,10 @@ $self->{texts} = {
   'The project type is in use and cannot be deleted.' => 'Der Projekttyp wird verwendet und kann nicht gelöscht werden.',
   'The required information consists of the IBAN and the BIC.' => 'Die benötigten Informationen bestehen aus der IBAN und der BIC.',
   'The required information consists of the IBAN, the BIC, the mandator ID and the mandate\'s date of signature.' => 'Die benötigten Informationen bestehen aus IBAN, BIC, Mandanten-ID und dem Unterschriftsdatum des Mandates.',
+  'The requirement spec has been created.' => 'Das Pflichtenheft wurde angelegt.',
+  'The requirement spec has been deleted.' => 'Das Pflichtenheft wurde gelöscht.',
+  'The requirement spec has been saved.' => 'Das Pflichtenheft wurde gespeichert.',
+  'The requirement spec is in use and cannot be deleted.' => 'Das Pflichtenheft wird verwendet und kann nicht gelöscht werden.',
   'The requirement spec status has been created.' => 'Der Pflichtenheftstatus wurde angelegt.',
   'The requirement spec status has been deleted.' => 'Der Pflichtenheftstatus wurde gelöscht.',
   'The requirement spec status has been saved.' => 'Der Pflichtenheftstatus wurde gespeichert.',
index a51b056..5f73f1a 100644 (file)
@@ -124,6 +124,10 @@ ACCESS=dunning_edit
 module=dn.pl
 action=add
 
+[AR--Add Requirement Spec]
+module=controller.pl
+action=RequirementSpec/new
+
 [AR--Reports]
 module=menu.pl
 action=acc_menu
@@ -164,6 +168,10 @@ ACCESS=dunning_edit
 module=dn.pl
 action=search
 
+[AR--Reports--Requirement Specs]
+module=controller.pl
+action=RequirementSpec/list
+
 [AR--Reports--Delivery Plan]
 ACCESS=delivery_plan
 module=controller.pl
diff --git a/templates/webpages/requirement_spec/_filter.html b/templates/webpages/requirement_spec/_filter.html
new file mode 100644 (file)
index 0000000..33fc863
--- /dev/null
@@ -0,0 +1,50 @@
+[%- USE HTML %][%- USE L %][%- USE LxERP %]
+
+<div class="filter_toggle">
+ <a href="#" onClick="javascript:$('.filter_toggle').toggle()">[% LxERP.t8("Show Filter") %]</a>
+</div>
+
+<div class="filter_toggle" style="display:none">
+ <a href="#" onClick="javascript:$('.filter_toggle').toggle()">[% LxERP.t8("Hide Filter") %]</a>
+
+ <form method="post" action="controller.pl">
+
+  <p>
+   <table class="rs_input_field">
+    <tr>
+     <th align="right">[% LxERP.t8("Title") %]</th>
+     <td>[% L.input_tag('filter.title:substr::ilike', filter.title_substr__ilike) %]</td>
+    </tr>
+
+    <tr>
+     <th align="right">[% LxERP.t8("Customer") %]</th>
+     <td>[% L.input_tag('filter.customer.name:substr::ilike', filter.customer.name_substr__ilike) %]</td>
+    </tr>
+
+    <tr>
+     <th align="right">[% LxERP.t8("Customer Number") %]</th>
+     <td>[% L.input_tag('filter.customer.customernumber:substr::ilike', filter.customer.customernumber_substr__ilike) %]</td>
+    </tr>
+
+    <tr>
+     <th align="right">[% LxERP.t8("Requirement Spec Type") %]</th>
+     <td>[% L.select_tag('filter.type_id', SELF.types, default=filter.type_id, title_key="description", with_empty=1) %]</td>
+    </tr>
+
+    <tr>
+     <th align="right">[% LxERP.t8("Requirement Spec Status") %]</th>
+     <td>[% L.select_tag('filter.status_id', SELF.statuses, default=filter.status_id, title_key="description", with_empty=1) %]</td>
+    </tr>
+
+    <tr>
+     <th align="right">[% LxERP.t8("Project") %]</th>
+     <td>[% L.select_tag('filter.project_id', SELF.projects, default=filter.project_id, title_key="full_description", with_empty=1) %]</td>
+    </tr>
+   </table>
+  </p>
+
+  [% L.hidden_tag("action", "RequirementSpec/list") %]
+
+  <p>[% L.submit_tag("dummy", LxERP.t8("Continue")) %]</p>
+ </form>
+</div>
diff --git a/templates/webpages/requirement_spec/form.html b/templates/webpages/requirement_spec/form.html
new file mode 100644 (file)
index 0000000..e038c3f
--- /dev/null
@@ -0,0 +1,62 @@
+[% USE HTML %][% USE L %][% USE LxERP %]
+
+ <form method="post" action="controller.pl">
+  <div class="listtop">[% FORM.title %]</div>
+
+[%- INCLUDE 'common/flash.html' %]
+
+  <table class="rs_input_field">
+   <tr>
+    <td>[% LxERP.t8("Title") %]</td>
+    <td>[% L.input_tag("requirement_spec.title", SELF.requirement_spec.description) %]</td>
+   </tr>
+
+   <tr>
+    <td>[% LxERP.t8("Requirement Spec Type") %]</td>
+    <td>[% L.select_tag("requirement_spec.type_id",  SELF.types, default=SELF.requirement_spec.type_id, title_key="description") %]</td>
+   </tr>
+
+   <tr>
+    <td>[% LxERP.t8("Requirement Spec Status") %]</td>
+    <td>[% L.select_tag("requirement_spec.status_id",  SELF.statuses, default=SELF.requirement_spec.status_id, title_key="description") %]</td>
+   </tr>
+
+   <tr>
+    <td>[% LxERP.t8("Customer") %]</td>
+    <td>[% L.select_tag("requirement_spec.customer_id",  SELF.customers, default=SELF.requirement_spec.customer_id, title_key="name", id="customer_id") %]</td>
+   </tr>
+
+   <tr>
+    <td>[% LxERP.t8("Hourly Rate") %]</td>
+    <td>[% L.input_tag("requirement_spec.hourly_rate_as_number", SELF.requirement_spec.hourly_rate_as_number, id="hourly_rate") %]</td>
+   </tr>
+
+  </table>
+
+  <p>
+   [% L.hidden_tag("id", SELF.requirement_spec.id) %]
+   [% L.hidden_tag("action", "RequirementSpec/dispatch") %]
+   [% L.submit_tag("action_" _ (SELF.requirement_spec.id ? "update" : "create"), LxERP.t8('Save')) %]
+   [%- IF SELF.requirement_spec.id %]
+    [% L.submit_tag("action_destroy", LxERP.t8('Delete'), confirm=LxERP.t8('Do you really want to delete this object?')) %]
+   [%- END %]
+   <a href="[% SELF.url_for(action="list") %]">[% LxERP.t8('Abort') %]</a>
+  </p>
+ </form>
+
+ <script type="text/javascript">
+  <!--
+    function on_customer_changed() {
+      $.ajax({
+        url: 'controller.pl?action=Customer/get_hourly_rate',
+        dataType: "json",
+        data: { id: $("#customer_id").attr('value') },
+        success: function(data) { if (data["hourly_rate"] > 0) $("#hourly_rate").attr("value", data["hourly_rate_formatted"]); }
+      });
+    }
+
+    $(document).ready(function() {
+      $("#customer_id").change(on_customer_changed);
+    });
+  -->
+ </script>
diff --git a/templates/webpages/requirement_spec/report_bottom.html b/templates/webpages/requirement_spec/report_bottom.html
new file mode 100644 (file)
index 0000000..79e1523
--- /dev/null
@@ -0,0 +1,2 @@
+[% USE L %]
+[%- L.paginate_controls %]
diff --git a/templates/webpages/requirement_spec/report_top.html b/templates/webpages/requirement_spec/report_top.html
new file mode 100644 (file)
index 0000000..507bfae
--- /dev/null
@@ -0,0 +1,3 @@
+[%- USE L %]
+[%- PROCESS "requirement_spec/_filter.html" filter=SELF.filter %]
+ <hr>