Pflichtenhefte: zusätzliche Artikel zuweisen und bearbeiten können
authorMoritz Bunkus <m.bunkus@linet-services.de>
Wed, 30 Jul 2014 12:40:06 +0000 (14:40 +0200)
committerMoritz Bunkus <m.bunkus@linet-services.de>
Tue, 26 Aug 2014 12:17:56 +0000 (14:17 +0200)
16 files changed:
SL/Controller/RequirementSpecOrder.pm
SL/Controller/RequirementSpecPart.pm [new file with mode: 0644]
SL/DB/Helper/ALL.pm
SL/DB/Helper/Mappings.pm
SL/DB/Manager/RequirementSpecPart.pm [new file with mode: 0644]
SL/DB/MetaSetup/RequirementSpecPart.pm [new file with mode: 0644]
SL/DB/RequirementSpec.pm
SL/DB/RequirementSpecPart.pm [new file with mode: 0644]
js/locale/de.js
js/requirement_spec.js
locale/de/all
sql/Pg-upgrade2/requirement_spec_parts.sql [new file with mode: 0644]
templates/webpages/requirement_spec/show.html
templates/webpages/requirement_spec_part/_edit.html [new file with mode: 0644]
templates/webpages/requirement_spec_part/_part.html [new file with mode: 0644]
templates/webpages/requirement_spec_part/show.html [new file with mode: 0644]

