use Data::Dumper;
 use DateTime;
 use SL::DB::History;
+use SL::DB::Helper::ValidateAssembly qw(validate_assembly);
 use SL::CVar;
 use Carp;
 
     ->html('#items_sum_diff',            $::form->format_amount(\%::myconfig, $sum_diff,      2, 0))
     ->html('#items_sellprice_sum_basic', $::form->format_amount(\%::myconfig, $sellprice_sum, 2, 0))
     ->html('#items_lastcost_sum_basic',  $::form->format_amount(\%::myconfig, $lastcost_sum,  2, 0))
-    ->render();
+    ->no_flash_clear->render();
 }
 
 sub action_add_multi_assortment_items {
   my ($self) = @_;
 
   my $item_objects = $self->parse_add_items_to_objects(part_type => 'assembly');
-  my $html         = $self->render_assembly_items_to_html($item_objects);
+  my @checked_objects;
+  foreach my $item (@{$item_objects}) {
+    my $errstr = validate_assembly($item->part,$self->part);
+    $self->js->flash('error',$errstr) if     $errstr;
+    push (@checked_objects,$item)     unless $errstr;
+  }
+
+  my $html = $self->render_assembly_items_to_html(\@checked_objects);
 
   $self->js->run('kivi.Part.close_multi_items_dialog')
            ->append('#assembly_rows', $html)
     ->html('#items_lastcost_sum_basic',  $::form->format_amount(\%::myconfig, $items_lastcost_sum,  2, 0))
     ->render;
 }
