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);
 }
 
   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__
 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.
 
 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
 =back
 
 =head1 AUTHOR
index 99bf116..31dcfe8 100644 (file)
@@ -3,6 +3,7 @@ package SL::DB::RequirementSpec;
 use strict;
 
 use SL::DB::MetaSetup::RequirementSpec;
 use strict;
 
 use SL::DB::MetaSetup::RequirementSpec;
+use SL::DB::Manager::RequirementSpec;
 use SL::Locale::String;
 
 __PACKAGE__->meta->add_relationship(
 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__->meta->initialize;
 
+__PACKAGE__->before_save('_before_save_initialize_not_null_columns');
+
 sub validate {
   my ($self) = @_;
 
 sub validate {
   my ($self) = @_;
 
@@ -32,4 +32,13 @@ sub validate {
   return @errors;
 }
 
   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;
 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)$/;
 
 
   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),
   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)$/;
 
 
   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 '', (
 
   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>',
   );
     $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 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',
   '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 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',
   '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 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',
   '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',
   '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',
   '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',
   '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 Statuses'   => 'Pflichtenheftstatus',
+  'Requirement Spec Type'       => 'Pflichtenhefttyp',
   'Requirement Spec Types'      => 'Pflichtenhefttypen',
   'Requirement Spec Types'      => 'Pflichtenhefttypen',
+  'Requirement Specs'           => 'Pflichtenhefte',
   'Requirement specs'           => 'Pflichtenhefte',
   'Reset'                       => 'Zurücksetzen',
   'Result'                      => 'Ergebnis',
   '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 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.',
   '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
 
 module=dn.pl
 action=add
 
+[AR--Add Requirement Spec]
+module=controller.pl
+action=RequirementSpec/new
+
 [AR--Reports]
 module=menu.pl
 action=acc_menu
 [AR--Reports]
 module=menu.pl
 action=acc_menu
@@ -164,6 +168,10 @@ ACCESS=dunning_edit
 module=dn.pl
 action=search
 
 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
 [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>