index 8c33d6f..7671ffb 100644 (file)
@@ -70,10 +70,7 @@ sub action_create {
   $order->db->with_transaction(sub {
     $order->save;
 
-    $self->requirement_spec->orders(
-      @{ $self->requirement_spec->orders },
-      SL::DB::RequirementSpecOrder->new(order => $order, version => $self->requirement_spec->version)
-    );
+    $self->requirement_spec->add_orders(SL::DB::RequirementSpecOrder->new(order => $order, version => $self->requirement_spec->version));
     $self->requirement_spec->save;
 
     $self->requirement_spec->link_to_record($order);
@@ -247,11 +244,6 @@ sub init_all_parts_time_unit {
 # helpers
 #
 
-sub load_parts_for_sections {
-  my ($self, %params) = @_;
-
-}
-
 sub create_order_item {
   my ($self, %params) = @_;
 
diff --git a/SL/Controller/RequirementSpecPart.pm b/SL/Controller/RequirementSpecPart.pm
new file mode 100644 (file)
index 0000000..b9df7f5
--- /dev/null
@@ -0,0 +1,119 @@
+package SL::Controller::RequirementSpecPart;
+
+use strict;
+
+use parent qw(SL::Controller::Base);
+
+use Carp;
+use List::MoreUtils qw(any);
+
+use SL::ClientJS;
+use SL::DB::Customer;
+use SL::DB::Project;
+use SL::DB::RequirementSpec;
+use SL::DB::RequirementSpecPart;
+use SL::Helper::Flash;
+use SL::Locale::String;
+
+use Rose::Object::MakeMethods::Generic
+(
+  'scalar --get_set_init' => [ qw(requirement_spec js) ],
+);
+
+__PACKAGE__->run_before('check_auth');
+
+#
+# actions
+#
+
+sub action_show {
+  my ($self, %params) = @_;
+
+  $self->render('requirement_spec_part/show', { layout => 0 });
+}
+
+sub action_ajax_edit {
+  my ($self, %params) = @_;
+
+  my $html = $self->render('requirement_spec_part/_edit', { output => 0 });
+
+  $self->js
+   ->hide('#additional_parts_list_container')
+   ->after('#additional_parts_list_container', $html)
+   ->on('#edit_additional_parts_form INPUT[type=text]', 'keydown', 'kivi.requirement_spec.additional_parts_input_key_down')
+   ->focus('#additional_parts_add_part_id_name')
+   ->run('kivi.requirement_spec.prepare_edit_additional_parts_form')
+   ->reinit_widgets
+   ->render;
+}
+
+sub action_ajax_add {
+  my ($self)  = @_;
+
+  my $part      = SL::DB::Part->new(id => $::form->{part_id})->load(with_objects => [ qw(unit_obj) ]);
+  my $rs_part   = SL::DB::RequirementSpecPart->new(
+    part        => $part,
+    qty         => 1,
+    unit        => $part->unit_obj,
+    description => $part->description,
+  );
+  my $row       = $self->render('requirement_spec_part/_part', { output => 0 }, part => $rs_part);
+
+  $self->js
+   ->val(  '#additional_parts_add_part_id',      '')
+   ->val(  '#additional_parts_add_part_id_name', '')
+   ->focus('#additional_parts_add_part_id_name')
+   ->append('#edit_additional_parts_list tbody', $row)
+   ->hide('#edit_additional_parts_list_empty')
+   ->show('#edit_additional_parts_list')
+   ->render;
+}
+
+sub action_ajax_save {
+  my ($self) = @_;
+
+  my $db = $self->requirement_spec->db;
+  $db->do_transaction(sub {
+    # Make Emacs happy
+    1;
+    my $parts    = $::form->{additional_parts} || [];
+    my $position = 1;
+    $_->{position} = $position++ for @{ $parts };
+
+    $self->requirement_spec->update_attributes(parts => $parts)->load;
+
+    1;
+  }) or do {
+    return $self->js->error(t8('Saving failed. Error message from the database: #1', $db->error))->render;
+  };
+
+  my $html = $self->render('requirement_spec_part/show', { output => 0 }, initially_hidden => !!$::form->{keep_open});
+
+  $self->js
+    ->replaceWith('#additional_parts_list_container', $html)
+    ->action_if(!$::form->{keep_open}, 'remove', '#additional_parts_form_container')
+    ->render;
+}
+
+#
+# filters
+#
+
+sub check_auth {
+  my ($self, %params) = @_;
+  $::auth->assert('requirement_spec_edit');
+}
+
+#
+# helpers
+#
+
+sub init_js { SL::ClientJS->new(controller => $_[0]) }
+
+sub init_requirement_spec {
+  SL::DB::RequirementSpec->new(id => $::form->{requirement_spec_id})->load(
+    with_objects => [ qw(parts parts.part parts.unit) ],
+  );
+}
+
+1;
index d0cb1cb..b60ddf6 100644 (file)
@@ -83,6 +83,7 @@ use SL::DB::RequirementSpecComplexity;
 use SL::DB::RequirementSpecDependency;
 use SL::DB::RequirementSpecItem;
 use SL::DB::RequirementSpecOrder;
+use SL::DB::RequirementSpecPart;
 use SL::DB::RequirementSpecPicture;
 use SL::DB::RequirementSpecPredefinedText;
 use SL::DB::RequirementSpecRisk;
index 5acc1c7..9695077 100644 (file)
@@ -163,6 +163,7 @@ my %kivitendo_package_names = (
   requirement_spec_item_dependencies   => 'RequirementSpecDependency',
   requirement_spec_items               => 'RequirementSpecItem',
   requirement_spec_orders              => 'RequirementSpecOrder',
+  requirement_spec_parts               => 'RequirementSpecPart',
   requirement_spec_pictures            => 'RequirementSpecPicture',
   requirement_spec_predefined_texts    => 'RequirementSpecPredefinedText',
   requirement_spec_risks               => 'RequirementSpecRisk',
diff --git a/SL/DB/Manager/RequirementSpecPart.pm b/SL/DB/Manager/RequirementSpecPart.pm
new file mode 100644 (file)
index 0000000..aa82534
--- /dev/null
@@ -0,0 +1,15 @@
+# This file has been auto-generated only because it didn't exist.
+# Feel free to modify it at will; it will not be overwritten automatically.
+
+package SL::DB::Manager::RequirementSpecPart;
+
+use strict;
+
+use SL::DB::Helper::Manager;
+use base qw(SL::DB::Helper::Manager);
+
+sub object_class { 'SL::DB::RequirementSpecPart' }
+
+__PACKAGE__->make_manager_methods;
+
+1;
diff --git a/SL/DB/MetaSetup/RequirementSpecPart.pm b/SL/DB/MetaSetup/RequirementSpecPart.pm
new file mode 100644 (file)
index 0000000..b29ae42
--- /dev/null
@@ -0,0 +1,41 @@
+# This file has been auto-generated. Do not modify it; it will be overwritten
+# by rose_auto_create_model.pl automatically.
+package SL::DB::RequirementSpecPart;
+
+use strict;
+
+use base qw(SL::DB::Object);
+
+__PACKAGE__->meta->table('requirement_spec_parts');
+
+__PACKAGE__->meta->columns(
+  id                  => { type => 'serial', not_null => 1 },
+  description         => { type => 'text', not_null => 1 },
+  part_id             => { type => 'integer', not_null => 1 },
+  position            => { type => 'integer', not_null => 1 },
+  qty                 => { type => 'numeric', not_null => 1, precision => 15, scale => 5 },
+  requirement_spec_id => { type => 'integer', not_null => 1 },
+  unit_id             => { type => 'integer', not_null => 1 },
+);
+
+__PACKAGE__->meta->primary_key_columns([ 'id' ]);
+
+__PACKAGE__->meta->foreign_keys(
+  part => {
+    class       => 'SL::DB::Part',
+    key_columns => { part_id => 'id' },
+  },
+
+  requirement_spec => {
+    class       => 'SL::DB::RequirementSpec',
+    key_columns => { requirement_spec_id => 'id' },
+  },
+
+  unit => {
+    class       => 'SL::DB::Unit',
+    key_columns => { unit_id => 'id' },
+  },
+);
+
+1;
+;
index e5dae7e..2354440 100644 (file)
@@ -43,6 +43,11 @@ __PACKAGE__->meta->add_relationship(
     class          => 'SL::DB::RequirementSpecOrder',
     column_map     => { id => 'requirement_spec_id' },
   },
+  parts            => {
+    type           => 'one to many',
+    class          => 'SL::DB::RequirementSpecPart',
+    column_map     => { id => 'requirement_spec_id' },
+  },
 );
 
 __PACKAGE__->meta->initialize;
@@ -119,6 +124,14 @@ sub versioned_copies_sorted {
   return \@copies;
 }
 
+sub parts_sorted {
+  my ($self, @rest) = @_;
+
+  croak "This sub is not a writer" if @rest;
+
+  return [ sort { $a->position <=> $b->position } @{ $self->parts } ];
+}
+
 sub create_copy {
   my ($self, %params) = @_;
 
@@ -519,6 +532,11 @@ column in ascending order. If the C<output_position> parameter is
 given then only the text blocks belonging to that C<output_position>
 are returned.
 
+=item C<parts_sorted>
+
+Returns an array reference of additional parts sorted by their
+positional column in ascending order.
+
 =item C<validate>
 
 Validate values before saving. Returns list or human-readable error
diff --git a/SL/DB/RequirementSpecPart.pm b/SL/DB/RequirementSpecPart.pm
new file mode 100644 (file)
index 0000000..a511d0b
--- /dev/null
@@ -0,0 +1,11 @@
+package SL::DB::RequirementSpecPart;
+
+use strict;
+
+use SL::DB::MetaSetup::RequirementSpecPart;
+use SL::DB::Manager::RequirementSpecPart;
+use SL::DB::Helper::ActsAsList;
+
+__PACKAGE__->meta->initialize;
+
+1;
index 3b7e865..e00362d 100644 (file)
@@ -7,6 +7,7 @@ namespace("kivi").setupLocale({
 "Add section":"Abschnitt hinzufügen",
 "Add sub function block":"Unterfunktionsblock hinzufügen",
 "Add text block":"Textblock erfassen",
+"Additional articles actions":"Aktionen zu zusätzlichen Artikeln",
 "Are you sure?":"Sind Sie sicher?",
 "Basic settings actions":"Aktionen zu Grundeinstellungen",
 "Cancel":"Abbrechen",
@@ -44,6 +45,7 @@ namespace("kivi").setupLocale({
 "Paste template":"Vorlage einfügen",
 "Project link actions":"Projektverknüpfungs-Aktionen",
 "Quotations/Orders actions":"Aktionen für Angebote/Aufträge",
+"Remove article":"Artikel entfernen",
 "Requirement spec actions":"Pflichtenheftaktionen",
 "Requirement spec template actions":"Pflichtenheftvorlagen-Aktionen",
 "Revert to version":"Auf Version zurücksetzen",
index 5078971..b1d5ce8 100644 (file)
@@ -644,6 +644,121 @@ ns.revert_to_versioned_copy_ajax_call = function(key, opt) {
   return true;
 };
 
+// -------------------------------------------------------------------------
+// -------------------------- time/cost estimate ---------------------------
+// -------------------------------------------------------------------------
+
+ns.standard_time_cost_estimate_ajax_call = function(key, opt) {
+  if (key == 'cancel') {
+    if (confirm(kivi.t8('Do you really want to cancel?'))) {
+      $('#time_cost_estimate').show();
+      $('#time_cost_estimate_form_container').remove();
+    }
+    return true;
+  }
+
+  var add_data = '';
+  if (key == 'save_keep_open') {
+    key      = 'save';
+    add_data = 'keep_open=1&';
+  }
+
+  var data = "action=RequirementSpec/ajax_" + key + "_time_and_cost_estimate&" + add_data;
+
+  if (key == 'save')
+    data += $('#edit_time_cost_estimate_form').serialize()
+         +  '&' + $('#current_content_type').serialize()
+         +  '&' + $('#current_content_id').serialize();
+  else
+    data += 'id=' + encodeURIComponent($('#requirement_spec_id').val());
+
+  $.post("controller.pl", data, kivi.eval_json_result);
+
+  return true;
+};
+
+ns.time_cost_estimate_input_key_down = function(event) {
+  if(event.keyCode == 13) {
+    event.preventDefault();
+    ns.standard_time_cost_estimate_ajax_call('save');
+    return false;
+  }
+};
+
+// -------------------------------------------------------------------------
+// -------------------------- additional parts -----------------------------
+// -------------------------------------------------------------------------
+
+ns.standard_additional_parts_ajax_call = function(key, opt) {
+  var add_data = '';
+  if (key == 'save_keep_open') {
+    key      = 'save';
+    add_data = 'keep_open=1&';
+  }
+
+  var data = "action=RequirementSpecPart/ajax_" + key + "&" + add_data + 'requirement_spec_id=' + encodeURIComponent($('#requirement_spec_id').val()) + '&';
+
+  if (key == 'save')
+    data += $('#edit_additional_parts_form').serialize();
+
+  $.post("controller.pl", data, kivi.eval_json_result);
+
+  return true;
+};
+
+ns.prepare_edit_additional_parts_form = function() {
+  $("#edit_additional_parts_list tbody").sortable({
+    distance: 5,
+    handle:   '.dragdrop',
+    helper:   function(event, ui) {
+      ui.children().each(function() {
+        $(this).width($(this).width());
+      });
+      return ui;
+    }
+
+  });
+};
+
+ns.cancel_edit_additional_parts_form = function() {
+  if (confirm(kivi.t8('Do you really want to cancel?'))) {
+    $('#additional_parts_list_container').show();
+    $('#additional_parts_form_container').remove();
+  }
+  return true;
+};
+
+ns.additional_parts_input_key_down = function(event) {
+  if(event.keyCode == 13) {
+    event.preventDefault();
+    ns.standard_additional_parts_ajax_call('save');
+    return false;
+  }
+};
+
+ns.add_additional_part = function() {
+  var part_id = $('#additional_parts_add_part_id').val();
+  if (!part_id || (part_id == ''))
+    return false;
+
+  var rspec_id = $('#requirement_spec_id').val();
+  var data     = 'action=RequirementSpecPart/ajax_add&requirement_spec_id=' + encodeURIComponent(rspec_id) + '&part_id=' + encodeURIComponent(part_id);
+
+  $.post("controller.pl", data, kivi.eval_json_result);
+
+  return true;
+};
+
+ns.delete_additional_part = function(key, opt) {
+  opt.$trigger.remove();
+  if (!$('#edit_additional_parts_list tbody tr').size()) {
+   $('#edit_additional_parts_list_empty').show();
+   $('#edit_additional_parts_list').hide();
+  }
+
+  return true;
+};
+
 // -------------------------------------------------------------------------
 // ------------------------------- tab widget ------------------------------
 // -------------------------------------------------------------------------
@@ -651,8 +766,9 @@ var content_div_ids_for_tab_headers = {
     'tab-header-function-block':     'function-blocks-tab'
   , 'tab-header-basic-settings':     'ui-tabs-1'
   , 'tab-header-time-cost-estimate': 'ui-tabs-2'
-  , 'tab-header-versions':           'ui-tabs-3'
-  , 'tab-header-quotations-orders':  'ui-tabs-4'
+  , 'tab-header-additional-parts':   'ui-tabs-3'
+  , 'tab-header-versions':           'ui-tabs-4'
+  , 'tab-header-quotations-orders':  'ui-tabs-5'
 };
 
 ns.tabs_before_activate = function(event, ui) {
@@ -804,6 +920,35 @@ ns.create_context_menus = function(is_template) {
     }, general_actions)
   });
 
+  $.contextMenu({
+    selector: '.additional-parts-context-menu',
+    items:    $.extend({
+        heading: { name: kivi.t8('Additional articles actions'), className: 'context-menu-heading' }
+      , edit:    { name: kivi.t8('Edit'), icon: "edit", callback: kivi.requirement_spec.standard_additional_parts_ajax_call }
+    }, general_actions)
+  });
+
+  var additional_parts_actions = {
+      save:           { name: kivi.t8('Save'),               icon: "save",  callback: kivi.requirement_spec.standard_additional_parts_ajax_call }
+    , save_keep_open: { name: kivi.t8('Save and keep open'), icon: "save",  callback: kivi.requirement_spec.standard_additional_parts_ajax_call }
+    , cancel:         { name: kivi.t8('Cancel'),             icon: "close",  callback: kivi.requirement_spec.cancel_edit_additional_parts_form }
+  };
+
+  $.contextMenu({
+    selector: '.edit-additional-parts-context-menu',
+    items:    $.extend({
+        heading:        { name: kivi.t8('Additional articles actions'), className: 'context-menu-heading' }
+    }, additional_parts_actions, general_actions)
+  });
+
+  $.contextMenu({
+    selector: '.edit-additional-parts-row-context-menu',
+    items:    $.extend({
+        heading:        { name: kivi.t8('Additional articles actions'), className: 'context-menu-heading' }
+      , delete:         { name: kivi.t8('Remove article'),     icon: "delete", callback: kivi.requirement_spec.delete_additional_part }
+    }, additional_parts_actions, general_actions)
+  });
+
   $.contextMenu({
     selector: '.quotations-and-orders-context-menu,.quotations-and-orders-order-context-menu',
     items:    $.extend({
index 2c31b40..d6664b5 100755 (executable)
@@ -176,6 +176,7 @@ $self->{texts} = {
   'Add new currency'            => 'Neue Währung hinzufügen',
   'Add new custom variable'     => 'Neue benutzerdefinierte Variable erfassen',
   'Add note'                    => 'Notiz erfassen',
+  'Add part'                    => 'Artikel hinzufügen',
   'Add picture'                 => 'Bild hinzufügen',
   'Add picture to text block'   => 'Bild dem Textblock hinzufügen',
   'Add section'                 => 'Abschnitt hinzufügen',
@@ -185,6 +186,8 @@ $self->{texts} = {
   'Add unit'                    => 'Einheit hinzuf&uuml;gen',
   'Added sections and function blocks: #1' => 'Hinzugefügte Abschnitte und Funktionsblöcke: #1',
   'Added text blocks: #1'       => 'Hinzugefügte Textblöcke: #1',
+  'Additional articles'         => 'Zusätzliche Artikel',
+  'Additional articles actions' => 'Aktionen zu zusätzlichen Artikeln',
   'Address'                     => 'Adresse',
   'Admin'                       => 'Administration',
   'Administration'              => 'Administration',
@@ -933,6 +936,7 @@ $self->{texts} = {
   'Edit Vendor Invoice'         => 'Einkaufsrechnung bearbeiten',
   'Edit Warehouse'              => 'Lager bearbeiten',
   'Edit acceptance status'      => 'Abnahmestatus bearbeiten',
+  'Edit additional articles'    => 'Zusätzliche Artikel bearbeiten',
   'Edit article/section assignments' => 'Zuweisung Artikel/Abschnitte bearbeiten',
   'Edit assignment of articles to sections' => 'Zuweisung Artikel zu Abschnitten bearbeiten',
   'Edit background job'         => 'Hintergrund-Job bearbeiten',
@@ -1520,6 +1524,7 @@ $self->{texts} = {
   'No Vendor was found matching the search parameters.' => 'Zu dem Suchbegriff wurde kein Händler gefunden',
   'No acceptance statuses has been created yet.' => 'Es wurde noch kein Abnahmestatus angelegt.',
   'No action defined.'          => 'Keine Aktion definiert.',
+  'No articles have been added yet.' => 'Es wurden noch keine Artikel hinzugefügt.',
   'No background job has been created yet.' => 'Es wurden noch keine Hintergrund-Jobs angelegt.',
   'No bank information has been entered in this customer\'s master data entry. You cannot create bank collections unless you enter bank information.' => 'Für diesen Kunden wurden in seinen Stammdaten keine Kontodaten hinterlegt. Solange dies nicht geschehen ist, können Sie keine Überweisungen für den Lieferanten anlegen.',
   'No bank information has been entered in this vendor\'s master data entry. You cannot create bank transfers unless you enter bank information.' => 'Für diesen Lieferanten wurden in seinen Stammdaten keine Kontodaten hinterlegt. Solange dies nicht geschehen ist, können Sie keine Überweisungen für den Lieferanten anlegen.',
@@ -1938,6 +1943,7 @@ $self->{texts} = {
   'Removal qty'                 => 'Entnahmemenge',
   'Remove'                      => 'Entfernen',
   'Remove Draft'                => 'Entwurf l&ouml;schen',
+  'Remove article'              => 'Artikel entfernen',
   'Remove draft when posting'   => 'Entwurf beim Buchen l&ouml;schen',
   'Removed sections and function blocks: #1' => 'Entfernte Abschnitte und Funktionsblöcke: #1',
   'Removed spoolfiles!'         => 'Druckdateien entfernt!',
diff --git a/sql/Pg-upgrade2/requirement_spec_parts.sql b/sql/Pg-upgrade2/requirement_spec_parts.sql
new file mode 100644 (file)
index 0000000..723eab6
--- /dev/null
@@ -0,0 +1,17 @@
+-- @tag: requirement_spec_parts
+-- @description: Artikelzuweisung zu Pflichtenheften
+-- @depends: release_3_1_0
+CREATE TABLE requirement_spec_parts (
+  id                  SERIAL         NOT NULL,
+  requirement_spec_id INTEGER        NOT NULL,
+  part_id             INTEGER        NOT NULL,
+  unit_id             INTEGER        NOT NULL,
+  qty                 NUMERIC(15, 5) NOT NULL,
+  description         TEXT           NOT NULL,
+  position            INTEGER        NOT NULL,
+
+  PRIMARY KEY (id),
+  FOREIGN KEY (requirement_spec_id) REFERENCES requirement_specs (id),
+  FOREIGN KEY (part_id)             REFERENCES parts             (id),
+  FOREIGN KEY (unit_id)             REFERENCES units             (id)
+);
index c41e87d..756fbc8 100644 (file)
@@ -12,6 +12,7 @@
   <li id="tab-header-function-block"><a href="#function-blocks-tab">[%- LxERP.t8("Content") %]</a></li>
   <li id="tab-header-basic-settings"><a href="controller.pl?action=RequirementSpec/ajax_show_basic_settings&id=[% HTML.url(SELF.requirement_spec.id) %]">[%- LxERP.t8("Basic settings") %]</a></li>
   <li id="tab-header-time-cost-estimate"><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>
+  <li id="tab-header-additional-parts"><a href="controller.pl?action=RequirementSpecPart/show&requirement_spec_id=[% HTML.url(SELF.requirement_spec.id) %]">[%- LxERP.t8("Additional articles") %]</a></li>
   [%- UNLESS SELF.requirement_spec.is_template %]
    <li id="tab-header-versions"><a href="controller.pl?action=RequirementSpecVersion/list&requirement_spec_id=[% HTML.url(SELF.requirement_spec.id) %]">[%- LxERP.t8("Versions") %]</a></li>
    <li id="tab-header-quotations-orders"><a href="[% SELF.url_for(controller='RequirementSpecOrder', action='list', requirement_spec_id=SELF.requirement_spec.id) %]">[%- LxERP.t8("Quotations and orders") %]</a></li>
diff --git a/templates/webpages/requirement_spec_part/_edit.html b/templates/webpages/requirement_spec_part/_edit.html
new file mode 100644 (file)
index 0000000..c1b2795
--- /dev/null
@@ -0,0 +1,38 @@
+[%- USE LxERP -%][%- USE L -%][%- USE P -%]
+[% SET parts = SELF.requirement_spec.parts_sorted %]
+
+<div id="additional_parts_form_container" class="edit-additional-parts-context-menu">
+
+ <h2>[% LxERP.t8("Edit additional articles") %]</h2>
+
+ <div>
+  [% LxERP.t8("Add part") %]:
+  [% P.part_picker('additional_parts_add_part_id', '', style="width: 300px") %]
+  [% L.button_tag('kivi.requirement_spec.add_additional_part()', LxERP.t8('Add part')) %]
+ </div>
+
+ <form method="post" id="edit_additional_parts_form">
+  <div id="edit_additional_parts_list_empty"[% IF parts.size %] style="display: none;"[% END %]>
+   [% LxERP.t8("No articles have been added yet.") %]
+  </div>
+
+  <table id="edit_additional_parts_list"[% IF !parts.size %] style="display: none;"[% END %]>
+   <thead>
+    <tr class="listheading">
+     <th></th>
+     <th>[%- LxERP.t8("Part Number") %]</th>
+     <th>[%- LxERP.t8("Description") %]</th>
+     <th>[%- LxERP.t8("Qty") %]</th>
+    </tr>
+   </thead>
+
+   <tbody>
+    [%- FOREACH part = parts %]
+     [%- INCLUDE 'requirement_spec_part/_part.html' part=part %]
+    [%- END %]
+   </tbody>
+  </table>
+
+   [% L.button_tag("kivi.requirement_spec.standard_additional_parts_ajax_call('save')", LxERP.t8("Save")) %]
+  </form>
+</div>
diff --git a/templates/webpages/requirement_spec_part/_part.html b/templates/webpages/requirement_spec_part/_part.html
new file mode 100644 (file)
index 0000000..6136de0
--- /dev/null
@@ -0,0 +1,14 @@
+[%- USE HTML -%][%- USE L -%][%- USE LxERP -%]
+<tr class="listrow edit-additional-parts-row-context-menu">
+ <td align="center">
+  [% L.hidden_tag("additional_parts[+].part_id", part.part.id) %]
+  [% L.hidden_tag("additional_parts[].id", part.id) %]
+  [% L.img_tag(src="image/updown.png", alt=LxERP.t8("reorder item"), class="dragdrop") %]
+ </td>
+ <td>[% HTML.escape(part.part.partnumber) %]</td>
+ <td>[% L.input_tag("additional_parts[].description", part.description, size="30") %]</td>
+ <td>
+  [% L.input_tag("additional_parts[].qty_as_number", part.qty_as_number, size="10") %]
+  [% L.select_tag("additional_parts[].unit_id", part.unit.convertible_units, title_key="name", default=part.unit.id) %]
+ </td>
+</tr>
diff --git a/templates/webpages/requirement_spec_part/show.html b/templates/webpages/requirement_spec_part/show.html
new file mode 100644 (file)
index 0000000..128c7e0
--- /dev/null
@@ -0,0 +1,31 @@
+[%- USE LxERP -%][%- USE L -%][%- USE P -%][%- USE HTML -%]
+[% SET parts = SELF.requirement_spec.parts_sorted %]
+
+<div id="additional_parts_list_container" class="additional-parts-context-menu"[% IF initially_hidden %] style="display: none;"[% END %]>
+
+ <h2>[% LxERP.t8("Additional articles") %]</h2>
+
+ <div id="additional_parts_list_empty"[% IF parts.size %] style="display: none;"[% END %]>
+  [% LxERP.t8("No articles have been added yet.") %]
+ </div>
+
+ <table id="additional_parts_list"[% IF !parts.size %] style="display: none;"[% END %]>
+  <thead>
+   <tr class="listheading">
+    <th>[%- LxERP.t8("Part Number") %]</th>
+    <th>[%- LxERP.t8("Description") %]</th>
+    <th>[%- LxERP.t8("Qty") %]</th>
+   </tr>
+  </thead>
+
+  <tbody>
+   [% FOREACH part = parts %]
+    <tr class="listrow">
+     <td>[% HTML.escape(part.part.partnumber) %]</td>
+     <td>[% HTML.escape(part.description) %]</td>
+     <td valign="right">[% HTML.escape(part.qty_as_number) %] [% HTML.escape(part.unit.name) %]</td>
+    </tr>
+   [% END %]
+  </tbody>
+ </table>
+</div>