+
 sub action_add_assembly_item {
   my ($self) = @_;
 
   carp('Too many objects passed to add_assembly_item') if @{$::form->{add_items}} > 1;
 
   my $add_item_id = $::form->{add_items}->[0]->{parts_id};
+
   my $duplicate_warning = 0; # duplicates are allowed, just warn
   if ( $add_item_id && grep { $add_item_id == $_->parts_id } @{ $self->assembly_items } ) {
     $duplicate_warning++;
 
   my $number_of_items = scalar @{$self->assembly_items};
   my $item_objects    = $self->parse_add_items_to_objects(part_type => 'assembly');
+  if ($add_item_id ) {
+    foreach my $item (@{$item_objects}) {
+      my $errstr = validate_assembly($item->part,$self->part);
+      return $self->js->flash('error',$errstr)->render if $errstr;
+    }
+  }
+
+
   my $html            = $self->render_assembly_items_to_html($item_objects, $number_of_items);
 
   $self->js->flash('info', t8("This part has already been added.")) if $duplicate_warning;
 
-# 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::Assembly;
 
 use strict;
 
--- /dev/null
+package SL::DB::Helper::ValidateAssembly;
+
+use strict;
+use parent qw(Exporter);
+our @EXPORT = qw(validate_assembly);
+
+use SL::Locale::String;
+use SL::DB::Part;
+use SL::DB::Assembly;
+
+sub validate_assembly {
+  my ($new_part, $part) = @_;
+
+  return t8("The assembly '#1' cannot be a part from itself.", $part->partnumber) if $new_part->id == $part->id;
+
+  my @seen = ($part->id);
+
+  return assembly_loop_exists(0, $new_part, @seen);
+}
+
+sub assembly_loop_exists {
+  my ($depth, $new_part, @seen) = @_;
+
+  return t8("Too much recursions in assembly tree (>100)") if $depth > 100;
+
+  # 1. check part is an assembly
+  return unless $new_part->is_assembly;
+
+  # 2. check assembly is still in list
+  return t8("The assembly '#1' would make a loop in assembly tree.", $new_part->partnumber) if grep { $_ == $new_part->id } @seen;
+
+  # 3. add to new list
+
+  push @seen, $new_part->id;
+
+  # 4. go into depth for each child
+
+  foreach my $assembly ($new_part->assemblies) {
+    my $retval = assembly_loop_exists($depth + 1, $assembly->part, @seen);
+    return $retval if $retval;
+  }
+  return undef;
+}
+
+1;
+
+__END__
+
+=encoding utf-8
+
+=head1 NAME
+
+SL::DB::Helper::ValidateAssembly - Mixin to check loops in assemblies
+
+=head1 SYNOPSIS
+
+SL::DB::Helper::ValidateAssembly->validate_assembly($newpart,$assembly_part);
+
+
+=head1 HELPER FUNCTION
+
+=over 4
+
+=item C<validate_assembly new_part_object  part_object>
+
+A new part is added to an assembly. C<new_part_object> is the part which is want to added.
+
+First it was checked if the new part is equal the actual part.
+Then recursively all assemblies in the assemby are checked for a loop.
+
+The function returns an error string if a loop exists or the maximum of 100 iterations is reached
+else on success ''.
+
+=back
+
+=head1 AUTHOR
+
+Martin Helmling E<lt>martin.helmling@opendynamic.de>E<gt>
+
+=cut
 
     SL::DB::OrderItem
     SL::DB::DeliveryOrderItem
     SL::DB::Inventory
-    SL::DB::Assembly
     SL::DB::AssortmentItem
   );
 
 
   my (%params) = @_;
 
   my @parts;
-  my $part1 = SL::Dev::Part::create_part(partnumber   => 'ap1',
+  my $partnumber = delete $params{part1number} || 'ap1';
+  my $part1 = SL::Dev::Part::create_part(partnumber   => $partnumber,
                                          description  => 'Testpart',
                                         )->save;
   push(@parts, $part1);
 
   for my $i ( 2 .. $number_of_parts ) {
     my $part = $parts[0]->clone_and_reset;
-    $part->partnumber(  ($part->partnumber  // '') . " " . $i );
+    $part->partnumber(  $partnumber . " " . $i );
     $part->description( ($part->description // '') . " " . $i );
     $part->save;
     push(@parts, $part);
   }
 
+  my $assnumber = delete $params{assnumber} || 'as1';
   my $assembly = SL::DB::Part->new_assembly(
-    partnumber         => 'as1',
+    partnumber         => $assnumber,
     description        => 'Test Assembly',
     sellprice          => '10',
     lastcost           => '5',
 
     $("#assembly_rows tr:last").find('input[type=text]').filter(':visible:first').focus();
   };
 
-  ns.show_multi_items_dialog = function(part_type) {
+  ns.show_multi_items_dialog = function(part_type,part_id) {
 
     $('#row_table_id thead a img').remove();
 
       data: { callback:         'Part/add_multi_' + part_type + '_items',
               callback_data_id: 'ic',
               'part.part_type': part_type,
+              'part.id'       : part_id,
             },
       id: 'jq_multi_items_dialog',
       dialog: {
 
   'Department (description)'    => 'Abteilung (Beschreibung)',
   'Department 1'                => 'Abteilung (1)',
   'Department 2'                => 'Abteilung (2)',
-  'Department Id'               => 'Reservierung',
   'Departments'                 => 'Abteilungen',
   'Dependencies'                => 'Abhängigkeiten',
   'Dependency loop detected:'   => 'Schleife in den Abhängigkeiten entdeckt:',
   'The action you\'ve chosen has not been executed because the document does not contain any item yet.' => 'Die von Ihnen ausgewählte Aktion wurde nicht ausgeführt, weil der Beleg noch keine Positionen enthält.',
   'The administration area is always accessible.' => 'Der Administrationsbereich ist immer zugänglich.',
   'The application "#1" was not found on the system.' => 'Die Anwendung "#1" wurde auf dem System nicht gefunden.',
+  'The assembly \'#1\' cannot be a part from itself.' => 'Das Erzeugnis \'#1\' kann kein Teil von sich selbst sein.',
+  'The assembly \'#1\' would make a loop in assembly tree.' => 'Das Erzeugnis \'#1\' würde eine Schleife im Erzeugnisbaum machen.',
   'The assembly doesn\'t have any items.' => 'Das Erzeugnis enthält keine Artikel.',
   'The assembly has been created.' => 'Das Erzeugnis wurde hergestellt.',
   'The assistant could not find anything wrong with #1. Maybe the problem has been solved in the meantime.' => 'Der Korrekturassistent konnte kein Problem bei #1 feststellen. Eventuell wurde das Problem in der Zwischenzeit bereits behoben.',
   'To user login'               => 'Zum Benutzerlogin',
   'Toggle marker'               => 'Markierung umschalten',
   'Too many results (#1 from #2).' => 'Zu viele Artikel (#1 von #2)',
+  'Too much recursions in assembly tree (>100)' => 'Zu tiefe Verschachtelung (>100) des Erzeugnisbaum',
   'Top'                         => 'Oben',
   'Top (CSS)'                   => 'Oben (mit CSS)',
   'Top (Javascript)'            => 'Oben (mit Javascript)',
 
 use SL::DB::Part;
 use SL::DB::Assembly;
 use SL::Dev::Part;
+use SL::DB::Helper::ValidateAssembly;
 
 Support::TestSetup::login();
+$::locale        = Locale->new("en");
 
 clear_up();
 reset_state();
 my $assembly_item_part = SL::DB::Manager::Part->find_by( partnumber => 'ap1' );
 
 is($assembly_part->part_type, 'assembly', 'assembly has correct type');
-is( scalar @{$assembly_part->assemblies}, 3, 'assembly consists of two parts' );
+is( scalar @{$assembly_part->assemblies}, 3, 'assembly consists of three parts' );
 
 # fetch assembly item corresponding to partnumber 19000
 my $assembly_items = $assembly_part->find_assemblies( { parts_id => $assembly_item_part->id } ) || die "can't find assembly_item";
 is($assembly_item->part->partnumber, 'ap1', 'assembly part part relation works');
 is($assembly_item->assembly_part->partnumber, '19000', 'assembly part assembly part relation works');
 
+
+
+my $assembly2_part = SL::Dev::Part::create_assembly( partnumber => '20000', part1number => 'ap2', assnumber => 'as2' )->save;
+my $retval = validate_assembly($assembly_part,$assembly2_part);
+ok( $retval eq undef , 'assembly 19000 can be child of assembly 20000' );
+$assembly2_part->add_assemblies(SL::DB::Assembly->new(parts_id => $assembly_part->id, qty => 3, bom => 1));
+$assembly2_part->save;
+
+my $assembly3_part = SL::Dev::Part::create_assembly( partnumber => '30000', part1number => 'ap3', assnumber => 'as3' )->save;
+$retval = validate_assembly($assembly3_part,$assembly_part);
+ok( $retval eq undef , 'assembly 30000 can be child of assembly 19000' );
+
+$retval = validate_assembly($assembly3_part,$assembly2_part);
+ok( $retval eq undef , 'assembly 30000 can be child of assembly 20000' );
+
+$assembly_part->add_assemblies(SL::DB::Assembly->new(parts_id => $assembly3_part->id, qty => 4, bom => 1));
+$assembly_part->save;
+
+$retval = validate_assembly($assembly3_part,$assembly2_part);
+ok( $retval eq undef , 'assembly 30000 can be child of assembly 20000' );
+
+$assembly2_part->add_assemblies(SL::DB::Assembly->new(parts_id => $assembly3_part->id, qty => 5, bom => 1));
+$assembly2_part->save;
+
+# fetch assembly item corresponding to partnumber 20000
+my $assembly2_items = $assembly2_part->find_assemblies() || die "can't find assembly_item";
+is( scalar @{$assembly2_items}, 5, 'assembly2 consists of four parts' );
+my $assembly2_item = $assembly2_items->[3];
+is($assembly2_item->qty, 3, 'count of 3.th assembly is 3' );
+is($assembly2_item->part->part_type, 'assembly', '3.th assembly \''.$assembly2_item->part->partnumber. '\' is also an assembly');
+my $assembly3_items = $assembly2_item->part->find_assemblies() || die "can't find assembly_item";
+is( scalar @{$assembly3_items}, 4, 'assembly3 consists of three parts' );
+
+
+
+# check loop to itself
+$retval = validate_assembly($assembly_part,$assembly_part);
+is( $retval,"The assembly '19000' cannot be a part from itself.", 'assembly loops to itself' );
+if (!$retval && $assembly_part->add_assemblies( SL::DB::Assembly->new(parts_id => $assembly_part->id, qty => 8, bom => 1))) {
+  $assembly_part->save;
+}
+is( scalar @{$assembly_part->assemblies}, 4, 'assembly consists of three parts' );
+
+# check indirekt loop
+$retval = validate_assembly($assembly2_part,$assembly_part);
+ok( $retval, 'assembly indirect loop' );
+if (!$retval && $assembly_part->add_assemblies( SL::DB::Assembly->new(parts_id => $assembly2_part->id, qty => 9, bom => 1))) {
+  $assembly_part->save;
+}
+is( scalar @{$assembly_part->assemblies}, 4, 'assembly consists of three parts' );
+
 clear_up();
 done_testing;
 
 
  <td align="right">[% 'Part' | $T8 %]:</td>
  <td>[% L.part_picker('add_items[+].parts_id'   , ''  , style='width: 300px' , class="add_assembly_item_input") %][% L.hidden_tag('add_items[].qty_as_number', 1) %]</td>
  <td>[%- L.button_tag("kivi.Part.add_assembly_item()", LxERP.t8("Add")) %]</td>
- <td>[% L.button_tag('kivi.Part.show_multi_items_dialog("assembly")', LxERP.t8('Add multiple items')) %]</td>
+ <td>[% L.button_tag('kivi.Part.show_multi_items_dialog("assembly",' _ SELF.part.id _ ')', LxERP.t8('Add multiple items')) %]</td>
  [% ELSE %]
  <td></td>
  <td></td>
 
  <td align="right">[% 'Part' | $T8 %]:</td>
  <td>[% L.part_picker('add_items[+].parts_id'   , ''  , style='width: 300px' , class="add_assortment_item_input") %][% L.hidden_tag('add_items[].qty_as_number', 1) %]</td>
  <td>[%- L.button_tag("kivi.Part.add_assortment_item()", LxERP.t8("Add")) %]</td>
- <td>[% L.button_tag('kivi.Part.show_multi_items_dialog("assortment")', LxERP.t8('Add multiple items')) %]</td>
+ <td>[% L.button_tag('kivi.Part.show_multi_items_dialog("assortment",' _ SELF.part.id _ ')', LxERP.t8('Add multiple items')) %]</td>
  <td></td>
  [% ELSE %]
  <td></td>
 
   // var data = data.concat($('#multi_items_form').serializeArray());
   var data = $('#multi_items_form').serializeArray();
   data.push({ name: 'action', value: '[%- FORM.callback %]' });
-  data.push({ name: 'part_type', value: '[%- part_type %]' });
+  data.push({ name: 'part_type', value: '[%- FORM.part.part_type %]' });
+  data.push({ name: 'part.id'  , value: '[%- FORM.part.id %]' });
   $.post("controller.pl", data, kivi.eval_json_result);
 }