Artikel-Klassifizierung
authorMartin Helmling martin.helmling@octosoft.eu <martin.helmling@octosoft.eu>
Thu, 15 Sep 2016 06:22:34 +0000 (08:22 +0200)
committerMartin Helmling martin.helmling@octosoft.eu <martin.helmling@octosoft.eu>
Thu, 24 Nov 2016 08:08:29 +0000 (09:08 +0100)
Die ursprünglich als "Artikeltyp" bezeichnete Klassifizierung von Artikeln
Sie dient einer weiteren Gliederung um zum Beispiel den Einkauf vom Verkauf zu trennen, etc.

Gekennzeichnet durch eine Beschreibung (z.B. "Einkauf") und ein Kürzel (z.B. "E")
Flexibel änderbar und erweiterbar.

- Neue Datenbanktablle und Rose-Objekte, sowie Controller zum Bearbeiten der Tabelle

- Zwei-Zeichen Abkürzung:

Der Typ des Artikel und die Klassifizierung werden durch zwei Buchstaben dargestellt.
Der erste Buchstabe  ist eine Lokalisierung des Typs des Artikel ('P','A','S') ,
deutch 'W', 'E', und 'D' für Ware Erzeugnis oder Dienstleistung, ggf. weitere Typen.
Der zweite Buchstabe ist eine Lokalisierung der Klassifizierungsabkürzung (abbreviation).

Die Abkürzungen sind aus dem Part Presenter abholbar:
-  SL::Presenter::Part->type_abbreviation($part_type)
-  SL::Presenter::Part->classification_abbreviation($classification_id)

Wenn im ERP-Dokument nach einer Artikelnummer oder Beschreibung gesucht wird,
diese in den Stammdaten vorhanden ist,
aber der Artikeltyp leer oder falsch ist, bzw im Typ for_purchase bzw for_sale nicht gesetzt ist,
wird die Fehlermeldung "Gesuchter Artikel ist nicht für den Einkauf bzw Verkauf" gemeldet

Anpassung des CSV Import,
nun wird alternativ zur 'type'-Spalte die 'pclass'-Spalte mit zwei Buchstaben geparsed und entsprechend
classification_id,assembly sowie inventory_accno_id gesetzt (oder type_id falls neue Implementierung eingebaut).

37 files changed:
SL/Controller/CsvImport/Part.pm
SL/Controller/PartsClassification.pm [new file with mode: 0644]
SL/DB/Helper/Mappings.pm
SL/DB/Manager/PartsClassification.pm [new file with mode: 0644]
SL/DB/MetaSetup/Part.pm
SL/DB/MetaSetup/PartsClassification.pm [new file with mode: 0644]
SL/DB/PartsClassification.pm [new file with mode: 0644]
SL/IC.pm
SL/IR.pm
SL/IS.pm
SL/OE.pm
SL/Presenter/Part.pm
SL/WH.pm
bin/mozilla/ic.pl
bin/mozilla/io.pl
bin/mozilla/wh.pl
doc/changelog
locale/de/all
locale/en/all
menus/user/13-parts-classification.yaml [new file with mode: 0644]
sql/Pg-upgrade2/parts_classifications.sql [new file with mode: 0644]
t/controllers/csvimport/parts.t
templates/webpages/csv_import/_form_parts.html
templates/webpages/csv_import/form.html
templates/webpages/generic/new_item.html
templates/webpages/ic/assembly_row.html
templates/webpages/ic/form_header.html
templates/webpages/ic/generate_report_bottom.html
templates/webpages/ic/search.html
templates/webpages/io/select_item.html
templates/webpages/part/_assembly.html
templates/webpages/part/_assembly_row.html
templates/webpages/part/_basic_data.html
templates/webpages/parts_classification/form.html [new file with mode: 0755]
templates/webpages/parts_classification/list.html [new file with mode: 0644]
templates/webpages/wh/journal_filter.html
templates/webpages/wh/report_filter.html

index ef7e238..21a2982 100644 (file)
@@ -23,7 +23,7 @@ use parent qw(SL::Controller::CsvImport::Base);
 use Rose::Object::MakeMethods::Generic
 (
  scalar                  => [ qw(table makemodel_columns) ],
- 'scalar --get_set_init' => [ qw(bg_by settings parts_by price_factors_by units_by partsgroups_by
+ 'scalar --get_set_init' => [ qw(bg_by settings parts_by price_factors_by classification_by units_by partsgroups_by
                                  warehouses_by bins_by
                                  translation_columns all_pricegroups) ],
 );
@@ -40,6 +40,7 @@ sub set_profile_defaults {
                        article_number_policy     => 'update_prices',
                        shoparticle_if_missing    => '0',
                        part_type                 => 'part',
+                       parts_classification      => 0,
                        default_buchungsgruppe    => ($bugru ? $bugru->id : undef),
                        apply_buchungsgruppe      => 'all',
                       );
@@ -58,6 +59,13 @@ sub init_bg_by {
   return { map { my $col = $_; ( $col => { map { ( $_->$col => $_ ) } @{ $all_bg } } ) } qw(id description) };
 }
 
+sub init_classification_by {
+  my ($self) = @_;
+  my $all_classifications = SL::DB::Manager::PartsClassification->get_all;
+  $_->abbreviation($::locale->text($_->abbreviation)) for @{ $all_classifications };
+  return { map { my $col = $_; ( $col => { map { ( $_->$col => $_ ) } @{ $all_classifications } } ) } qw(id abbreviation) };
+}
+
 sub init_price_factors_by {
   my ($self) = @_;
 
@@ -126,7 +134,7 @@ sub init_settings {
 
   return { map { ( $_ => $self->controller->profile->get($_) ) } qw(apply_buchungsgruppe default_buchungsgruppe article_number_policy
                                                                     sellprice_places sellprice_adjustment sellprice_adjustment_type
-                                                                    shoparticle_if_missing part_type default_unit) };
+                                                                    shoparticle_if_missing part_type classification_id default_unit) };
 }
 
 sub init_all_cvar_configs {
@@ -174,7 +182,7 @@ sub check_objects {
     $i++;
   }
 
-  $self->add_columns(qw(part_type)) if $self->settings->{part_type} eq 'mixed';
+  $self->add_columns(qw(part_type classification_id)) if $self->settings->{part_type} eq 'mixed';
   $self->add_columns(qw(buchungsgruppen_id unit));
   $self->add_columns(map { "${_}_id" } grep { exists $self->controller->data->[0]->{raw_data}->{$_} } qw (price_factor payment partsgroup warehouse bin));
   $self->add_columns(qw(shop)) if $self->settings->{shoparticle_if_missing};
@@ -413,12 +421,30 @@ sub check_part_type {
   # $bg  ||= SL::DB::Buchungsgruppe->new(inventory_accno_id => 1); # does this case ever occur?
 
   my $part_type = $self->settings->{part_type};
-  if ($part_type eq 'mixed') {
+  if ($part_type eq 'mixed' && $entry->{raw_data}->{part_type}) {
     $part_type = $entry->{raw_data}->{part_type} =~ m/^p/i ? 'part'
                : $entry->{raw_data}->{part_type} =~ m/^s/i ? 'service'
                : $entry->{raw_data}->{part_type} =~ m/^assem/i ? 'assembly'
                : $entry->{raw_data}->{part_type} =~ m/^assor/i ? 'assortment'
-               : undef;
+               : $self->settings->{part_type};
+  }
+  my $classification_id = $self->settings->{classification_id};
+
+  if ( $entry->{raw_data}->{pclass} && length($entry->{raw_data}->{pclass}) >= 2 ) {
+      my $abbr1 = substr($entry->{raw_data}->{pclass},0,1);
+      my $abbr2 = substr($entry->{raw_data}->{pclass},1);
+
+      if ( $self->classification_by->{abbreviation}->{$abbr2} ) {
+         my $tmp_classification_id = $self->classification_by->{abbreviation}->{$abbr2}->id;
+         $classification_id = $tmp_classification_id if $tmp_classification_id;
+      }
+      if ($part_type eq 'mixed') {
+          $part_type = $abbr1 eq $::locale->text('Part (typeabbreviation)') ? 'part'
+                     : $abbr1 eq $::locale->text('Service (typeabbreviation)') ? 'service'
+                     : $abbr1 eq $::locale->text('Assembly (typeabbreviation)') ? 'assembly'
+                     : $abbr1 eq $::locale->text('Assortment (typeabbreviation)') ? 'assortment'
+                     :                                        undef;
+      }
   }
 
   # when saving income_accno_id or expense_accno_id use ids from the selected
@@ -443,6 +469,7 @@ sub check_part_type {
   }
 
   $entry->{object}->part_type($part_type);
+  $entry->{object}->classification_id( $classification_id );
 
   return 1;
 }
@@ -699,8 +726,8 @@ sub setup_displayable_columns {
 
   $self->add_displayable_columns({ name => 'bin_id',             description => $::locale->text('Bin (database ID)')                                    },
                                  { name => 'bin',                description => $::locale->text('Bin (name)')                                           },
-                                 { name => 'buchungsgruppen_id', description => $::locale->text('Booking group (database ID)')                         },
-                                 { name => 'buchungsgruppe',     description => $::locale->text('Booking group (name)')                                },
+                                 { name => 'buchungsgruppen_id', description => $::locale->text('Booking group (database ID)')                          },
+                                 { name => 'buchungsgruppe',     description => $::locale->text('Booking group (name)')                                 },
                                  { name => 'description',        description => $::locale->text('Description')                                          },
                                  { name => 'drawing',            description => $::locale->text('Drawing')                                              },
                                  { name => 'ean',                description => $::locale->text('EAN')                                                  },
@@ -721,18 +748,19 @@ sub setup_displayable_columns {
                                  { name => 'partnumber',         description => $::locale->text('Part Number')                                          },
                                  { name => 'partsgroup_id',      description => $::locale->text('Partsgroup (database ID)')                             },
                                  { name => 'partsgroup',         description => $::locale->text('Partsgroup (name)')                                    },
+                                 { name => 'classification_by',  description => $::locale->text('Article classification')  . ' [3]'                     },
                                  { name => 'payment_id',         description => $::locale->text('Payment terms (database ID)')                          },
                                  { name => 'payment',            description => $::locale->text('Payment terms (name)')                                 },
                                  { name => 'price_factor_id',    description => $::locale->text('Price factor (database ID)')                           },
                                  { name => 'price_factor',       description => $::locale->text('Price factor (name)')                                  },
                                  { name => 'rop',                description => $::locale->text('ROP')                                                  },
                                  { name => 'sellprice',          description => $::locale->text('Sellprice')                                            },
-                                 { name => 'shop',               description => $::locale->text('Shop article')                                          },
-                                 { name => 'type',               description => $::locale->text('Article type')  . ' [3]'                             },
+                                 { name => 'shop',               description => $::locale->text('Shop article')                                         },
+                                 { name => 'type',               description => $::locale->text('Article type')  . ' [3]'                               },
                                  { name => 'unit',               description => $::locale->text('Unit (if missing or empty default unit will be used)') },
                                  { name => 've',                 description => $::locale->text('Verrechnungseinheit')                                  },
                                  { name => 'warehouse_id',       description => $::locale->text('Warehouse (database ID)')                              },
-                                 { name => 'warehouse',          description => $::locale->text('Warehouse (name)')                              },
+                                 { name => 'warehouse',          description => $::locale->text('Warehouse (name)')                                     },
                                  { name => 'weight',             description => $::locale->text('Weight')                                               },
                                 );
 
diff --git a/SL/Controller/PartsClassification.pm b/SL/Controller/PartsClassification.pm
new file mode 100644 (file)
index 0000000..ced484c
--- /dev/null
@@ -0,0 +1,115 @@
+package SL::Controller::PartsClassification;
+
+use strict;
+
+use parent qw(SL::Controller::Base);
+
+use SL::DB::PartsClassification;
+use SL::Helper::Flash;
+
+use Rose::Object::MakeMethods::Generic
+(
+ scalar => [ qw(parts_classification) ],
+);
+
+__PACKAGE__->run_before('check_auth');
+__PACKAGE__->run_before('load_parts_classification', only => [ qw(edit update destroy) ]);
+
+#
+# actions
+#
+
+sub action_list {
+  my ($self) = @_;
+
+  $self->render('parts_classification/list',
+                title         => $::locale->text('Parts Classifications'),
+                PARTS_CLASSIFICATIONS => SL::DB::Manager::PartsClassification->get_all_sorted);
+}
+
+sub action_new {
+  my ($self) = @_;
+
+  $self->{parts_classification} = SL::DB::PartsClassification->new;
+  $self->render('parts_classification/form', title => $::locale->text('Create a new parts classification'));
+}
+
+sub action_edit {
+  my ($self) = @_;
+  $self->render('parts_classification/form', title => $::locale->text('Edit parts classification'));
+}
+
+sub action_create {
+  my ($self) = @_;
+
+  $self->{parts_classification} = SL::DB::PartsClassification->new;
+  $self->create_or_update;
+}
+
+sub action_update {
+  my ($self) = @_;
+  $self->create_or_update;
+}
+
+sub action_destroy {
+  my ($self) = @_;
+
+  if ( $self->{parts_classification}->id < 5 ) {
+    flash_later('error', $::locale->text('The basic parts classification cannot be deleted.'));
+  }
+  elsif (eval { $self->{parts_classification}->delete; 1; }) {
+    flash_later('info',  $::locale->text('The parts classification has been deleted.'));
+  } else {
+    flash_later('error', $::locale->text('The parts classification is in use and cannot be deleted.'));
+  }
+
+  $self->redirect_to(action => 'list');
+}
+
+sub action_reorder {
+  my ($self) = @_;
+
+  SL::DB::PartsClassification->reorder_list(@{ $::form->{parts_classification_id} || [] });
+
+  $self->render(\'', { type => 'json' });
+}
+
+#
+# filters
+#
+
+sub check_auth {
+  $::auth->assert('config');
+}
+
+#
+# helpers
+#
+
+sub create_or_update {
+  my $self   = shift;
+  my $is_new = !$self->{parts_classification}->id;
+  my $params = delete($::form->{parts_classification}) || { };
+
+  $self->{parts_classification}->assign_attributes(%{ $params });
+
+  my @errors = $self->{parts_classification}->validate;
+
+  if (@errors) {
+    flash('error', @errors);
+    $self->render('parts_classification/form', title => $is_new ? $::locale->text('Create a new parts classification') : $::locale->text('Edit parts classification'));
+    return;
+  }
+
+  $self->{parts_classification}->save;
+
+  flash_later('info', $is_new ? $::locale->text('The parts classification has been created.') : $::locale->text('The parts classification has been saved.'));
+  $self->redirect_to(action => 'list');
+}
+
+sub load_parts_classification {
+  my ($self) = @_;
+  $self->{parts_classification} = SL::DB::PartsClassification->new(id => $::form->{id})->load;
+}
+
+1;
index 7d1907d..d61f59f 100644 (file)
@@ -153,6 +153,7 @@ my %kivitendo_package_names = (
   oe                             => 'order',
   parts                          => 'part',
   partsgroup                     => 'parts_group',
+  parts_classifications          => 'PartsClassification',
   parts_price_history            => 'PartsPriceHistory',
   payment_terms                  => 'payment_term',
   periodic_invoices              => 'periodic_invoice',
diff --git a/SL/DB/Manager/PartsClassification.pm b/SL/DB/Manager/PartsClassification.pm
new file mode 100644 (file)
index 0000000..23558dc
--- /dev/null
@@ -0,0 +1,22 @@
+# 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::PartsClassification;
+
+use strict;
+
+use parent qw(SL::DB::Helper::Manager);
+use SL::DB::Helper::Sorted;
+
+sub object_class { 'SL::DB::PartsClassification' }
+
+__PACKAGE__->make_manager_methods;
+
+
+sub get_abbreviation {
+  my ($class,$id) = @_;
+  my $obj = $class->get_first(query => [ id => $id ]);
+  return $obj->abbreviation?$obj->abbreviation:undef;
+}
+
+1;
index 65cd85d..7c79e2e 100644 (file)
@@ -12,6 +12,7 @@ __PACKAGE__->meta->columns(
   bin_id             => { type => 'integer' },
   bom                => { type => 'boolean', default => 'false' },
   buchungsgruppen_id => { type => 'integer' },
+  classification_id  => { type => 'integer', default => '0' },
   description        => { type => 'text' },
   drawing            => { type => 'text' },
   ean                => { type => 'text' },
@@ -63,6 +64,11 @@ __PACKAGE__->meta->foreign_keys(
     key_columns => { buchungsgruppen_id => 'id' },
   },
 
+  classification => {
+    class       => 'SL::DB::PartsClassification',
+    key_columns => { classification_id => 'id' },
+  },
+
   partsgroup => {
     class       => 'SL::DB::PartsGroup',
     key_columns => { partsgroup_id => 'id' },
diff --git a/SL/DB/MetaSetup/PartsClassification.pm b/SL/DB/MetaSetup/PartsClassification.pm
new file mode 100644 (file)
index 0000000..18e1dff
--- /dev/null
@@ -0,0 +1,22 @@
+# This file has been auto-generated. Do not modify it; it will be overwritten
+# by rose_auto_create_model.pl automatically.
+package SL::DB::PartsClassification;
+
+use strict;
+
+use parent qw(SL::DB::Object);
+
+__PACKAGE__->meta->table('parts_classifications');
+
+__PACKAGE__->meta->columns(
+  abbreviation      => { type => 'text' },
+  description       => { type => 'text' },
+  id                => { type => 'serial', not_null => 1 },
+  used_for_purchase => { type => 'boolean', default => 'true' },
+  used_for_sale     => { type => 'boolean', default => 'true' },
+);
+
+__PACKAGE__->meta->primary_key_columns([ 'id' ]);
+
+1;
+;
diff --git a/SL/DB/PartsClassification.pm b/SL/DB/PartsClassification.pm
new file mode 100644 (file)
index 0000000..7d350c2
--- /dev/null
@@ -0,0 +1,24 @@
+# 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::PartsClassification;
+
+use strict;
+
+use SL::DB::MetaSetup::PartsClassification;
+use SL::DB::Manager::PartsClassification;
+
+__PACKAGE__->meta->initialize;
+
+sub validate {
+  my ($self) = @_;
+
+  my @errors;
+  push @errors, $::locale->text('The description is missing.')  if !$self->description;
+  push @errors, $::locale->text('The abbreviation is missing.') if !$self->abbreviation;
+
+  return @errors;
+}
+
+
+1;
index 2cf7996..2163bf8 100644 (file)
--- a/SL/IC.pm
+++ b/SL/IC.pm
@@ -85,6 +85,7 @@ sub get_part {
     # retrieve assembly items
     $query =
       qq|SELECT p.id, p.partnumber, p.description,
+           p.classification_id,
            p.sellprice, p.lastcost, p.weight, a.qty, a.bom, p.unit,
            pg.partsgroup, p.price_factor_id, pfac.factor AS price_factor
          FROM parts p
@@ -101,6 +102,8 @@ sub get_part {
       foreach my $key (keys %{$ref}) {
         $form->{"${key}_$form->{assembly_rows}"} = $ref->{$key};
       }
+      $form->{"type_and_classific_$form->{assembly_rows}"} = $::request->presenter->type_abbreviation($ref->{part_type}).
+                                                             $::request->presenter->classification_abbreviation($ref->{classification_id});
     }
     $sth->finish;
 
@@ -334,6 +337,7 @@ sub _save {
          partnumber = ?,
          description = ?,
          makemodel = ?,
+         classification_id = ?,
          listprice = ?,
          sellprice = ?,
          lastcost = ?,
@@ -367,6 +371,7 @@ sub _save {
   @values = ($form->{partnumber},
              $form->{description},
              $makemodel ? 't' : 'f',
+             $form->{classification_id},
              $form->{listprice},
              $form->{sellprice},
              $form->{lastcost},
@@ -642,6 +647,7 @@ sub assembly_item {
 
   my $query =
     qq|SELECT p.id, p.partnumber, p.description, p.sellprice,
+       p.classification_id,
        p.weight, p.onhand, p.unit, pg.partsgroup, p.lastcost,
        p.price_factor_id, pfac.factor AS price_factor, p.notes as longdescription
        FROM parts p
@@ -723,7 +729,7 @@ sub all_parts {
 #  my @other_flags          = qw(onhand); # ToDO: implement these
 #  my @inactive_flags       = qw(l_subtotal short l_linetotal);
 
-  my @select_tokens = qw(id factor);
+  my @select_tokens = qw(id factor part_type classification_id);
   my @where_tokens  = qw(1=1);
   my @group_tokens  = ();
   my @bind_vars     = ();
@@ -800,7 +806,7 @@ sub all_parts {
     insertdate         => 'itime::DATE',
   );
 
-  if (($form->{searchitems} eq 'assembly') && $form->{l_lastcost}) {
+  if ($form->{l_assembly} && $form->{l_lastcost}) {
     @simple_l_switches = grep { $_ ne 'lastcost' } @simple_l_switches;
   }
 
@@ -890,10 +896,22 @@ sub all_parts {
     push @select_tokens, $_;
   }
 
-  for ($form->{searchitems}) {
-    push @where_tokens, "p.part_type = 'part'"     if /part/;
-    push @where_tokens, "p.part_type = 'service'"  if /service/;
-    push @where_tokens, "p.part_type = 'assembly'" if /assembly/;
+  # Oder Bedingungen fuer Ware Dienstleistung Erzeugnis:
+  if ($form->{l_part} || $form->{l_assembly} || $form->{l_service}) {
+      my @or_tokens = ();
+      push @or_tokens, "p.part_type = 'service'"  if $form->{l_service};
+      push @or_tokens, "p.part_type = 'assembly'" if $form->{l_assembly};
+      push @or_tokens, "p.part_type = 'part'"     if $form->{l_part};
+      push @where_tokens, join ' OR ', map { "($_)" } @or_tokens;
+  }
+  else {
+      # gar keine Teile
+      push @where_tokens, q|'F' = 'T'|;
+  }
+
+  if ( $form->{classification_id} > 0 ) {
+    push @where_tokens, "p.classification_id = ?";
+    push @bind_vars, $form->{classification_id};
   }
 
   for ($form->{itemstatus}) {
@@ -951,7 +969,7 @@ sub all_parts {
 
   push @select_tokens, @qsooqr_flags, 'quotation', 'cv', 'ioi.id', 'ioi.ioi'  if $bsooqr;
   push @select_tokens, @deliverydate_flags                                    if $bsooqr && $form->{l_deliverydate};
-  push @select_tokens, $q_assembly_lastcost                                   if ($form->{searchitems} eq 'assembly') && $form->{l_lastcost};
+  push @select_tokens, $q_assembly_lastcost                                   if $form->{l_assembly} && $form->{l_lastcost};
   push @bsooqr_tokens, q|module = 'ir' AND NOT ioi.assemblyitem|              if $form->{bought};
   push @bsooqr_tokens, q|module = 'is' AND NOT ioi.assemblyitem|              if $form->{sold};
   push @bsooqr_tokens, q|module = 'oe' AND NOT quotation AND cv = 'customer'| if $form->{ordered};
@@ -1049,7 +1067,7 @@ sub all_parts {
   # post processing for assembly parts lists (bom)
   # for each part get the assembly parts and add them into the partlist.
   my @assemblies;
-  if ($form->{searchitems} eq 'assembly' && $form->{bom}) {
+  if ($form->{l_assembly} && $form->{bom}) {
     $query =
       qq|SELECT p.id, p.partnumber, p.description, a.qty AS onhand,
            p.unit, p.notes, p.itime::DATE as insertdate,
@@ -1079,7 +1097,7 @@ sub all_parts {
     $form->{parts} = \@assemblies;
   }
 
-  if ($form->{l_pricegroups} ) {
+  if ($form->{l_pricegroups} && SL::DB::Manager::Price->get_all_count() > 0 ) {
     my $query = <<SQL;
        SELECT parts_id, price, pricegroup_id
        FROM prices
@@ -1096,8 +1114,7 @@ SQL
       }
       $sth->finish;
     }
-  };
-
+  }
 
   $main::lxdebug->leave_sub();
 
@@ -1393,7 +1410,8 @@ sub get_parts {
   }
 
   my $query =
-    qq|SELECT id, partnumber, description, unit, sellprice
+    qq|SELECT id, partnumber, description, unit, sellprice,
+       classification_id
        FROM parts
        WHERE $where ORDER BY $order|;
 
@@ -1406,6 +1424,8 @@ sub get_parts {
     }
 
     $j++;
+    $form->{"type_and_classific_$j"} = $::request->presenter->type_abbreviation($ref->{part_type}).
+                                       $::request->presenter->classification_abbreviation($ref->{classification_id});
     $form->{"id_$j"}          = $ref->{id};
     $form->{"partnumber_$j"}  = $ref->{partnumber};
     $form->{"description_$j"} = $ref->{description};
@@ -1733,12 +1753,14 @@ sub prepare_parts_for_printing {
   for my $i (1..$rowcount) {
     my $id = $form->{"${prefix}${i}"};
     next unless $id;
-
-    push @{ $template_arrays{part_type} },  $parts_by_id{$id}->type;
+    my $prt = $parts_by_id{$id};
+    my $type_abbr = $::request->presenter->type_abbreviation($prt->part_type);
+    push @{ $template_arrays{part_type} }, $type_abbr;
+    push @{ $template_arrays{type_and_classific}},  $type_abbr.$::request->presenter->classification_abbreviation($prt->classification_id);
   }
 
-  return %template_arrays;
   $main::lxdebug->leave_sub();
+  return %template_arrays;
 }
 
 sub normalize_text_blocks {
@@ -1760,5 +1782,4 @@ sub normalize_text_blocks {
    $main::lxdebug->leave_sub();
 }
 
-
 1;
index a0bf6cd..7518ed4 100644 (file)
--- a/SL/IR.pm
+++ b/SL/IR.pm
@@ -1020,6 +1020,7 @@ sub retrieve_invoice {
         i.description, i.longdescription, i.qty, i.fxsellprice AS sellprice, i.parts_id AS id, i.unit, i.deliverydate, i.project_id, i.serialnumber,
         i.price_factor_id, i.price_factor, i.marge_price_factor, i.discount, i.active_price_source, i.active_discount_source,
         p.partnumber, p.part_type, pr.projectnumber, pg.partsgroup
+        ,p.classification_id
 
         FROM invoice i
         JOIN parts p ON (i.parts_id = p.id)
@@ -1279,6 +1280,7 @@ sub retrieve_item {
          p.notes AS partnotes, p.notes AS longdescription, p.not_discountable,
          p.inventory_accno_id, p.price_factor_id,
          p.ean,
+         p.classification_id,
 
          pfac.factor AS price_factor,
 
@@ -1294,6 +1296,7 @@ sub retrieve_item {
          c3.new_chart_id                  AS expense_new_chart,
          date($transdate) - c3.valid_from AS expense_valid,
 
+         pt.used_for_purchase AS used_for_purchase,
          pg.partsgroup
 
        FROM parts p
@@ -1310,6 +1313,7 @@ sub retrieve_item {
            FROM taxzone_charts tc
            WHERE tc.taxzone_id = '$taxzone_id' and tc.buchungsgruppen_id = p.buchungsgruppen_id) = c3.id)
        LEFT JOIN partsgroup pg ON (pg.id = p.partsgroup_id)
+       LEFT JOIN parts_classifications pt ON (pt.id = p.classification_id)
        LEFT JOIN price_factors pfac ON (pfac.id = p.price_factor_id)
        WHERE $where|;
   my $sth = prepare_execute_query($form, $dbh, $query, @values);
@@ -1328,6 +1332,7 @@ sub retrieve_item {
   map { push @{ $_ }, prepare_query($form, $dbh, $_->[0]) } @translation_queries;
 
   $form->{item_list} = [];
+  my $has_wrong_pclass = 0;
   while (my $ref = $sth->fetchrow_hashref("NAME_lc")) {
 
     if ($mm_by_id{$ref->{id}}) {
@@ -1338,7 +1343,13 @@ sub retrieve_item {
     if (($::form->{"partnumber_$i"} ne '') && ($ref->{ean} eq $::form->{"partnumber_$i"})) {
       push @{ $ref->{matches} ||= [] }, $::locale->text('EAN') . ': ' . $ref->{ean};
     }
+    $ref->{type_and_classific} = $::request->presenter->type_abbreviation($ref->{part_type}).
+                                 $::request->presenter->classification_abbreviation($ref->{classification_id});
 
+    if (! $ref->{used_for_purchase} ) {
+       $has_wrong_pclass = 2;
+       next;
+    }
     # In der Buchungsgruppe ist immer ein Bestandskonto verknuepft, auch wenn
     # es sich um eine Dienstleistung handelt. Bei Dienstleistungen muss das
     # Buchungskonto also aus dem Ergebnis rausgenommen werden.
@@ -1408,12 +1419,13 @@ sub retrieve_item {
   $sth->finish();
   $_->[1]->finish for @translation_queries;
 
+  $form->{is_wrong_pclass} = $has_wrong_pclass;
   foreach my $item (@{ $form->{item_list} }) {
     my $custom_variables = CVar->get_custom_variables(module   => 'IC',
                                                       trans_id => $item->{id},
                                                       dbh      => $dbh,
                                                      );
-
+    $form->{is_wrong_pclass} = 0; # one correct type
     map { $item->{"ic_cvar_" . $_->{name} } = $_->{value} } @{ $custom_variables };
   }
 
index 5686947..b55cd7f 100644 (file)
--- a/SL/IS.pm
+++ b/SL/IS.pm
@@ -2003,6 +2003,7 @@ sub _retrieve_invoice {
            i.project_id, i.serialnumber, i.pricegroup_id, i.ordnumber, i.donumber, i.transdate, i.cusordnumber, i.subtotal, i.lastcost,
            i.price_factor_id, i.price_factor, i.marge_price_factor, i.active_price_source, i.active_discount_source,
            p.partnumber, p.part_type, p.notes AS partnotes, p.formel, p.listprice,
+           p.classification_id,
            pr.projectnumber, pg.partsgroup, prg.pricegroup
 
          FROM invoice i
@@ -2300,6 +2301,7 @@ sub retrieve_item {
          p.id, p.partnumber, p.description, p.sellprice,
          p.listprice, p.part_type, p.lastcost,
          p.ean, p.notes,
+         p.classification_id,
 
          c1.accno AS inventory_accno,
          c1.new_chart_id AS inventory_new_chart,
@@ -2319,7 +2321,7 @@ sub retrieve_item {
          p.price_factor_id, p.weight,
 
          pfac.factor AS price_factor,
-
+         pt.used_for_sale AS used_for_sale,
          pg.partsgroup
 
        FROM parts p
@@ -2336,6 +2338,7 @@ sub retrieve_item {
            FROM taxzone_charts tc
            WHERE tc.buchungsgruppen_id = p.buchungsgruppen_id and tc.taxzone_id = ${taxzone_id}) = c3.id)
        LEFT JOIN partsgroup pg ON (pg.id = p.partsgroup_id)
+       LEFT JOIN parts_classifications pt ON (pt.id = p.classification_id)
        LEFT JOIN price_factors pfac ON (pfac.id = p.price_factor_id)
        WHERE $where|;
   my $sth = prepare_execute_query($form, $dbh, $query, @values);
@@ -2353,6 +2356,7 @@ sub retrieve_item {
                                    LIMIT 1| ] );
   map { push @{ $_ }, prepare_query($form, $dbh, $_->[0]) } @translation_queries;
 
+  my $has_wrong_pclass = 0;
   while (my $ref = $sth->fetchrow_hashref('NAME_lc')) {
 
     if ($mm_by_id{$ref->{id}}) {
@@ -2364,6 +2368,12 @@ sub retrieve_item {
       push @{ $ref->{matches} ||= [] }, $::locale->text('EAN') . ': ' . $ref->{ean};
     }
 
+    $ref->{type_and_classific} = $::request->presenter->type_abbreviation($ref->{part_type}).
+                                 $::request->presenter->classification_abbreviation($ref->{classification_id});
+    if (! $ref->{used_for_sale} ) {
+        $has_wrong_pclass = 1;
+        next;
+    }
     # In der Buchungsgruppe ist immer ein Bestandskonto verknuepft, auch wenn
     # es sich um eine Dienstleistung handelt. Bei Dienstleistungen muss das
     # Buchungskonto also aus dem Ergebnis rausgenommen werden.
@@ -2447,15 +2457,15 @@ sub retrieve_item {
   $sth->finish;
   $_->[1]->finish for @translation_queries;
 
+  $form->{is_wrong_pclass} = $has_wrong_pclass;
   foreach my $item (@{ $form->{item_list} }) {
     my $custom_variables = CVar->get_custom_variables(module   => 'IC',
                                                       trans_id => $item->{id},
                                                       dbh      => $dbh,
                                                      );
-
+    $form->{is_wrong_pclass} = 0; # one correct type
     map { $item->{"ic_cvar_" . $_->{name} } = $_->{value} } @{ $custom_variables };
   }
-
   $main::lxdebug->leave_sub();
 }
 
index d347d5b..24e88f0 100644 (file)
--- a/SL/OE.pm
+++ b/SL/OE.pm
@@ -1094,6 +1094,7 @@ sub _retrieve {
            c3.accno AS expense_accno,   c3.new_chart_id AS expense_new_chart,   date($transdate) - c3.valid_from as expense_valid,
            oe.ordnumber AS ordnumber_oe, oe.transdate AS transdate_oe, oe.cusordnumber AS cusordnumber_oe,
            p.partnumber, p.part_type, p.listprice, o.description, o.qty,
+           p.classification_id,
            o.sellprice, o.parts_id AS id, o.unit, o.discount, p.notes AS partnotes, p.part_type,
            o.reqdate, o.project_id, o.serialnumber, o.ship, o.lastcost,
            o.ordnumber, o.transdate, o.cusordnumber, o.subtotal, o.longdescription,
index 1f96cbe..c3bbea2 100644 (file)
@@ -3,9 +3,10 @@ package SL::Presenter::Part;
 use strict;
 
 use SL::DB::Part;
+use SL::DB::Manager::PartsClassification;
 
 use Exporter qw(import);
-our @EXPORT = qw(part_picker part);
+our @EXPORT = qw(part_picker part select_classification classification_abbreviation type_abbreviation type_and_classification);
 
 use Carp;
 
@@ -46,6 +47,48 @@ sub part_picker {
   $self->html_tag('span', $ret, class => 'part_picker');
 }
 
+#
+# Must be addapted to new type table
+#
+sub type_abbreviation {
+  my ($self, $part_type) = @_;
+  $main::lxdebug->message(LXDebug->DEBUG2(),"parttype=".$part_type);
+  return $::locale->text('Assembly (typeabbreviation)')   if $part_type eq 'assembly';
+  return $::locale->text('Part (typeabbreviation)')       if $part_type eq 'part';
+  return $::locale->text('Assortment (typeabbreviation)') if $part_type eq 'assortment';
+  return $::locale->text('Service (typeabbreviation)');
+}
+
+#
+# Translations for Abbreviations:
+#
+# $::locale->text('None (typeabbreviation)')
+# $::locale->text('Purchase (typeabbreviation)')
+# $::locale->text('Sales (typeabbreviation)')
+# $::locale->text('Merchandise (typeabbreviation)')
+# $::locale->text('Production (typeabbreviation)')
+#
+# and for descriptions
+# $::locale->text('Purchase')
+# $::locale->text('Sales')
+# $::locale->text('Merchandise')
+# $::locale->text('Production')
+
+sub classification_abbreviation {
+  my ($self, $id) = @_;
+  $main::lxdebug->message(LXDebug->DEBUG2(),"classif=".$id);
+  return $::locale->text(SL::DB::Manager::PartsClassification->get_abbreviation($id));
+}
+
+sub select_classification {
+  my ($self, $name, %attributes) = @_;
+  $attributes{value_key} = 'id';
+  $attributes{title_key} = 'description';
+  my $collection = SL::DB::Manager::PartsClassification->get_all_sorted();
+  $_->description($::locale->text($_->description)) for @{ $collection };
+  return $self->select_tag( $name, $collection, %attributes );
+}
+
 1;
 
 __END__
index 89a937f..eaba553 100644 (file)
--- a/SL/WH.pm
+++ b/SL/WH.pm
@@ -368,6 +368,11 @@ sub get_warehouse_journal {
     push @filter_vars, like($filter{description});
   }
 
+  if ($filter{classification_id}) {
+    push @filter_ary, "p.classification_id = ?";
+    push @filter_vars, $filter{classification_id};
+  }
+
   if ($filter{chargenumber}) {
     push @filter_ary, "i1.chargenumber ILIKE ?";
     push @filter_vars, like($filter{chargenumber});
@@ -426,6 +431,9 @@ sub get_warehouse_journal {
      "qty"                  => "ABS(SUM(i1.qty))",
      "partnumber"           => "p.partnumber",
      "partdescription"      => "p.description",
+     "classification_id"    => "p.classification_id",
+     "assembly"             => "p.assembly",
+     "inventory_accno_id"   => "p.inventory_accno_id",
      "bindescription"       => "b.description",
      "chargenumber"         => "i1.chargenumber",
      "bestbefore"           => "i1.bestbefore",
@@ -457,6 +465,9 @@ sub get_warehouse_journal {
      "warehouse_from"       => "'$filter{na}'",
      };
 
+  $form->{l_classification_id}  = 'Y';
+  $form->{l_assembly}           = 'Y';
+  $form->{l_inventory_accno_id} = 'Y';
   $form->{l_invoice_id} = $form->{l_oe_id} if $form->{l_oe_id};
 
   # build the select clauses.
@@ -614,6 +625,7 @@ SQL
 #  - warehouse_id - will return matches with this warehouse_id only
 #  - partnumber   - will return only matches where the given string is a substring of the partnumber
 #  - partsid      - will return matches with this parts_id only
+#  - classification_id - will return matches with this parts with this classification only
 #  - description  - will return only matches where the given string is a substring of the description
 #  - chargenumber - will return only matches where the given string is a substring of the chargenumber
 #  - bestbefore   - will return only matches with this bestbefore date
@@ -665,6 +677,11 @@ sub get_warehouse_report {
     push @filter_vars, like($filter{partnumber});
   }
 
+  if ($filter{classification_id}) {
+    push @filter_ary, "p.classification_id = ?";
+    push @filter_vars, $filter{classification_id};
+  }
+
   if ($filter{description}) {
     push @filter_ary,  "p.description ILIKE ?";
     push @filter_vars, like($filter{description});
@@ -737,6 +754,9 @@ sub get_warehouse_report {
      "warehouseid"          => "i.warehouse_id",
      "partnumber"           => "p.partnumber",
      "partdescription"      => "p.description",
+     "classification_id"    => "p.classification_id",
+     "assembly"             => "p.assembly",
+     "inventory_accno_id"   => "p.inventory_accno_id",
      "bindescription"       => "b.description",
      "binid"                => "b.id",
      "chargenumber"         => "i.chargenumber",
@@ -747,6 +767,9 @@ sub get_warehouse_report {
      "partunit"             => "p.unit",
      "stock_value"          => "p.lastcost / COALESCE(pfac.factor, 1)",
   );
+  $form->{l_classification_id}  = 'Y';
+  $form->{l_assembly}           = 'Y';
+  $form->{l_inventory_accno_id} = 'Y';
   my $select_clause = join ', ', map { +/^l_/; "$select_tokens{$'} AS $'" }
         ( grep( { !/qty/ and /^l_/ and $form->{$_} eq 'Y' } keys %$form),
           qw(l_parts_id l_qty l_partunit) );
index d3cd910..fdd7a82 100644 (file)
@@ -100,11 +100,12 @@ sub search {
   $form->{lastsort}     = ""; # memory for which table was sort at last time
   $form->{ndxs_counter} = 0;  # counter for added entries to top100
 
-  my %is_xyz     = map { +"is_$_" => ($form->{searchitems} eq $_) } qw(part service assembly);
+  # for seach all possibibilities, is_service only used as UNLESS so == 0
+  my %is_xyz     = ("is_part" => 1, "is_service" => 0, "is_assembly" =>1 );
 
   $form->{title} = (ucfirst $form->{searchitems}) . "s";
+  $form->{title} =~ s/ys$/ies/;
   $form->{title} = $locale->text($form->{title});
-  $form->{title} = $locale->text('Assemblies') if ($is_xyz{is_assembly});
 
   $form->{CUSTOM_VARIABLES}                  = CVar->get_configs('module' => 'IC');
   ($form->{CUSTOM_VARIABLES_FILTER_CODE},
@@ -233,7 +234,7 @@ sub top100 {
 #  searchitems=part revers=0 lastsort=''
 #
 # filter:
-# partnumber ean description partsgroup serialnumber make model drawing microfiche
+# partnumber ean description partsgroup classification serialnumber make model drawing microfiche
 # transdatefrom transdateto
 #
 # radio:
@@ -300,6 +301,7 @@ sub generate_report {
     'unit'               => { 'text' => $locale->text('Unit'), },
     'weight'             => { 'text' => $locale->text('Weight'), },
     'shop'               => { 'text' => $locale->text('Shop article'), },
+    'type_and_classific' => { 'text' => $locale->text('Type'), },
     'projectnumber'      => { 'text' => $locale->text('Project Number'), },
     'projectdescription' => { 'text' => $locale->text('Project Description'), },
   );
@@ -424,8 +426,9 @@ sub generate_report {
     $form->{l_linetotallastcost}  = $form->{searchitems} eq 'assembly' && !$form->{bom} ? "" : 'Y' if  $form->{l_lastcost};
     $form->{l_linetotallistprice} = "Y" if $form->{l_listprice};
   }
+  $form->{"l_type_and_classific"} = "Y";
 
-  if ($form->{searchitems} eq 'service') {
+   if ($form->{l_service} && !$form->{l_assembly} && !$form->{l_part}) {
 
     # remove bin, weight and rop from list
     map { $form->{"l_$_"} = "" } qw(bin weight rop);
@@ -472,7 +475,7 @@ sub generate_report {
   IC->all_parts(\%myconfig, \%$form);
 
   my @columns = qw(
-    partnumber description notes partsgroup bin onhand rop soldtotal unit listprice
+    partnumber type_and_classific description notes partsgroup bin onhand rop soldtotal unit listprice
     linetotallistprice sellprice linetotalsellprice lastcost linetotallastcost
     priceupdate weight image drawing microfiche invnumber ordnumber quonumber
     transdate name serialnumber deliverydate ean projectnumber projectdescription
@@ -505,6 +508,7 @@ sub generate_report {
 
   my @hidden_variables = (
     qw(l_subtotal l_linetotal searchitems itemstatus bom l_pricegroups insertdatefrom insertdateto),
+    qw(l_type_and_classific classification_id),
     @itemstatus_keys,
     @callback_keys,
     map({ "cvar_$_->{name}" } @searchable_custom_variables),
@@ -531,13 +535,14 @@ sub generate_report {
     'part'     => $locale->text('part_list'),
     'service'  => $locale->text('service_list'),
     'assembly' => $locale->text('assembly_list'),
+    'article'  => $locale->text('article_list'),
   );
 
   $report->set_options('raw_top_info_text'     => $form->parse_html_template('ic/generate_report_top', { options => \@options }),
                        'raw_bottom_info_text'  => $form->parse_html_template('ic/generate_report_bottom'),
                        'output_format'         => 'HTML',
                        'title'                 => $form->{title},
-                       'attachment_basename'   => $attachment_basenames{$form->{searchitems}} . strftime('_%Y%m%d', localtime time),
+                       'attachment_basename'   => 'article_list' . strftime('_%Y%m%d', localtime time),
   );
   $report->set_options_from_form();
   $locale->set_numberformat_wo_thousands_separator(\%myconfig) if lc($report->{options}->{output_format}) eq 'csv';
@@ -647,6 +652,8 @@ sub generate_report {
     map { $row->{$_}{link} = $ref->{$_} } qw(drawing microfiche);
 
     $row->{notes}{data} = SL::HTML::Util->strip($ref->{notes});
+    $row->{type_and_classific}{data} = $::request->presenter->type_abbreviation($ref->{part_type}).
+                                       $::request->presenter->classification_abbreviation($ref->{classification_id});
 
     $report->add_data($row);
 
@@ -658,7 +665,7 @@ sub generate_report {
          (!$next_ref->{assemblyitem} && ($same_item ne $next_ref->{ $form->{sort} })))) {
       my $row = { map { $_ => { 'class' => 'listsubtotal', } } @columns };
 
-      if (($form->{searchitems} ne 'assembly') || !$form->{bom}) {
+      if ( !$form->{l_assembly} || !$form->{bom}) {
         $row->{soldtotal}->{data} = $form->format_amount(\%myconfig, $subtotals{soldtotal});
       }
 
@@ -696,7 +703,7 @@ sub parts_subtotal {
   my ($column_index, $subtotalonhand, $subtotalsellprice, $subtotallastcost, $subtotallistprice) = @_;
 
   map { $column_data{$_} = "<td>&nbsp;</td>" } @{ $column_index };
-  $$subtotalonhand = 0 if ($form->{searchitems} eq 'assembly' && $form->{bom});
+  $$subtotalonhand = 0 if ($form->{l_assembly} && $form->{bom});
 
   $column_data{onhand} =
       "<th class=listsubtotal align=right>"
@@ -938,11 +945,11 @@ sub assembly_row {
   my (@column_index);
   my ($nochange, $callback, $previousform, $linetotal, $line_purchase_price, $href);
 
-  @column_index = qw(runningnumber qty unit bom partnumber description partsgroup lastcost total);
+  @column_index = qw(runningnumber qty unit bom partnumber type_and_classific description partsgroup lastcost total);
 
   if ($form->{previousform}) {
     $nochange     = 1;
-    @column_index = qw(qty unit bom partnumber description partsgroup total);
+    @column_index = qw(qty unit bom partnumber type_and_classific description partsgroup total);
   } else {
 
     # change callback
@@ -965,15 +972,16 @@ sub assembly_row {
   }
 
   my %header = (
-   runningnumber => { text =>  $locale->text('No.'),              nowrap => 1, width => '5%',  align => 'left',},
-   qty           => { text =>  $locale->text('Qty'),              nowrap => 1, width => '10%', align => 'left',},
-   unit          => { text =>  $locale->text('Unit'),             nowrap => 1, width => '5%',  align => 'left',},
-   partnumber    => { text =>  $locale->text('Part Number'),      nowrap => 1, width => '20%', align => 'left',},
-   description   => { text =>  $locale->text('Part Description'), nowrap => 1, width => '50%', align => 'left',},
-   lastcost      => { text =>  $locale->text('Purchase Prices'),  nowrap => 1, width => '50%', align => 'right',},
-   total         => { text =>  $locale->text('Sale Prices'),      nowrap => 1,                 align => 'right',},
-   bom           => { text =>  $locale->text('BOM'),                                           align => 'center',},
-   partsgroup    => { text =>  $locale->text('Group'),                                         align => 'left',},
+   runningnumber      => { text =>  $locale->text('No.'),              nowrap => 1, width => '5%',  align => 'left',},
+   qty                => { text =>  $locale->text('Qty'),              nowrap => 1, width => '10%', align => 'left',},
+   unit               => { text =>  $locale->text('Unit'),             nowrap => 1, width => '5%',  align => 'left',},
+   partnumber         => { text =>  $locale->text('Part Number'),      nowrap => 1, width => '20%', align => 'left',},
+   type_and_classific => { text =>  $locale->text('Typ'),              nowrap => 1, width => '5%' , align => 'left',},
+   description        => { text =>  $locale->text('Part Description'), nowrap => 1, width => '50%', align => 'left',},
+   lastcost           => { text =>  $locale->text('Purchase Prices'),  nowrap => 1, width => '45%', align => 'right',},
+   total              => { text =>  $locale->text('Sale Prices'),      nowrap => 1,                 align => 'right',},
+   bom                => { text =>  $locale->text('BOM'),                                           align => 'center',},
+   partsgroup         => { text =>  $locale->text('Group'),                                         align => 'left',},
   );
 
   my @ROWS;
@@ -991,7 +999,7 @@ sub assembly_row {
     $linetotal           = $form->format_amount(\%myconfig, $linetotal, 2);
     $line_purchase_price = $form->format_amount(\%myconfig, $line_purchase_price, 2);
     $href                = build_std_url("action=edit", qq|id=$form->{"id_$i"}|, "rowcount=$numrows", "currow=$i", "previousform=$previousform");
-    map { $row{$_}{data} = "" } qw(qty unit partnumber description bom partsgroup runningnumber);
+    map { $row{$_}{data} = "" } qw(qty unit partnumber typ_and_class description bom partsgroup runningnumber);
 
     # last row
     if (($i >= 1) && ($i == $numrows)) {
@@ -1011,12 +1019,15 @@ sub assembly_row {
         $row{qty}{align}          = 'right';
       } else {
         $row{partnumber}{data}    = qq|$form->{"partnumber_$i"}|;
-        $row{partnumber}{link}     = $href;
+        $row{partnumber}{link}    = $href;
         $row{qty}{data}           = qq|<input name="qty_$i" size=5 value="$form->{"qty_$i"}">|;
         $row{runningnumber}{data} = qq|<input name="runningnumber_$i" size=3 value="$i">|;
         $row{bom}{data}   = sprintf qq|<input name="bom_$i" type=checkbox class=checkbox value=1 %s>|,
                                        $form->{"bom_$i"} ? 'checked' : '';
       }
+      # type impl $row{type_and_classific}{data} = $::request->presenter->type_abbreviation($form->{"type_$i"}).
+      $row{type_and_classific}{data} = $::request->presenter->type_abbreviation($form->{"assembly_$i"},$form->{"inventory_accno_id_$i"}).
+                                       $::request->presenter->classification_abbreviation($form->{"classification_id_$i"});
       push @row_hiddens,        qw(unit description partnumber partsgroup);
       $row{unit}{data}        = $form->{"unit_$i"};
       #Bei der Artikelbeschreibung und Warengruppe können Sonderzeichen verwendet
index 29ddae3..5a154e0 100644 (file)
@@ -159,7 +159,7 @@ sub display_row {
 
   # column_index
   my @header_sort = qw(
-    runningnumber partnumber description ship ship_missing qty price_factor
+    runningnumber partnumber type_and_classific description ship ship_missing qty price_factor
     unit weight price_source sellprice discount linetotal
     bin stock_in_out
   );
@@ -169,6 +169,8 @@ sub display_row {
   my %column_def = (
     runningnumber => { width => 5,     value => $locale->text('No.'),                  display => 1, },
     partnumber    => { width => 8,     value => $locale->text('Number'),               display => 1, },
+    type_and_classific
+                  => { width => 2,     value => $locale->text('Type'),                 display => 1, },
     description   => { width => 30,    value => $locale->text('Part Description'),     display => 1, },
     ship          => { width => 5,     value => $locale->text('Delivered'),            display => $is_s_p_order, },
     ship_missing  => { width => 5,     value => $locale->text('Not delivered'),        display => $show_ship_missing, },
@@ -295,12 +297,15 @@ sub display_row {
     my $rows            = $form->numtextrows($form->{"description_$i"}, 30, 6);
 
     # quick delete single row
-    $column_data{runningnumber} .= q|<a onclick= "$('#partnumber_| . $i . q|').val(''); $('#update_button').click();">| .
+    $column_data{runningnumber}  = q|<a onclick= "$('#partnumber_| . $i . q|').val(''); $('#update_button').click();">| .
                                    q|<img height="10px" width="10px" src="image/cross.png" alt="| . $locale->text('Remove') . q|"></a> |;
     $column_data{runningnumber} .= $cgi->textfield(-name => "runningnumber_$i", -id => "runningnumber_$i", -size => 5,  -value => $i);    # HuT
 
 
     $column_data{partnumber}    = $cgi->textfield(-name => "partnumber_$i",    -id => "partnumber_$i",    -size => 12, -value => $form->{"partnumber_$i"});
+   # type impl $column_data{type_and_classific} = $::request->presenter->type_abbreviation(($form->{"type_$i"}).
+    $column_data{type_and_classific} = $::request->presenter->type_abbreviation($form->{"assembly_$i"},$form->{"inventory_accno_id_$i"}).
+                                       $::request->presenter->classification_abbreviation($form->{"classification_id_$i"}) if $form->{"id_$i"};
     $column_data{description} = (($rows > 1) # if description is too large, use a textbox instead
                                 ? $cgi->textarea( -name => "description_$i", -id => "description_$i", -default => $form->{"description_$i"}, -rows => $rows, -columns => 30)
                                 : $cgi->textfield(-name => "description_$i", -id => "description_$i",   -value => $form->{"description_$i"}, -size => 30))
@@ -665,7 +670,6 @@ sub item_selected {
     map { $amount += ($form->{"${_}_base"} * $form->{"${_}_rate"}) } split / /, $form->{"taxaccounts_$i"} if !$form->{taxincluded};
 
     $form->{creditremaining} -= $amount;
-
     $form->{"runningnumber_$i"} = $i;
 
     # format amounts
index ab88983..66594ff 100644 (file)
@@ -677,10 +677,10 @@ sub generate_journal {
   $form->{report_generator_output_format} = 'HTML' if !$form->{report_generator_output_format};
 
   my %filter;
-  my @columns = qw(trans_id date warehouse_from bin_from warehouse_to bin_to partnumber partdescription chargenumber bestbefore trans_type comment qty employee oe_id projectnumber);
+  my @columns = qw(trans_id date warehouse_from bin_from warehouse_to bin_to partnumber type_and_classific partdescription chargenumber bestbefore trans_type comment qty unit partunit employee oe_id projectnumber);
 
   # filter stuff
-  map { $filter{$_} = $form->{$_} if ($form->{$_}) } qw(warehouse_id bin_id partnumber description chargenumber bestbefore);
+  map { $filter{$_} = $form->{$_} if ($form->{$_}) } qw(warehouse_id bin_id classification_id partnumber description chargenumber bestbefore);
 
   $filter{qty_op} = WH->convert_qty_op($form->{qty_op});
   if ($filter{qty_op}) {
@@ -696,6 +696,7 @@ sub generate_journal {
 
   my @hidden_variables = map { "l_${_}" } @columns;
   push @hidden_variables, qw(warehouse_id bin_id partnumber description chargenumber bestbefore qty_op qty qty_unit fromdate todate);
+  push @hidden_variables, qw(classification_id);
 
   my %column_defs = (
     'date'            => { 'text' => $locale->text('Date'), },
@@ -707,6 +708,8 @@ sub generate_journal {
     'bin_from'        => { 'text' => $locale->text('Bin From'), },
     'bin_to'          => { 'text' => $locale->text('Bin To'), },
     'partnumber'      => { 'text' => $locale->text('Part Number'), },
+    'type_and_classific'
+                      => { 'text' => $locale->text('Type'), },
     'partdescription' => { 'text' => $locale->text('Part Description'), },
     'chargenumber'    => { 'text' => $locale->text('Charge Number'), },
     'bestbefore'      => { 'text' => $locale->text('Best Before'), },
@@ -725,6 +728,8 @@ sub generate_journal {
   my %column_alignment = map { $_ => 'right' } qw(qty);
 
   map { $column_defs{$_}->{visible} = $form->{"l_${_}"} ? 1 : 0 } @columns;
+  $column_defs{type_and_classific}->{visible} = 1;
+  $column_defs{type_and_classific}->{link} ='';
 
   $report->set_columns(%column_defs);
   $report->set_column_order(@columns);
@@ -763,6 +768,9 @@ sub generate_journal {
    my $idx       = 0;
 
   foreach my $entry (@contents) {
+    # type impl $entry->{type_and_classific} = $::request->presenter->type_abbreviation($entry->{type}).
+    $entry->{type_and_classific} = $::request->presenter->type_abbreviation($entry->{assembly},$entry->{inventory_accno_id}).
+                                   $::request->presenter->classification_abbreviation($entry->{classification_id});
     $entry->{qty}        = $form->format_amount_units('amount'     => $entry->{qty},
                                                       'part_unit'  => $entry->{partunit},
                                                       'conv_units' => 'convertible');
@@ -852,13 +860,16 @@ sub generate_report {
   my $sort_col     = $form->{sort};
 
   my %filter;
-  my @columns = qw(warehousedescription bindescription partnumber partdescription chargenumber bestbefore qty stock_value);
+  my @columns = qw(warehousedescription bindescription partnumber type_and_classific partdescription chargenumber bestbefore comment qty partunit stock_value);
 
   # filter stuff
-  map { $filter{$_} = $form->{$_} if ($form->{$_}) } qw(warehouse_id bin_id partstypes_id partnumber description chargenumber bestbefore date include_invalid_warehouses);
+  map { $filter{$_} = $form->{$_} if ($form->{$_}) } qw(warehouse_id bin_id classification_id partnumber description chargenumber bestbefore date include_invalid_warehouses);
 
   # show filter stuff also in report
   my @options;
+  my $currentdate = $form->current_date(\%myconfig);
+  push @options, $locale->text('Printdate') . " : ".$locale->date(\%myconfig, $currentdate, 1);
+
   # dispatch all options
   my $dispatch_options = {
    warehouse_id   => sub { push @options, $locale->text('Warehouse') . " : " .
@@ -866,15 +877,18 @@ sub generate_report {
    bin_id         => sub { push @options, $locale->text('Bin') . " : " .
                                             SL::DB::Manager::Bin->find_by(id => $form->{bin_id})->description},
    partnumber     => sub { push @options, $locale->text('Partnumber')     . " : $form->{partnumber}"},
+   classification_id => sub { push @options, $locale->text('Parts Classification'). " : ".
+     SL::DB::Manager::PartsClassification->get_first(where => [ id => $form->{classification_id} ] )->description; },
    description    => sub { push @options, $locale->text('Description')    . " : $form->{description}"},
    chargenumber   => sub { push @options, $locale->text('Charge Number')  . " : $form->{chargenumber}"},
    bestbefore     => sub { push @options, $locale->text('Best Before')    . " : $form->{bestbefore}"},
-   date           => sub { push @options, $locale->text('Date')           . " : $form->{date}"},
    include_invalid_warehouses    => sub { push @options, $locale->text('Include invalid warehouses ')},
   };
   foreach (keys %filter) {
    $dispatch_options->{$_}->() if $dispatch_options->{$_};
   }
+  push @options, $locale->text('Stock Qty for Date') . " " . $locale->date(\%myconfig, $form->{date}?$form->{date}:$currentdate, 1);
+
   # / end show filter stuff also in report
 
   $filter{qty_op} = WH->convert_qty_op($form->{qty_op});
@@ -895,11 +909,13 @@ sub generate_report {
   my @hidden_variables = map { "l_${_}" } @columns;
   push @hidden_variables, qw(warehouse_id bin_id partnumber partstypes_id description chargenumber bestbefore qty_op qty qty_unit partunit l_warehousedescription l_bindescription);
   push @hidden_variables, qw(include_empty_bins subtotal include_invalid_warehouses date);
+  push @hidden_variables, qw(classification_id);
 
   my %column_defs = (
     'warehousedescription' => { 'text' => $locale->text('Warehouse'), },
     'bindescription'       => { 'text' => $locale->text('Bin'), },
     'partnumber'           => { 'text' => $locale->text('Part Number'), },
+    'type_and_classific'   => { 'text' => $locale->text('Type'), },
     'partdescription'      => { 'text' => $locale->text('Part Description'), },
     'chargenumber'         => { 'text' => $locale->text('Charge Number'), },
     'bestbefore'           => { 'text' => $locale->text('Best Before'), },
@@ -916,6 +932,9 @@ sub generate_report {
 
   map { $column_defs{$_}->{visible} = $form->{"l_${_}"} ? 1 : 0 } @columns;
 
+  $column_defs{type_and_classific}->{visible} = 1;
+  $column_defs{type_and_classific}->{link} ='';
+
   $report->set_columns(%column_defs);
   $report->set_column_order(@columns);
 
@@ -950,6 +969,10 @@ sub generate_report {
   my $last_nr  = $first_nr + $pages->{per_page};
 
   foreach my $entry (@contents) {
+
+    # type impl $entry->{type_and_classific} = $::request->presenter->type_abbreviation($entry->{type}).
+    $entry->{type_and_classific} = $::request->presenter->type_abbreviation($entry->{assembly},$entry->{inventory_accno_id}).
+                                   $::request->presenter->classification_abbreviation($entry->{classification_id});
     map { $subtotals{$_} += $entry->{$_} } @subtotals_columns;
     $total_stock_value   += $entry->{stock_value} * 1;
     $entry->{qty}         = $form->format_amount(\%myconfig, $entry->{qty});
index 0f78189..12327db 100644 (file)
@@ -4,6 +4,39 @@
 
 2016-xx-xx - Release 3.4.x Unstable
 
+große Features:
+
+- Artikel-Klassifizierung
+    
+    Die Klassifizierung von Artikeln
+    Sie dient einer weiteren Gliederung um zum Beispiel den Einkauf vom Verkauf zu trennen, etc.
+    
+    Gekennzeichnet durch eine Beschreibung (z.B. "Einkauf") und ein Kürzel (z.B. "E")
+    Flexibel änderbar und erweiterbar.
+    
+    - Neue Datenbanktablle und Rose-Objekte, sowie Controller zum Bearbeiten der Tabelle
+    
+    - Zwei-Zeichen Abkürzung:
+    
+    Der Typ des Artikel und die Klassifizierung werden durch zwei Buchstaben dargestellt.
+    Der erste Buchstabe  ist eine Lokalisierung des Typs des Artikel ('P','A','S') ,
+    deutch 'W', 'E', und 'D' für Ware Erzeugnis oder Dienstleistung, ggf. weitere Typen.
+    Der zweite Buchstabe ist eine Lokalisierung der Klassifizierungsabkürzung (abbreviation).
+    
+    Die Abkürzungen sind aus dem Part Presenter abholbar:
+    -  SL::Presenter::Part->type_abbreviation($assembly,inventory_accno_id) bzw
+        bei neuer Type Implementierung SL::Presenter::Part->type_abbreviation($type_id)
+    -  SL::Presenter::Part->classification_abbreviation($classification_id)
+    
+    Wenn im ERP-Dokument nach einer Artikelnummer oder Beschreibung gesucht wird,
+    diese in den Stammdaten vorhanden ist,
+    aber der Artikeltyp leer oder falsch ist, bzw im Typ for_purchase bzw for_sale nicht gesetzt ist,
+    wird die Fehlermeldung "Gesuchter Artikel ist nicht für den Einkauf bzw Verkauf" gemeldet
+    
+    Anpassung des CSV Import,
+    nun wird alternativ zur 'type'-Spalte die 'pclass'-Spalte mit zwei Buchstaben geparsed und entsprechend
+    classification_id,assembly sowie inventory_accno_id gesetzt (oder type_id falls neue Implementierung eingebaut).
+
 kleinere neue Features und Detailverbesserungen:
 
   - SEPA Überweisungen zusätzlich Kunden- oder Lieferantennummer im Verwendungszweck vorbelegen
index b241279..3f837bb 100755 (executable)
@@ -297,12 +297,13 @@ $self->{texts} = {
   'Article'                     => 'Artikel',
   'Article Code'                => 'Artikelkürzel',
   'Article Code missing!'       => 'Artikelkürzel fehlt',
+  'Article classification'      => 'Artikel-Klassifizierung',
   'Article type'                => 'Artikeltyp',
   'Articles'                    => 'Artikel',
   'As a result, the saved onhand values of the present goods can be stored into a warehouse designated by you, or will be reset for a proper warehouse tracking' => 'Als Konsequenz k&ouml;nnen die gespeicherten Mengen entweder in ein Lager &uuml;berf&uuml;hrt werden, oder f&uuml;r eine frische Lagerverwaltung resettet werden.',
   'Assemblies'                  => 'Erzeugnisse',
-  'Assemblies can not be imported (yet). But the type column is used for sanity checks on price updates in order to prevent that articles with the wrong type will be updated.' => 'Erzeugnisse können (noch) nicht importiert werden. Aber die Typ-Spalte wird für Plausibilitätsprüfungen bei Preisaktualisierungen verwendet, um zu verhindern, dass Artikel vom falschen Typ aktualisiert werden.',
   'Assembly'                    => 'Erzeugnis',
+  'Assembly (typeabbreviation)' => 'E',
   'Assembly Description'        => 'Erzeugnis-Beschreibung',
   'Assembly Number'             => 'Erzeugnis-Nummer',
   'Assembly Number missing!'    => 'Erzeugnisnummer fehlt!',
@@ -662,6 +663,7 @@ $self->{texts} = {
   'Create a new delivery term'  => 'Neue Lieferbedingungen anlegen',
   'Create a new department'     => 'Eine neue Abteilung erfassen',
   'Create a new group'          => 'Neue Benutzergruppe erfassen',
+  'Create a new parts classification' => 'Erzeuge eine neue Artikel-Klassifizierung',
   'Create a new payment term'   => 'Neue Zahlungsbedingungen anlegen',
   'Create a new predefined text' => 'Einen neuen vordefinierten Textblock anlegen',
   'Create a new price rule'     => 'Neue Preisregel anlegen',
@@ -1108,6 +1110,7 @@ $self->{texts} = {
   'Edit general settings'       => 'Grundeinstellungen bearbeiten',
   'Edit greetings'              => 'Anreden bearbeiten',
   'Edit note'                   => 'Notiz bearbeiten',
+  'Edit parts classification'   => 'Bearbeite eine Artikel-Klassifizierung',
   'Edit payment term'           => 'Zahlungsbedingungen bearbeiten',
   'Edit picture'                => 'Bild bearbeiten',
   'Edit predefined text'        => 'Vordefinierten Textblock bearbeiten',
@@ -1444,6 +1447,7 @@ $self->{texts} = {
   'If all of the following match' => 'Wenn alle der folgenden Bedingungen zutreffen',
   'If amounts differ more than "Maximal amount difference" (see settings), this item is marked as invalid.' => 'Weichen die Beträge mehr als die "maximale Betragsabweichung" (siehe Einstellungen) ab, so wird diese Position als ungültig markiert.',
   'If checked the taxkey will not be exported in the DATEV Export, but only IF chart taxkeys differ from general ledger taxkeys' => 'Falls angehakt wird der DATEV-Steuerschlüssel bei Buchungen auf dieses Konto nicht beim DATEV-Export mitexportiert, allerdings nur wenn zusätzlich der Konto-Steuerschlüssel vom Buchungs (Hauptbuch) Steuerschlüssel abweicht',
+  'If column \'pclass\' is present the article type is then irrelevant or used as default ' => 'Falls Spalte \'pclass\' existiert, wird dies nur als default genommen',
   'If configured this bin will be preselected for all new parts. Also this bin will be used as the master default bin, if default transfer out with master bin is activated.' => 'Falls konfiguriert, wird dieses Lager mit Lagerplatz für neu angelegte Waren vorausgewählt.',
   'If disabled purchase delivery orders can only be created by conversion from existing requests for quotations and purchase orders.' => 'Falls deaktiviert, so können Einkaufslieferscheine nur durch Umwandlung aus bestehenden Preisanfragen und Lieferantenaufträgen angelegt werden.',
   'If disabled purchase invoices can only be created by conversion from existing requests for quotations, purchase orders and purchase delivery orders.' => 'Falls deaktiviert, so können Einkaufsrechnungen nur durch Umwandlung aus bestehenden Preisanfragen, Lieferantenaufträgen und Einkaufslieferscheinen angelegt werden.',
@@ -1455,7 +1459,7 @@ $self->{texts} = {
   'If enabled purchase and sales records cannot be saved if no transaction description has been entered.' => 'Wenn angeschaltet, so können Einkaufs- und Verkaufsbelege nicht gespeichert werden, solange keine Vorgangsbezeichnung eingegeben wurde.',
   'If left empty the default sender from the kivitendo configuration will be used (key \'email_from\' in section \'periodic_invoices\'; current value: #1).' => 'Falls leer, so wird der Standardabsender aus der kivitendo-Konfiguration genutzt (Schlüssel »email_from« in Abschnitt »periodic_invoices«; aktueller Wert: #1).',
   'If missing then the start date will be used.' => 'Falls es fehlt, so wird die erste Rechnung für das Startdatum erzeugt.',
-  'If the article type is set to \'mixed\' then a column called \'type\' must be present.' => 'Falls der Artikeltyp auf \'gemischt\' gestellt wird, muss eine Spalte namens \'type\' vorhanden sein.',
+  'If the article type is set to \'mixed\' then a column called \'type\' or called \'pclass\' must be present.' => 'Falls der Artikeltyp auf \'mixed\' eingestellt muß entwder die Spale \'type\' oder die Spalte \'pclass\' vorhanden sein',
   'If the automatic creation of invoices for fees and interest is switched on for a dunning level then the following accounts will be used for the invoice.' => 'Wenn das automatische Erstellen einer Rechnung &uuml;ber Mahngeb&uuml;hren und Zinsen f&uuml;r ein Mahnlevel aktiviert ist, so werden die folgenden Konten f&uuml;r die Rechnung benutzt.',
   'If the database user listed above does not have the right to create a database then enter the name and password of the superuser below:' => 'Falls der oben genannte Datenbankbenutzer nicht die Berechtigung zum Anlegen neuer Datenbanken hat, so k&ouml;nnen Sie hier den Namen und das Passwort des Datenbankadministratoraccounts angeben:',
   'If the default transfer out always succeed use this bin for negative stock quantity.' => 'Standardlagerplatz für Auslagern ohne Prüfung auf Bestand',
@@ -1572,9 +1576,9 @@ $self->{texts} = {
   'It will simply set the taxkey to 0 (meaning "no taxes") which is the correct value for such inventory transactions.' => 'Es wird einfach die Steuerschlüssel auf  0 setzen, was "keine Steuer" bedeutet und für solche Warenbestandsbuchungen der richtige Wert ist.',
   'Italy'                       => 'Italien',
   'Item deleted!'               => 'Artikel gelöscht!',
+  'Item does not exists in File' => 'Gesuchter Artikel nicht in den Stammdaten vorhanden.',
   'Item mode'                   => 'Artikelmodus',
   'Item multi selection with qty' => 'Artikel-Mehrfachauswahl mit Menge',
-  'Item not on file!'           => 'Dieser Artikel ist nicht in der Datenbank!',
   'Item values'                 => 'Artikelwerte',
   'Item variables'              => 'Artikelvariablen',
   'Jahresverkehrszahlen neu'    => 'Jahresverkehrszahlen neu',
@@ -1735,6 +1739,8 @@ $self->{texts} = {
   'Media \'#1\' is not supported yet/anymore.' => 'Das Medium \'#1\' wird noch nicht oder nicht mehr unterstützt.',
   'Medium Number'               => 'Datentr&auml;gernummer',
   'Memo'                        => 'Memo',
+  'Merchandise'                 => 'Handelsware',
+  'Merchandise (typeabbreviation)' => 'H',
   'Message'                     => 'Nachricht',
   'Method'                      => 'Verfahren',
   'Microfiche'                  => 'Mikrofilm',
@@ -1751,7 +1757,7 @@ $self->{texts} = {
   'Missing taxkeys in invoices with taxes.' => 'Fehlende Steuerschl&uuml;ssel in Rechnungen mit Steuern',
   'Missing transport cost: #1  Are you sure?' => 'Fehlender Transportkosten-Artikel #1 Trotzdem speichern?',
   'Mitarbeiter'                 => 'Mitarbeiter',
-  'Mixed (requires column "type")' => 'Gemischt (erfordert Spalte "type")',
+  'Mixed (requires column "type" or "pclass")' => 'Gemischt (Spalte "type" oder "pclass" notwendig',
   'Mobile'                      => 'Mobiltelefon',
   'Mobile1'                     => 'Mobil 1',
   'Mobile2'                     => 'Mobil 2',
@@ -1789,7 +1795,6 @@ $self->{texts} = {
   'New Password'                => 'Neues Passwort',
   'New Purchase Price Rule'     => 'Neue Einkaufspreisregel',
   'New Sales Price Rule'        => 'Neue Verkaufspreisregel',
-  'New assembly'                => 'Neues Erzeugnis',
   'New client #1: The database configuration fields "host", "port", "name" and "user" must not be empty.' => 'Neuer Mandant #1: Die Datenbankkonfigurationsfelder "Host", "Port" und "Name" dürfen nicht leer sein.',
   'New client #1: The name must be unique and not empty.' => 'Neuer Mandant #1: Der Name darf nicht leer und muss eindeutig sein.',
   'New contact'                 => 'Neue Ansprechperson',
@@ -1801,7 +1806,6 @@ $self->{texts} = {
   'New row, description'        => 'Neue Zeile, Artikelbeschreibung',
   'New row, partnumber'         => 'Neue Zeile, Nummer',
   'New sales order'             => 'Neuer Auftrag',
-  'New service'                 => 'Neue Dienstleistung',
   'New shipto'                  => 'Neue Lieferadresse',
   'New vendor'                  => 'Neuer Lieferant',
   'New window/tab'              => 'Neues Fenster/Tab',
@@ -1847,6 +1851,7 @@ $self->{texts} = {
   'No or an unknown authenticantion module specified in "config/kivitendo.conf".' => 'Es wurde kein oder ein unbekanntes Authentifizierungsmodul in "config/kivitendo.conf" angegeben.',
   'No part was found matching the search parameters.' => 'Es wurde kein Artikel gefunden, auf den die Suchparameter zutreffen.',
   'No part was selected.'       => 'Es wurde kein Artikel ausgewählt',
+  'No parts classification has been created yet.' => 'Keine Artikel-Klassifizierung erzeugt.',
   'No payment term has been created yet.' => 'Es wurden noch keine Zahlungsbedingungen angelegt.',
   'No picture has been uploaded' => 'Es wurde kein Bild hochgeladen',
   'No picture uploaded yet'     => 'Noch kein Bild hochgeladen',
@@ -1889,6 +1894,7 @@ $self->{texts} = {
   'None'                        => 'Kein',
   'None (PriceSource Discount)' => 'Freier Rabatt',
   'None (PriceSource)'          => 'Freier Preis',
+  'None (typeabbreviation)'     => '-',
   'Normal'                      => 'Normal',
   'Normal users cannot log in.' => 'Normale Benutzer können sich nicht anmelden.',
   'Normalize Customer / Vendor names' => 'Normalisierung Kunden- / Lieferantennamen',
@@ -2011,6 +2017,7 @@ $self->{texts} = {
   'POSTED'                      => 'Gebucht',
   'POSTED AS NEW'               => 'Als neu gebucht',
   'PRINTED'                     => 'Gedruckt',
+  'PType'                       => 'Typ',
   'Package name'                => 'Paketname',
   'Packing Lists'               => 'Lieferschein',
   'Page'                        => 'Seite',
@@ -2020,12 +2027,14 @@ $self->{texts} = {
   'Part'                        => 'Ware',
   'Part "#1" has chargenumber or best before date set. So it cannot be transfered automatically.' => 'Bei Artikel "#1" ist eine Chargenummer oder ein Mindesthaltbarkeitsdatum vergeben. Deshalb kann dieser Artikel nicht automatisch ausgelagert werden.',
   'Part (database ID)'          => 'Artikel (Datenbank-ID)',
+  'Part (typeabbreviation)'     => 'W',
+  'Part Classification'         => 'Artikel-Klassifizierung',
   'Part Description'            => 'Artikelbeschreibung',
   'Part Description missing!'   => 'Artikelbezeichnung fehlt!',
   'Part Notes'                  => 'Bemerkungen',
   'Part Number'                 => 'Artikelnummer',
   'Part Number missing!'        => 'Artikelnummer fehlt!',
-  'Part Unit'                   => 'Artikeleinheit',
+  'Part Unit'                   => 'Einheit des Artikels',
   'Part picker'                 => 'Artikelauswahl',
   'Part_br_Description'         => 'Beschreibung',
   'Partial invoices'            => 'Teilrechnungen',
@@ -2033,6 +2042,8 @@ $self->{texts} = {
   'Partnumber must not be set to empty!' => 'Die Artikelnummer darf nicht auf leer ge&auml;ndert werden.',
   'Partnumber not unique!'      => 'Artikelnummer bereits vorhanden!',
   'Parts'                       => 'Waren',
+  'Parts Classification'        => 'Artikel-Klassifizierung',
+  'Parts Classifications'       => 'Artikel-Klassifizierung',
   'Parts Inventory'             => 'Warenliste',
   'Parts Master Data'           => 'Artikelstammdaten',
   'Parts must have an entry type.' => 'Waren m&uuml;ssen eine Buchungsgruppe haben.',
@@ -2183,6 +2194,7 @@ $self->{texts} = {
   'Print template base file name' => 'Druckvorlagen-Basisdateiname',
   'Print templates'             => 'Druckvorlagen',
   'Print templates to use'      => 'Zu verwendende Druckvorlagen',
+  'Printdate'                   => '',
   'Printer'                     => 'Drucker',
   'Printer Command'             => 'Druckbefehl',
   'Printer Description'         => 'Druckerbeschreibung',
@@ -2196,6 +2208,8 @@ $self->{texts} = {
   'Private Phone'               => 'Privates Tel.',
   'Problem'                     => 'Problem',
   'Produce Assembly'            => 'Erzeugnis fertigen',
+  'Production'                  => 'Produktion',
+  'Production (typeabbreviation)' => 'P',
   'Productivity'                => 'Produktivität',
   'Profit determination'        => 'Gewinnermittlung',
   'Proforma Invoice'            => 'Proformarechnung',
@@ -2219,6 +2233,8 @@ $self->{texts} = {
   'Proposal'                    => 'Vorschlag',
   'Proposals'                   => 'Vorschläge',
   'Prozentual/Absolut'          => 'Prozentual/Absolut',
+  'Purchase'                    => 'Einkauf',
+  'Purchase (typeabbreviation)' => 'E',
   'Purchase Delivery Order'     => 'Einkaufslieferschein',
   'Purchase Delivery Orders'    => 'Einkaufslieferscheine',
   'Purchase Delivery Orders deleteable' => 'Einkaufslieferscheine löschbar',
@@ -2421,6 +2437,8 @@ $self->{texts} = {
   'Saldo neu'                   => 'Saldo neu',
   'Saldo per'                   => 'Saldo per',
   'Sale Prices'                 => 'Verkaufspreise',
+  'Sales'                       => 'Verkauf',
+  'Sales (typeabbreviation)'    => 'V',
   'Sales Delivery Order'        => 'Verkaufslieferschein',
   'Sales Delivery Orders'       => 'Verkaufslieferscheine',
   'Sales Delivery Orders deleteable' => 'Verkaufslieferscheine löschbar',
@@ -2543,6 +2561,7 @@ $self->{texts} = {
   'Serial No.'                  => 'Seriennummer',
   'Serial Number'               => 'Seriennummer',
   'Service'                     => 'Dienstleistung',
+  'Service (typeabbreviation)'  => 'D',
   'Service Items'               => 'Dienstleistungen',
   'Service Number missing!'     => 'Dienstleistungsnummer fehlt!',
   'Service, assembly or part'   => 'Dienstleistung, Erzeugnis oder Ware',
@@ -2796,6 +2815,7 @@ $self->{texts} = {
   'That export does not exist.' => 'Dieser Export existiert nicht.',
   'That is why kivitendo could not find a default currency.' => 'Daher konnte kivitendo keine Standardwährung finden.',
   'The \'name\' is the field shown to the user during login.' => 'Der \'Name\' ist derjenige, der dem Benutzer beim Login angezeigt wird.',
+  'The \'pclass\' column has the same abbreviation like a part export. The first letter is for the type Part,Assembly or Service, the second(and third) for Part Classification' => 'Die Spalte \'pclass\' besteht aus zwei Buchstaben entsprechend dem Export. Der erste Buchstabe ist für \'W\'=Ware \'E\'=Erzeugnis oder \'D\' für Dienstleistung, der zweite steht für den Artikeltyp, z.B. E,V,H,P oder - ',
   'The \'tag\' field must only consist of alphanumeric characters or the carachters - _ ( )' => 'Das Feld \'tag\' darf nur aus alphanumerischen Zeichen und den Zeichen - _ ( ) bestehen.',
   'The AP transaction #1 has been deleted.' => 'Die Kreditorenbuchung #1 wurde gelöscht.',
   'The AR transaction #1 has been deleted.' => 'Die Debitorenbuchung #1 wurde gelöscht.',
@@ -2812,6 +2832,7 @@ $self->{texts} = {
   'The SEPA export has been created.' => 'Der SEPA-Export wurde erstellt',
   'The SEPA strings have been saved.' => 'Die bei SEPA-Überweisungen verwendeten Begriffe wurden gespeichert.',
   'The WebDAV feature has been used.' => 'Das WebDAV-Feature wurde benutzt.',
+  'The abbreviation is missing.' => 'Abkürzung fehlt',
   'The acceptance status has been created.' => 'Der Abnahmestatus wurde angelegt.',
   'The acceptance status has been deleted.' => 'Der Abnahmestatus wurde gelöscht.',
   'The acceptance status has been saved.' => 'Der Abnahmestatus wurde gespeichert.',
@@ -2846,6 +2867,7 @@ $self->{texts} = {
   'The base unit does not exist.' => 'Die Basiseinheit existiert nicht.',
   'The base unit relations must not contain loops (e.g. by saying that unit A\'s base unit is B, B\'s base unit is C and C\'s base unit is A) in row %d.' => 'Die Beziehungen der Einheiten d&uuml;rfen keine Schleifen beinhalten (z.B. wenn gesagt wird, dass Einheit As Basiseinheit B, Bs Basiseinheit C und Cs Basiseinheit A ist) in Zeile %d.',
   'The basic client tables have not been created for this client\'s database yet.' => 'Die grundlegenden Mandantentabellen wurden in der für diesen Mandanten konfigurierten Datenbank noch nicht angelegt.',
+  'The basic parts classification cannot be deleted.' => '',
   'The body is missing.'        => 'Der Text fehlt',
   'The booking group has been created.' => 'Die Buchungsgruppe wurde erstellt.',
   'The booking group has been deleted.' => 'Die Buchungsgruppe wurde gelöscht.',
@@ -2915,7 +2937,7 @@ $self->{texts} = {
   'The discounted amount will be shown in documents.' => 'Der Rabattbetrag wird in Belegen ausgewiesen.',
   'The document has been changed by another user. No mail was sent. Please reopen it in another window and copy the changes to the new window' => 'Die Daten wurden bereits von einem anderen Benutzer verändert. Deshalb ist das Dokument ungültig und es wurde keine E-Mail verschickt. Bitte öffnen Sie das Dokument erneut in einem extra Fenster und übertragen Sie die Daten',
   'The document has been changed by another user. Please reopen it in another window and copy the changes to the new window' => 'Die Daten wurden bereits von einem anderen Benutzer verändert. Deshalb ist das Dokument ungültig. Bitte öffnen Sie das Dokument erneut in einem extra Fenster und übertragen Sie die Daten',
-  'The documents have been sent to the printer \'#1\'.' => 'Die Dokumente wurden an den Drucker \'#1\' geschickt.',
+  'The documents have been sent to the printer \'#1\'.' => 'Die Dokumente sind zum Drucker \'#1\' geschickt',
   'The dunning process started' => 'Der Mahnprozess ist gestartet.',
   'The dunnings have been printed.' => 'Die Mahnung(en) wurden gedruckt.',
   'The email has been sent.'    => 'Die E-Mail wurde verschickt.',
@@ -2976,6 +2998,10 @@ $self->{texts} = {
   'The order has been deleted'  => 'Der Auftrag wurde gelöscht.',
   'The order has been saved'    => 'Der Auftrag wurde gespeichert.',
   'The package name is invalid.' => 'Der Paketname ist ungültig.',
+  'The parts classification has been created.' => 'Die Artikel-Klassifizierung ist erzeugt',
+  'The parts classification has been deleted.' => 'Die Artikel-Klassifizierung ist gelöscht',
+  'The parts classification has been saved.' => 'Die Artikel-Klassifizierung ist gespeichert',
+  'The parts classification is in use and cannot be deleted.' => 'Die Artikel-Klassifizierung ist in Verwendung, kann deshalb nicht gelöscht werden.',
   'The parts for this delivery order have already been transferred in.' => 'Die Artikel dieses Lieferscheins wurden bereits eingelagert.',
   'The parts for this delivery order have already been transferred out.' => 'Die Artikel dieses Lieferscheins wurden bereits ausgelagert.',
   'The parts have been removed.' => 'Die Waren wurden aus dem Lager entnommen.',
@@ -3279,11 +3305,13 @@ $self->{texts} = {
   'Trial Balance'               => 'Summen- und Saldenliste',
   'Trial balance between %s and %s' => 'Summen- und Saldenlisten vom %s bis zum %s',
   'Trying to call a sub without a name' => 'Es wurde versucht, eine Unterfunktion ohne Namen aufzurufen.',
+  'Typ'                         => 'Typ',
   'Type'                        => 'Typ',
   'Type can be either \'part\', \'service\' or \'assembly\'.' => 'Der Typ kann entweder \'part\' (für Waren), \'service\' (für Dienstleistungen) oder \'assembly\' (für Erzeugnisse) enthalten.',
   'Type of Business'            => 'Kunden-/Lieferantentyp',
   'Type of Customer'            => 'Kundentyp',
   'Type of Vendor'              => 'Lieferantentyp',
+  'TypeAbbreviation'            => 'Typ-Abkürzung',
   'Types of Business'           => 'Kunden-/Lieferantentypen',
   'USTVA'                       => 'USTVA',
   'USTVA 2004'                  => 'USTVA 2004',
@@ -3358,6 +3386,8 @@ $self->{texts} = {
   'Use linked items'            => 'Verknüpfte Positionen verwenden',
   'Use master default bin for Default Transfer, if no default bin for the part is configured' => 'Standardlagerplatz für Ein- / Auslagern über Standard-Lagerplatz, falls für die Ware kein expliziter Lagerplatz konfiguriert ist',
   'Useable for…'                => 'Benutzbar für…',
+  'Used for Purchase'           => 'im Einkauf verwenden',
+  'Used for Sale'               => 'im Verkauf verwenden',
   'User'                        => 'Benutzer',
   'User Config'                 => 'Einstellungen',
   'User Preferences'            => 'Benutzereinstellungen',
@@ -3422,7 +3452,7 @@ $self->{texts} = {
   'WHJournal'                   => 'Lagerbuchungen',
   'WHUsage'                     => 'Lagerentnahme',
   'Warehouse'                   => 'Lager',
-  'Warehouse (database ID)'     => 'Lager (Datenbank-ID)',
+  'Warehouse (database ID)'     => 'Lager (database ID)',
   'Warehouse (name)'            => 'Lager (Name)',
   'Warehouse From'              => 'Quelllager',
   'Warehouse Migration'         => 'Lagermigration',
@@ -3531,6 +3561,7 @@ $self->{texts} = {
   'ap_aging_list'               => 'liste_offene_verbindlichkeiten',
   'ar_aging_list'               => 'liste_offene_forderungen',
   'ar_chart isn\'t a valid chart' => 'Das Forderungskonto ist kein gültiges Konto.',
+  'article_list'                => 'article_list',
   'as at'                       => 'zum Stand',
   'assembled'                   => 'Gefertigt',
   'assembly'                    => 'Erzeugnis',
@@ -3717,6 +3748,8 @@ $self->{texts} = {
   'saved'                       => 'gespeichert',
   'saved!'                      => 'gespeichert',
   'saving data'                 => 'Speichere Daten',
+  'searched part not for purchase' => 'Gesuchter Artikel ist nicht für den Einkauf',
+  'searched part not for sale'  => 'Gesuchter Artikel ist nicht für den Verkauf',
   'semiannually'                => 'halbjährlich',
   'sent'                        => 'gesendet',
   'sent to printer'             => 'an Drucker geschickt',
index c1c7857..121ad42 100644 (file)
@@ -15,13 +15,16 @@ $self->{texts} = {
   ' Part Number missing!'       => '',
   ' missing!'                   => '',
   '#1 (custom variable)'        => '',
+  '#1 CB transactions and #1 OB transactions generated.' => '',
   '#1 MD'                       => '',
   '#1 additional part(s)'       => '',
   '#1 dunnings have been deleted' => '',
   '#1 h'                        => '',
+  '#1 invoice(s) saved.'        => '',
   '#1 of #2 importable objects were imported.' => '',
   '#1 prices were updated.'     => '',
   '#1 proposal(s) saved.'       => '',
+  '#1 proposal(s) with #2 invoice(s) saved.' => '',
   '#1 section(s)'               => '',
   '#1 text block(s) back'       => '',
   '#1 text block(s) front'      => '',
@@ -54,6 +57,7 @@ $self->{texts} = {
   'A directory with the name for the new print templates exists already.' => '',
   'A lot of the usability of kivitendo has been enhanced with javascript. Although it is currently possible to use every aspect of kivitendo without javascript, we strongly recommend it. In a future version this may change and javascript may be necessary to access advanced features.' => '',
   'A lower-case character is required.' => '',
+  'A payment can only be posted for multiple invoices if the amount to post is equal to or bigger than the sum of the open amounts of the affected invoices.' => '',
   'A special character is required (valid characters: #1).' => '',
   'A transaction description is required.' => '',
   'A unit with this name does already exist.' => '',
@@ -70,6 +74,7 @@ $self->{texts} = {
   'AP Transactions'             => 'Purchase Transactions',
   'AP transactions changeable'  => '',
   'AP transactions with sales taxkeys and/or AR transactions with input taxkeys' => '',
+  'AP/AR Aging & Journal'       => '',
   'AR'                          => 'Sales',
   'AR Aging'                    => 'Debtor Aging',
   'AR Transaction'              => 'Sales Transaction',
@@ -128,6 +133,7 @@ $self->{texts} = {
   'Account number not unique!'  => '',
   'Account number of the goal/source' => '',
   'Account saved!'              => '',
+  'Accounting desired'          => '',
   'Accounting method'           => '',
   'Accrual'                     => '',
   'Accrual accounting'          => '',
@@ -144,6 +150,7 @@ $self->{texts} = {
   'Add Client'                  => '',
   'Add Credit Note'             => '',
   'Add Customer'                => '',
+  'Add Customer/Vendor Number as a reference add-on for SEPA export.' => '',
   'Add Delivery Note'           => '',
   'Add Delivery Order'          => '',
   'Add Dunning'                 => '',
@@ -156,7 +163,6 @@ $self->{texts} = {
   'Add Letter'                  => '',
   'Add Part'                    => '',
   'Add Price Factor'            => '',
-  'Add Pricegroup'              => '',
   'Add Printer'                 => '',
   'Add Project'                 => '',
   'Add Purchase Delivery Order' => '',
@@ -198,6 +204,7 @@ $self->{texts} = {
   'Add part'                    => '',
   'Add picture'                 => '',
   'Add picture to text block'   => '',
+  'Add pricegroup'              => '',
   'Add section'                 => '',
   'Add sub function block'      => '',
   'Add taxzone'                 => '',
@@ -241,7 +248,6 @@ $self->{texts} = {
   'Alternatively you can create a new part which will then be selected.' => '',
   'Always save orders with a projectnumber (create new projects)' => '',
   'Amended Advance Turnover Tax Return' => '',
-  'Amended Advance Turnover Tax Return (Nr. 10)' => '',
   'Amount'                      => '',
   'Amount (for verification)'   => '',
   'Amount BB'                   => '',
@@ -273,6 +279,7 @@ $self->{texts} = {
   'Apr'                         => '',
   'April'                       => '',
   'Ar aging on %s'              => '',
+  'Are you sure to generate cb/ob transactions?' => '',
   'Are you sure you want to delete Invoice Number' => '',
   'Are you sure you want to delete Transaction' => '',
   'Are you sure you want to delete this background job?' => '',
@@ -287,12 +294,13 @@ $self->{texts} = {
   'Article'                     => '',
   'Article Code'                => '',
   'Article Code missing!'       => '',
+  'Article classification'      => '',
   'Article type'                => '',
   'Articles'                    => '',
   'As a result, the saved onhand values of the present goods can be stored into a warehouse designated by you, or will be reset for a proper warehouse tracking' => '',
   'Assemblies'                  => '',
-  'Assemblies can not be imported (yet). But the type column is used for sanity checks on price updates in order to prevent that articles with the wrong type will be updated.' => '',
   'Assembly'                    => '',
+  'Assembly (typeabbreviation)' => '',
   'Assembly Description'        => '',
   'Assembly Number'             => '',
   'Assembly Number missing!'    => '',
@@ -316,6 +324,7 @@ $self->{texts} = {
   'Attachment name'             => '',
   'Attachments'                 => '',
   'Attempt to call an undefined sub named \'%s\'' => '',
+  'Attention: Here will be generated a lot of CB/OB transactions.' => '',
   'Audit Control'               => '',
   'Aug'                         => '',
   'August'                      => '',
@@ -369,6 +378,8 @@ $self->{texts} = {
   'Bank transaction with id #1 has already been linked to #2.' => '',
   'Bank transactions'           => '',
   'Bank transactions MT940'     => '',
+  'Bank transactions that either only have warnings or no message at all have been posted.' => '',
+  'Bank transactions with errors have not been posted.' => '',
   'Bank transfer amount'        => '',
   'Bank transfer payment list for export #1' => '',
   'Bank transfer via SEPA'      => '',
@@ -405,6 +416,7 @@ $self->{texts} = {
   'Billing/shipping address (zipcode)' => '',
   'Bin'                         => '',
   'Bin (database ID)'           => '',
+  'Bin (name)'                  => '',
   'Bin From'                    => '',
   'Bin List'                    => '',
   'Bin To'                      => '',
@@ -449,9 +461,10 @@ $self->{texts} = {
   'CANCELED'                    => '',
   'CB Transaction'              => '',
   'CB Transactions'             => '',
+  'CB/OB Transactions'          => '',
+  'CN'                          => '',
   'CR'                          => '',
   'CSS style for pictures'      => '',
-  'CSV'                         => '',
   'CSV export -- options'       => '',
   'CSV import: ar transactions' => '',
   'CSV import: bank transactions' => '',
@@ -533,6 +546,7 @@ $self->{texts} = {
   'Chart of Accounts'           => '',
   'Chart picker'                => '',
   'Chartaccounts connected to this Tax:' => '',
+  'Charts'                      => '',
   'Check'                       => 'Cheque',
   'Check Details'               => '',
   'Check for duplicates'        => '',
@@ -567,6 +581,7 @@ $self->{texts} = {
   'Clients this user has access to' => '',
   'Close'                       => '',
   'Close Books up to'           => '',
+  'Close Details'               => '',
   'Close Flash'                 => '',
   'Close SEPA exports'          => '',
   'Close Window'                => '',
@@ -611,7 +626,6 @@ $self->{texts} = {
   'Copy requirement spec'       => '',
   'Copy template'               => '',
   'Correct taxkey'              => '',
-  'Cost'                        => '',
   'Costs'                       => '',
   'Could not load class #1 (#2): "#3"' => '',
   'Could not load class #1, #2' => '',
@@ -641,6 +655,7 @@ $self->{texts} = {
   'Create a new delivery term'  => '',
   'Create a new department'     => '',
   'Create a new group'          => '',
+  'Create a new parts classification' => '',
   'Create a new payment term'   => '',
   'Create a new predefined text' => '',
   'Create a new price rule'     => '',
@@ -706,6 +721,8 @@ $self->{texts} = {
   'Credit'                      => '',
   'Credit (one letter abbreviation)' => '',
   'Credit Account'              => '',
+  'Credit Account Name'         => '',
+  'Credit Amount'               => '',
   'Credit Limit'                => '',
   'Credit Limit exceeded!!!'    => '',
   'Credit Note'                 => '',
@@ -713,6 +730,7 @@ $self->{texts} = {
   'Credit Note Number'          => '',
   'Credit Starting Balance'     => '',
   'Credit Tax'                  => '',
+  'Credit Tax (lit)'            => '',
   'Credit Tax Account'          => '',
   'Credit note (one letter abbreviation)' => '',
   'Cumulated or averaged values' => '',
@@ -789,6 +807,8 @@ $self->{texts} = {
   'DUNS number'                 => '',
   'DUNS-Nr'                     => '',
   'Data'                        => '',
+  'DataSet #1'                  => '',
+  'DataSet for GoBD version #1. Created with kivitendo #2 by #3 (#4)' => '',
   'Database Administration'     => '',
   'Database Connection Test'    => '',
   'Database Host'               => '',
@@ -819,8 +839,11 @@ $self->{texts} = {
   'Debit'                       => '',
   'Debit (one letter abbreviation)' => '',
   'Debit Account'               => '',
+  'Debit Account Name'          => '',
+  'Debit Amount'                => '',
   'Debit Starting Balance'      => '',
   'Debit Tax'                   => '',
+  'Debit Tax (lit)'             => '',
   'Debit Tax Account'           => '',
   'Debit and credit out of balance!' => '',
   'Dec'                         => '',
@@ -919,6 +942,7 @@ $self->{texts} = {
   'Dial command missing in kivitendo configuration\'s [cti] section' => '',
   'Difference'                  => '',
   'Dimensions'                  => '',
+  'Direct debit revoked'        => '',
   'Directory'                   => '',
   'Disabled Price Sources'      => '',
   'Discard duplicate entries in CSV file' => '',
@@ -982,6 +1006,7 @@ $self->{texts} = {
   'Drafts'                      => '',
   'Drawing'                     => '',
   'Dropdown Limit'              => '',
+  'Druckdatum '                 => '',
   'Due'                         => '',
   'Due Date'                    => '',
   'Due Date missing!'           => '',
@@ -1015,7 +1040,7 @@ $self->{texts} = {
   'EB-Wert'                     => '',
   'EK'                          => '',
   'ELSE'                        => '',
-  'ELSTER Tax Number'           => '',
+  'ELSTER Export (via Geierlein)' => '',
   'EQUITY'                      => '',
   'EUER'                        => '',
   'Earlier versions of kivitendo contained bugs which might have led to wrong entries in the general ledger.' => '',
@@ -1044,7 +1069,6 @@ $self->{texts} = {
   'Edit Part'                   => '',
   'Edit Preferences for #1'     => '',
   'Edit Price Factor'           => '',
-  'Edit Pricegroup'             => '',
   'Edit Printer'                => '',
   'Edit Purchase Delivery Order' => '',
   'Edit Purchase Order'         => '',
@@ -1078,16 +1102,19 @@ $self->{texts} = {
   'Edit general settings'       => '',
   'Edit greetings'              => '',
   'Edit note'                   => '',
+  'Edit parts classification'   => '',
   'Edit payment term'           => '',
   'Edit picture'                => '',
   'Edit predefined text'        => '',
   'Edit price rule'             => '',
+  'Edit pricegroup'             => '',
   'Edit prices and discount (if not used, textfield is ONLY set readonly)' => '',
   'Edit project'                => '',
   'Edit project #1'             => '',
   'Edit project link'           => '',
   'Edit project status'         => '',
   'Edit project type'           => '',
+  'Edit purchase letters'       => '',
   'Edit purchase price rule'    => '',
   'Edit requirement spec'       => '',
   'Edit requirement spec status' => '',
@@ -1115,6 +1142,7 @@ $self->{texts} = {
   'Editable'                    => '',
   'Either there are no open invoices, or you have already initiated bank transfers with the open amounts for those that are still open.' => '',
   'Element disabled'            => '',
+  'Email'                       => '',
   'Email journal'               => '',
   'Employee'                    => '',
   'Employee #1 saved!'          => '',
@@ -1144,12 +1172,15 @@ $self->{texts} = {
   'Error: A negative target quantity is not allowed.' => '',
   'Error: A quantity and a target quantity could not be given both.' => '',
   'Error: A quantity or a target quantity must be given.' => '',
+  'Error: Bin #1 is not from warehouse #2' => '',
   'Error: Bin not found'        => '',
   'Error: Customer/vendor missing' => '',
   'Error: Customer/vendor not found' => '',
   'Error: Found local bank account number but local bank code doesn\'t match' => '',
   'Error: Gender (cp_gender) missing or invalid' => '',
   'Error: Invalid bin'          => '',
+  'Error: Invalid bin id'       => '',
+  'Error: Invalid bin name #1'  => '',
   'Error: Invalid business'     => '',
   'Error: Invalid contact'      => '',
   'Error: Invalid currency'     => '',
@@ -1158,7 +1189,6 @@ $self->{texts} = {
   'Error: Invalid language'     => '',
   'Error: Invalid order for this order item' => '',
   'Error: Invalid part'         => '',
-  'Error: Invalid part type'    => '',
   'Error: Invalid parts group'  => '',
   'Error: Invalid payment terms' => '',
   'Error: Invalid price factor' => '',
@@ -1170,6 +1200,8 @@ $self->{texts} = {
   'Error: Invalid unit'         => '',
   'Error: Invalid vendor in column make_#1' => '',
   'Error: Invalid warehouse'    => '',
+  'Error: Invalid warehouse id' => '',
+  'Error: Invalid warehouse name #1' => '',
   'Error: Name missing'         => '',
   'Error: Part is obsolete'     => '',
   'Error: Part not found'       => '',
@@ -1240,6 +1272,7 @@ $self->{texts} = {
   'Expense'                     => '',
   'Expense Account'             => '',
   'Expense/Asset'               => '',
+  'Export'                      => '',
   'Export Buchungsdaten'        => '',
   'Export Number'               => '',
   'Export Stammdaten'           => '',
@@ -1248,6 +1281,8 @@ $self->{texts} = {
   'Export date'                 => '',
   'Export date from'            => '',
   'Export date to'              => '',
+  'Export error in transaction #1: Rounding error too large #2' => '',
+  'Export error in transaction #1: Unbalanced ledger before next transaction (#2)' => '',
   'Extend automatically by n months' => '',
   'Extended'                    => '',
   'Extended status'             => '',
@@ -1295,6 +1330,7 @@ $self->{texts} = {
   'For AP transactions it will replace the sales taxkeys with input taxkeys with the same tax rate.' => '',
   'For AR transactions it will replace the input taxkeys with sales taxkeys with the same tax rate.' => '',
   'For all delivery orders create and print invoices' => '',
+  'For changeing goto USTVA Config' => '',
   'For further information read this: ' => '',
   'For part "#1" there are missing #2 #3 in the default warehouse/bin "#4/#5".' => '',
   'For part "#1" there is no default warehouse and bin defined.' => '',
@@ -1313,6 +1349,7 @@ $self->{texts} = {
   'Fristsetzung'                => '',
   'From'                        => '',
   'From Date'                   => '',
+  'From bin'                    => '',
   'From this version on a new feature is available.' => '',
   'From this version on it is necessary to name a default value.' => '',
   'From this version on the partnumber of services, articles and assemblies have to be unique.' => '',
@@ -1326,6 +1363,7 @@ $self->{texts} = {
   'Function/position'           => '',
   'GL Transaction'              => '',
   'GL Transaction (abbreviation)' => '',
+  'GL Transactions'             => '',
   'GL search'                   => '',
   'GL transactions changeable'  => '',
   'GLN'                         => '',
@@ -1342,6 +1380,7 @@ $self->{texts} = {
   'Git revision: #1, #2 #3'     => '',
   'Given Name'                  => '',
   'Global Record BCC'           => '',
+  'GoBD Export'                 => '',
   'Greeting'                    => '',
   'Greetings'                   => '',
   'Group'                       => '',
@@ -1390,6 +1429,7 @@ $self->{texts} = {
   'I'                           => '',
   'IBAN'                        => '',
   'ID'                          => '',
+  'ID (lit)'                    => '',
   'ID of own bank account'      => '',
   'ID-Nummer'                   => '',
   'ID/Acc_ID'                   => '',
@@ -1399,6 +1439,7 @@ $self->{texts} = {
   'If all of the following match' => '',
   'If amounts differ more than "Maximal amount difference" (see settings), this item is marked as invalid.' => '',
   'If checked the taxkey will not be exported in the DATEV Export, but only IF chart taxkeys differ from general ledger taxkeys' => '',
+  'If column \'pclass\' is present the article type is then irrelevant or used as default ' => '',
   'If configured this bin will be preselected for all new parts. Also this bin will be used as the master default bin, if default transfer out with master bin is activated.' => '',
   'If disabled purchase delivery orders can only be created by conversion from existing requests for quotations and purchase orders.' => '',
   'If disabled purchase invoices can only be created by conversion from existing requests for quotations, purchase orders and purchase delivery orders.' => '',
@@ -1410,7 +1451,7 @@ $self->{texts} = {
   'If enabled purchase and sales records cannot be saved if no transaction description has been entered.' => '',
   'If left empty the default sender from the kivitendo configuration will be used (key \'email_from\' in section \'periodic_invoices\'; current value: #1).' => '',
   'If missing then the start date will be used.' => '',
-  'If the article type is set to \'mixed\' then a column called \'type\' must be present.' => '',
+  'If the article type is set to \'mixed\' then a column called \'type\' or called \'pclass\' must be present.' => '',
   'If the automatic creation of invoices for fees and interest is switched on for a dunning level then the following accounts will be used for the invoice.' => '',
   'If the database user listed above does not have the right to create a database then enter the name and password of the superuser below:' => '',
   'If the default transfer out always succeed use this bin for negative stock quantity.' => '',
@@ -1477,6 +1518,7 @@ $self->{texts} = {
   'Internal Phone List'         => '',
   'Internal comment'            => '',
   'Internet'                    => '',
+  'Into bin'                    => '',
   'Intra-Community supply'      => '',
   'Introduction of clients'     => '',
   'Inv. Duedate'                => '',
@@ -1499,6 +1541,7 @@ $self->{texts} = {
   'Invnumber'                   => '',
   'Invnumber missing!'          => '',
   'Invoice'                     => '',
+  'Invoice #1 was overpaid by #2.' => '',
   'Invoice (one letter abbreviation)' => '',
   'Invoice Date'                => '',
   'Invoice Date missing!'       => '',
@@ -1525,9 +1568,9 @@ $self->{texts} = {
   'It will simply set the taxkey to 0 (meaning "no taxes") which is the correct value for such inventory transactions.' => '',
   'Italy'                       => '',
   'Item deleted!'               => '',
+  'Item does not exists in File' => '',
   'Item mode'                   => '',
   'Item multi selection with qty' => '',
-  'Item not on file!'           => '',
   'Item values'                 => '',
   'Item variables'              => '',
   'Jahresverkehrszahlen neu'    => '',
@@ -1646,7 +1689,6 @@ $self->{texts} = {
   'MAILED'                      => '',
   'MD'                          => '',
   'MIME type'                   => '',
-  'MT940'                       => '',
   'MT940 import'                => '',
   'Main Preferences'            => '',
   'Main sorting'                => '',
@@ -1688,6 +1730,8 @@ $self->{texts} = {
   'Media \'#1\' is not supported yet/anymore.' => '',
   'Medium Number'               => '',
   'Memo'                        => '',
+  'Merchandise'                 => 'Merchandise',
+  'Merchandise (typeabbreviation)' => 'M',
   'Message'                     => '',
   'Method'                      => '',
   'Microfiche'                  => '',
@@ -1704,7 +1748,7 @@ $self->{texts} = {
   'Missing taxkeys in invoices with taxes.' => '',
   'Missing transport cost: #1  Are you sure?' => '',
   'Mitarbeiter'                 => '',
-  'Mixed (requires column "type")' => '',
+  'Mixed (requires column "type" or "pclass")' => '',
   'Mobile'                      => '',
   'Mobile1'                     => '',
   'Mobile2'                     => '',
@@ -1742,7 +1786,6 @@ $self->{texts} = {
   'New Password'                => '',
   'New Purchase Price Rule'     => '',
   'New Sales Price Rule'        => '',
-  'New assembly'                => '',
   'New client #1: The database configuration fields "host", "port", "name" and "user" must not be empty.' => '',
   'New client #1: The name must be unique and not empty.' => '',
   'New contact'                 => '',
@@ -1754,7 +1797,6 @@ $self->{texts} = {
   'New row, description'        => '',
   'New row, partnumber'         => '',
   'New sales order'             => '',
-  'New service'                 => '',
   'New shipto'                  => '',
   'New vendor'                  => '',
   'New window/tab'              => '',
@@ -1789,6 +1831,8 @@ $self->{texts} = {
   'No department has been created yet.' => '',
   'No draft was found.'         => '',
   'No dunnings have been selected for printing.' => '',
+  'No end date given, setting to today' => '',
+  'No entries have been imported yet.' => '',
   'No errors have occurred.'    => '',
   'No file has been uploaded yet.' => '',
   'No function blocks have been created yet.' => '',
@@ -1797,6 +1841,7 @@ $self->{texts} = {
   'No invoices have been selected.' => '',
   'No or an unknown authenticantion module specified in "config/kivitendo.conf".' => '',
   'No part was found matching the search parameters.' => '',
+  'No parts classification has been created yet.' => '',
   'No payment term has been created yet.' => '',
   'No picture has been uploaded' => '',
   'No picture uploaded yet'     => '',
@@ -1813,11 +1858,13 @@ $self->{texts} = {
   'No requirement spec templates have been created yet.' => '',
   'No requirement spec type has been created yet.' => '',
   'No results.'                 => '',
+  'No revert available.'        => '',
   'No risks level has been created yet.' => '',
   'No sections created yet'     => '',
   'No sections have been created so far.' => '',
   'No sections have been created yet.' => '',
   'No shipto selected to delete' => '',
+  'No start date given, setting to #1' => '',
   'No summary account'          => '',
   'No text blocks have been created for this position.' => '',
   'No text has been entered yet.' => '',
@@ -1830,11 +1877,13 @@ $self->{texts} = {
   'No valid number entered for pricegroup "#1".' => '',
   'No vendor has been selected yet.' => '',
   'No warehouse has been created yet or the quantity of the bins is not configured yet.' => '',
+  'No year given for method year' => '',
   'No.'                         => '',
   'No/individual shipping address' => '',
   'None'                        => '',
   'None (PriceSource Discount)' => '',
   'None (PriceSource)'          => '',
+  'None (typeabbreviation)'     => '-',
   'Normal'                      => '',
   'Normal users cannot log in.' => '',
   'Normalize Customer / Vendor names' => '',
@@ -1873,6 +1922,7 @@ $self->{texts} = {
   'Number pages'                => '',
   'Number variables: \'PRECISION=n\' forces numbers to be shown with exactly n decimal places.' => '',
   'OB Transaction'              => '',
+  'OB Transactions'             => '',
   'Objects have been imported.' => '',
   'Obsolete'                    => '',
   'Oct'                         => '',
@@ -1884,6 +1934,8 @@ $self->{texts} = {
   'On'                          => '',
   'On Hand'                     => '',
   'On Order'                    => '',
+  'One OB-transaction'          => '',
+  'One SB-transaction'          => '',
   'One of the columns "qty" or "target_qty" must be given. If "target_qty" is given, the quantity to transfer for each transfer will be calculate, so that the quantity for this part, warehouse and bin will result in the given "target_qty" after each transfer.' => '',
   'One or more Perl modules missing' => '',
   'Onhand only sets the quantity in master data, not in inventory. This is only a legacy info field and will be overwritten as soon as a inventory transfer happens.' => '',
@@ -1916,6 +1968,7 @@ $self->{texts} = {
   'Order Number missing!'       => '',
   'Order amount'                => '',
   'Order deleted!'              => '',
+  'Order item search'           => '',
   'Order probability'           => '',
   'Order probability & expected billing date' => '',
   'Order value periodicity'     => '',
@@ -1937,7 +1990,6 @@ $self->{texts} = {
   'Out of balance transaction!' => '',
   'Out of balance!'             => '',
   'Output Number Format'        => '',
-  'Outputformat'                => '',
   'Overdue sales quotations and requests for quotations' => '',
   'Override'                    => '',
   'Override invoice language'   => '',
@@ -1950,9 +2002,11 @@ $self->{texts} = {
   'PDF'                         => '',
   'PDF (OpenDocument/OASIS)'    => '',
   'PDF export -- options'       => '',
+  'PLZ Grosskunden'             => '',
   'POSTED'                      => '',
   'POSTED AS NEW'               => '',
   'PRINTED'                     => '',
+  'PType'                       => '',
   'Package name'                => '',
   'Packing Lists'               => '',
   'Page'                        => '',
@@ -1962,17 +2016,23 @@ $self->{texts} = {
   'Part'                        => '',
   'Part "#1" has chargenumber or best before date set. So it cannot be transfered automatically.' => '',
   'Part (database ID)'          => '',
+  'Part (typeabbreviation)'     => '',
+  'Part Classification'         => '',
   'Part Description'            => '',
   'Part Description missing!'   => '',
   'Part Notes'                  => '',
   'Part Number'                 => '',
   'Part Number missing!'        => '',
+  'Part Unit'                   => '',
   'Part picker'                 => '',
+  'Part_br_Description'         => '',
   'Partial invoices'            => '',
   'Partnumber'                  => '',
   'Partnumber must not be set to empty!' => '',
   'Partnumber not unique!'      => '',
   'Parts'                       => '',
+  'Parts Classification'        => '',
+  'Parts Classifications'       => '',
   'Parts Inventory'             => '',
   'Parts Master Data'           => '',
   'Parts must have an entry type.' => '',
@@ -2110,9 +2170,6 @@ $self->{texts} = {
   'Price sources deactivated in this client' => '',
   'Price type explanation'      => '',
   'Pricegroup'                  => '',
-  'Pricegroup deleted!'         => '',
-  'Pricegroup missing!'         => '',
-  'Pricegroup saved!'           => '',
   'Pricegroups'                 => '',
   'Prices'                      => '',
   'Print'                       => '',
@@ -2139,6 +2196,8 @@ $self->{texts} = {
   'Private Phone'               => '',
   'Problem'                     => '',
   'Produce Assembly'            => '',
+  'Production'                  => 'Production',
+  'Production (typeabbreviation)' => 'W',
   'Productivity'                => '',
   'Profit determination'        => '',
   'Proforma Invoice'            => '',
@@ -2162,6 +2221,8 @@ $self->{texts} = {
   'Proposal'                    => '',
   'Proposals'                   => '',
   'Prozentual/Absolut'          => '',
+  'Purchase'                    => 'Purchase',
+  'Purchase (typeabbreviation)' => 'P',
   'Purchase Delivery Order'     => '',
   'Purchase Delivery Orders'    => '',
   'Purchase Delivery Orders deleteable' => '',
@@ -2232,10 +2293,12 @@ $self->{texts} = {
   'Receipt posted!'             => '',
   'Receipt, payment, reconciliation' => '',
   'Receipts'                    => '',
+  'Receipts attached/extra'     => '',
   'Receivable account'          => '',
   'Receivables'                 => '',
   'Receivables account'         => '',
   'Receivables account (account number)' => '',
+  'Received payments can only be posted for sales invoices and purchase credit notes.' => '',
   'Recipients'                  => '',
   'Reconcile'                   => '',
   'Reconciliation'              => '',
@@ -2255,6 +2318,7 @@ $self->{texts} = {
   'Remaining'                   => '',
   'Remaining Amount'            => '',
   'Remaining Net Amount'        => '',
+  'Remittance information optional Vendor/Customer No postfix' => '',
   'Remittance information prefix' => '',
   'Remote Bank Code'            => '',
   'Remote Name/Customer/Description' => '',
@@ -2347,6 +2411,7 @@ $self->{texts} = {
   'SAVED'                       => '',
   'SAVED FOR DUNNING'           => '',
   'SCREENED'                    => '',
+  'SEPA'                        => '',
   'SEPA XML download'           => '',
   'SEPA creditor ID'            => '',
   'SEPA exports'                => '',
@@ -2354,11 +2419,14 @@ $self->{texts} = {
   'SEPA message ID'             => '',
   'SEPA message IDs'            => '',
   'SEPA strings'                => '',
+  'Saldo'                       => '',
   'Saldo Credit'                => '',
   'Saldo Debit'                 => '',
   'Saldo neu'                   => '',
   'Saldo per'                   => '',
   'Sale Prices'                 => '',
+  'Sales'                       => 'Sales',
+  'Sales (typeabbreviation)'    => 'S',
   'Sales Delivery Order'        => '',
   'Sales Delivery Orders'       => '',
   'Sales Delivery Orders deleteable' => '',
@@ -2446,6 +2514,7 @@ $self->{texts} = {
   'Select a period'             => '',
   'Select a vendor'             => '',
   'Select all'                  => '',
+  'Select charts for which the CB/OB transactions want to be posted.' => '',
   'Select federal state...'     => '',
   'Select file to upload'       => '',
   'Select from one of the items below' => '',
@@ -2456,6 +2525,7 @@ $self->{texts} = {
   'Select template to paste'    => '',
   'Select type of removal'      => '',
   'Select type of transfer'     => '',
+  'Select type of transfer in'  => '',
   'Selected'                    => '',
   'Selection'                   => '',
   'Selection fields: The option field must contain the available options for the selection. Options are separated by \'##\', for example \'Early##Normal##Late\'.' => '',
@@ -2471,6 +2541,7 @@ $self->{texts} = {
   'Sending E-mail: '            => '',
   'Sent emails can be optionally stored in the database with or without their attachments.' => '',
   'Sent on'                     => '',
+  'Sent payments can only be posted for purchase invoices and sales credit notes.' => '',
   'Sep'                         => '',
   'Separator'                   => '',
   'Separator chararacter'       => '',
@@ -2478,6 +2549,7 @@ $self->{texts} = {
   'Serial No.'                  => '',
   'Serial Number'               => '',
   'Service'                     => '',
+  'Service (typeabbreviation)'  => '',
   'Service Items'               => '',
   'Service Number missing!'     => '',
   'Service, assembly or part'   => '',
@@ -2538,12 +2610,14 @@ $self->{texts} = {
   'Show follow ups...'          => '',
   'Show help text'              => '',
   'Show history'                => '',
+  'Show images'                 => '',
   'Show items from invoices individually' => '',
   'Show mappings (csv_import)'  => '',
   'Show old dunnings'           => '',
   'Show overdue sales quotations and requests for quotations...' => '',
   'Show parts'                  => '',
   'Show parts longdescription (notes) in select list' => '',
+  'Show purchase letters report' => '',
   'Show requirement spec'       => '',
   'Show requirement spec template' => '',
   'Show sales letters report'   => '',
@@ -2565,13 +2639,16 @@ $self->{texts} = {
   'Skipping due to existing bank transaction in database' => '',
   'Skipping due to existing entry in database' => '',
   'Skipping due to existing entry in database with different type' => '',
-  'Skipping, for assemblies are not importable (yet)' => '',
+  'Skipping due to existing entry with different unit or inventory_accno_id' => '',
+  'Skipping due to same partnumber in csv file' => '',
+  'Skipping non-existent article' => '',
   'Skonto'                      => '',
   'Skonto Terms'                => '',
   'Skonto amount'               => '',
   'Skonto information'          => '',
   'So far you could use one partnumber for severel parts, for example a service and an article.' => '',
   'Sold'                        => '',
+  'Sold order items'            => '',
   'Soldtotal does not make sense without any bsooqr options' => '',
   'Solution'                    => '',
   'Sort By'                     => '',
@@ -2628,8 +2705,10 @@ $self->{texts} = {
   'Subtotals per quarter'       => '',
   'Such entries cannot be exported into the DATEV format and have to be fixed as well.' => '',
   'Suggested invoice'           => '',
+  'Sum CB Transactions'         => '',
   'Sum Credit'                  => '',
   'Sum Debit'                   => '',
+  'Sum OB Transactions'         => '',
   'Sum for'                     => '',
   'Sum for #1'                  => '',
   'Sum for section'             => '',
@@ -2652,9 +2731,12 @@ $self->{texts} = {
   'Target bank account'         => '',
   'Target table'                => '',
   'Task Server is not running, starting it now. If this does not change, please check your task server config' => '',
+  'Task server'                 => '',
   'Task server control'         => '',
   'Task server status'          => '',
   'Tax'                         => '',
+  'Tax Account'                 => '',
+  'Tax Account Name'            => '',
   'Tax Consultant'              => '',
   'Tax ID number'               => '',
   'Tax Included'                => '',
@@ -2718,6 +2800,7 @@ $self->{texts} = {
   'That export does not exist.' => '',
   'That is why kivitendo could not find a default currency.' => '',
   'The \'name\' is the field shown to the user during login.' => '',
+  'The \'pclass\' column has the same abbreviation like a part export. The first letter is for the type Part,Assembly or Service, the second(and third) for Part Classification' => '',
   'The \'tag\' field must only consist of alphanumeric characters or the carachters - _ ( )' => '',
   'The AP transaction #1 has been deleted.' => '',
   'The AR transaction #1 has been deleted.' => '',
@@ -2726,6 +2809,7 @@ $self->{texts} = {
   'The GL transaction #1 has been deleted.' => '',
   'The IBAN \'#1\' is not valid as IBANs in #2 must be exactly #3 characters long.' => '',
   'The IBAN is missing.'        => '',
+  'The ID #1 is not a valid database ID.' => '',
   'The LDAP server "#1:#2" is unreachable. Please check config/kivitendo.conf.' => '',
   'The MT940 import needs an import profile called MT940' => '',
   'The PDF has been created'    => '',
@@ -2733,6 +2817,7 @@ $self->{texts} = {
   'The SEPA export has been created.' => '',
   'The SEPA strings have been saved.' => '',
   'The WebDAV feature has been used.' => '',
+  'The abbreviation is missing.' => '',
   'The acceptance status has been created.' => '',
   'The acceptance status has been deleted.' => '',
   'The acceptance status has been saved.' => '',
@@ -2843,6 +2928,7 @@ $self->{texts} = {
   'The execution schedule is invalid.' => '',
   'The execution type is invalid.' => '',
   'The existing record has been created from the link target to add.' => '',
+  'The export failed because of malformed transactions. Please fix those before exporting.' => '',
   'The factor is missing in row %d.' => '',
   'The factor is missing.'      => '',
   'The file has been sent to the printer.' => '',
@@ -2853,6 +2939,7 @@ $self->{texts} = {
   'The following currencies have been used, but they are not defined:' => '',
   'The following drafts have been saved and can be loaded.' => '',
   'The following groups are valid for this client' => '',
+  'The following is only a preview.' => '',
   'The following list has been generated automatically from existing users collapsing users with identical settings into a single entry.' => '',
   'The following old files whose settings have to be merged manually into the new configuration file "config/kivitendo.conf" still exist:' => '',
   'The following transaction contains wrong taxes:' => '',
@@ -2888,6 +2975,10 @@ $self->{texts} = {
   'The order has been deleted'  => '',
   'The order has been saved'    => '',
   'The package name is invalid.' => '',
+  'The parts classification has been created.' => '',
+  'The parts classification has been deleted.' => '',
+  'The parts classification has been saved.' => '',
+  'The parts classification is in use and cannot be deleted.' => '',
   'The parts for this delivery order have already been transferred in.' => '',
   'The parts for this delivery order have already been transferred out.' => '',
   'The parts have been removed.' => '',
@@ -2913,6 +3004,11 @@ $self->{texts} = {
   'The price rule has been saved.' => '',
   'The price rule is not a rule for discounts' => '',
   'The price rule is not a rule for prices' => '',
+  'The pricegroup has been created.' => '',
+  'The pricegroup has been deleted.' => '',
+  'The pricegroup has been saved.' => '',
+  'The pricegroup has been used and cannot be deleted.' => '',
+  'The pricegroup is being used by customers.' => '',
   'The printer could not be deleted.' => '',
   'The printer has been created.' => '',
   'The printer has been deleted.' => '',
@@ -3027,7 +3123,6 @@ $self->{texts} = {
   'There are entries in tax where taxkey is NULL.' => '',
   'There are invalid taxnumbers in use.' => '',
   'There are invalid transactions in your database.' => '',
-  'There are invoices which could not be paid by bank transaction #1 (Account number: #2, bank code: #3)!' => '',
   'There are no documents in the WebDAV directory at the moment.' => '',
   'There are no entries in the background job history.' => '',
   'There are no entries that match the filter.' => '',
@@ -3055,6 +3150,7 @@ $self->{texts} = {
   'There was an error saving the draft' => '',
   'There was an error saving the letter' => '',
   'There was an error saving the letter draft' => '',
+  'There will be two transactions done:' => '',
   'There you can let kivitendo create the basic tables for you, even in an already existing database.' => '',
   'Therefore several settings that had to be made for each user in the past have been consolidated into the client configuration.' => '',
   'Therefore the definition of "kg" with the base unit "g" and a factor of 1000 is valid while defining "g" with a base unit of "kg" and a factor of "0.001" is not.' => '',
@@ -3073,6 +3169,7 @@ $self->{texts} = {
   'This discount is only valid in purchase documents' => '',
   'This discount is only valid in records with customer or vendor' => '',
   'This discount is only valid in sales documents' => '',
+  'This export will include all records in the given time range and all supplicant information from checked entities. You will receive a single zip file. Please extract this file onto the data medium requested by your auditor.' => '',
   'This feature especially prevents mistakes by mixing up prior tax and sales tax.' => '',
   'This function requires the presence of articles with a time-based unit such as "h" or "min".' => '',
   'This group is valid for the following clients' => '',
@@ -3112,16 +3209,18 @@ $self->{texts} = {
   'This will set an exact price.' => '',
   'Three Options:'              => '',
   'Time Format'                 => '',
-  'Time and cost estimate'      => '',
+  'Time and price estimate'     => '',
   'Time estimate'               => '',
   'Time period for the analysis:' => '',
   'Time/cost estimate actions'  => '',
+  'Timerange'                   => '',
   'Timestamp'                   => '',
   'Title'                       => '',
   'To'                          => '',
   'To (email)'                  => '',
   'To (time)'                   => '',
   'To Date'                     => '',
+  'To Geierlein'                => '',
   'To continue please change the taxkey 0 to another value.' => '',
   'To user login'               => '',
   'Toggle marker'               => '',
@@ -3129,8 +3228,6 @@ $self->{texts} = {
   'Top'                         => '',
   'Top (CSS)'                   => '',
   'Top (Javascript)'            => '',
-  'Top 100'                     => '',
-  'Top 100 hinzufuegen'         => '',
   'Top Level Designation only'  => '',
   'Total'                       => '',
   'Total Fees'                  => '',
@@ -3167,6 +3264,7 @@ $self->{texts} = {
   'Transfer from warehouse'     => '',
   'Transfer in'                 => '',
   'Transfer in via default'     => '',
+  'Transfer of assemblies uses the assembly\'s default warehouse instead of the destination warehouse.' => '',
   'Transfer out'                => '',
   'Transfer out all items of a sales invoice when posting it. Items are transfered out acording to the settings above.' => '',
   'Transfer out on posting sales invoices?' => '',
@@ -3180,17 +3278,21 @@ $self->{texts} = {
   'Trial Balance'               => '',
   'Trial balance between %s and %s' => '',
   'Trying to call a sub without a name' => '',
+  'Typ'                         => '',
   'Type'                        => '',
+  'Type and Class'              => '',
   'Type can be either \'part\', \'service\' or \'assembly\'.' => '',
   'Type of Business'            => '',
   'Type of Customer'            => '',
   'Type of Vendor'              => '',
+  'TypeAbbreviation'            => '',
   'Types of Business'           => '',
   'USTVA'                       => '',
   'USTVA 2004'                  => '',
   'USTVA 2005'                  => '',
   'USTVA 2006'                  => '',
   'USTVA 2007'                  => '',
+  'USTVA Data sent to geierlein' => '',
   'USTVA-Hint: Method'          => '',
   'USTVA-Hint: Tax Authoritys'  => '',
   'USt-IdNr.'                   => '',
@@ -3224,13 +3326,16 @@ $self->{texts} = {
   'Update SKR04: new tax account 3804 (19%)' => '',
   'Update prices'               => '',
   'Update prices of existing entries' => '',
+  'Update prices of existing entries / skip non-existent' => '',
   'Update properties of existing entries' => '',
+  'Update properties of existing entries / skip non-existent' => '',
   'Update quotation/order'      => '',
   'Update sales order #1'       => '',
   'Update sales quotation #1'   => '',
   'Update this draft.'          => '',
   'Update with section'         => '',
   'Updated'                     => '',
+  'Updating data of existing entry in database' => '',
   'Updating existing entry in database' => '',
   'Updating items with additional parts' => '',
   'Updating items with sections' => '',
@@ -3238,6 +3343,8 @@ $self->{texts} = {
   'Updating the client fields in the database "#1" on host "#2:#3" failed.' => '',
   'Uploaded at'                 => '',
   'Uploaded on #1, size #2 kB'  => '',
+  'UsageE'                      => '',
+  'UsageWithout'                => '',
   'Use As New'                  => '',
   'Use Balance Sheet'           => '',
   'Use Datevautomatik'          => '',
@@ -3245,10 +3352,15 @@ $self->{texts} = {
   'Use Income'                  => 'Use GUV and BWA',
   'Use UStVA'                   => '',
   'Use WebDAV Repository'       => '',
+  'Use default booking group because setting is \'all\'' => '',
+  'Use default booking group because wanted is missing' => '',
+  'Use default warehouse for assembly transfer' => '',
   'Use existing templates'      => '',
   'Use linked items'            => '',
   'Use master default bin for Default Transfer, if no default bin for the part is configured' => '',
   'Useable for…'                => '',
+  'Used for Purchase'           => '',
+  'Used for Sale'               => '',
   'User'                        => '',
   'User Config'                 => '',
   'User Preferences'            => '',
@@ -3264,6 +3376,7 @@ $self->{texts} = {
   'Users, Clients and User Groups' => '',
   'Usually the sales quotation is valid until the next working day. If a value is set here then the quotation will be valid for at least that many days. The resulting date will be adjusted to the next working day if it ends up on a weekend.' => '',
   'VAT ID'                      => '',
+  'VN'                          => '',
   'Valid'                       => '',
   'Valid from'                  => '',
   'Valid until'                 => '',
@@ -3282,6 +3395,7 @@ $self->{texts} = {
   'Vendor Discount'             => '',
   'Vendor Invoice'              => '',
   'Vendor Invoices & AP Transactions' => '',
+  'Vendor Master Data'          => '',
   'Vendor Name'                 => '',
   'Vendor Number'               => '',
   'Vendor Order Number'         => '',
@@ -3309,8 +3423,10 @@ $self->{texts} = {
   'View/edit all employees sales documents' => '',
   'Von Konto: '                 => '',
   'WHJournal'                   => '',
+  'WHUsage'                     => '',
   'Warehouse'                   => '',
   'Warehouse (database ID)'     => '',
+  'Warehouse (name)'            => '',
   'Warehouse From'              => '',
   'Warehouse Migration'         => '',
   'Warehouse To'                => '',
@@ -3322,6 +3438,7 @@ $self->{texts} = {
   'Warn before saving orders with duplicate parts (new controller only)' => '',
   'Warning'                     => '',
   'Warning! Loading a draft will discard unsaved data!' => '',
+  'Warnings and errors'         => '',
   'WebDAV'                      => '',
   'WebDAV link'                 => '',
   'WebDAV save documents'       => '',
@@ -3419,12 +3536,15 @@ $self->{texts} = {
   'ap_aging_list'               => '',
   'ar_aging_list'               => '',
   'ar_chart isn\'t a valid chart' => '',
+  'article_list'                => '',
   'as at'                       => '',
   'assembled'                   => '',
   'assembly'                    => '',
   'assembly_list'               => '',
   'averaged values, in invoice mode only useful when filtered by a part' => '',
+  'averconsumed_br'             => '',
   'back'                        => '',
+  'back_br'                     => '',
   'balance'                     => '',
   'bank_collection_payment_list_#1' => '',
   'bank_transfer_payment_list_#1' => '',
@@ -3437,20 +3557,21 @@ $self->{texts} = {
   'cash'                        => '',
   'chargenumber #1'             => '',
   'chart_of_accounts'           => '',
-  'choice'                      => '',
-  'choice part'                 => '',
   'cleared'                     => '',
   'click here to edit cvars'    => '',
   'close'                       => '',
+  'close chart'                 => '',
   'closed'                      => '',
   'companylogo_subtitle'        => '',
   'config/kivitendo.conf: Key "DB_config" is missing.' => '',
   'config/kivitendo.conf: Key "authentication/ldap" is missing.' => '',
   'config/kivitendo.conf: Missing parameters in "authentication/database". Required parameters are "host", "db" and "user".' => '',
   'config/kivitendo.conf: Missing parameters in "authentication/ldap". Required parameters are "host", "attribute" and "base_dn".' => '',
+  'consumed'                    => '',
   'contact_list'                => '',
   'continue'                    => '',
   'correction'                  => '',
+  'correction_br'               => '',
   'cp_greeting to cp_gender migration' => '',
   'customer'                    => '',
   'customer_list'               => '',
@@ -3461,8 +3582,10 @@ $self->{texts} = {
   'delivered'                   => '',
   'deliverydate'                => '',
   'difference as skonto'        => '',
+  'difference_as_skonto'        => '',
   'direct debit'                => '',
   'disposed'                    => '',
+  'disposed_br'                 => '',
   'do not include'              => '',
   'done'                        => '',
   'dunning_list'                => '',
@@ -3475,7 +3598,9 @@ $self->{texts} = {
   'every third month'           => '',
   'every time'                  => '',
   'executed'                    => '',
+  'execution as user \'#1\''    => '',
   'failed'                      => '',
+  'false'                       => '',
   'female'                      => '',
   'flat-rate position'          => '',
   'follow_up_list'              => '',
@@ -3484,8 +3609,11 @@ $self->{texts} = {
   'for all'                     => '',
   'for date'                    => '',
   'found'                       => '',
+  'found_br'                    => '',
   'from (time)'                 => '',
   'general_ledger_list'         => '',
+  'generate cb/ob transactions for selected charts' => '',
+  'gobd-#1-#2.zip'              => '',
   'h'                           => '',
   'history'                     => '',
   'history search engine'       => '',
@@ -3516,13 +3644,13 @@ $self->{texts} = {
   'lead deleted!'               => '',
   'lead saved!'                 => '',
   'letters_list'                => '',
-  'list'                        => '',
   'list_of_payments'            => '',
   'list_of_receipts'            => '',
   'list_of_transactions'        => '',
   'male'                        => '',
   'mark as paid'                => '',
   'missing'                     => '',
+  'missing_br'                  => '',
   'month'                       => '',
   'monthly'                     => '',
   'never'                       => '',
@@ -3531,6 +3659,7 @@ $self->{texts} = {
   'no article assigned yet'     => '',
   'no bestbefore'               => '',
   'no chargenumber'             => '',
+  'no execution for this client' => '',
   'no skonto_chart configured for taxkey #1 : #2 : #3' => '',
   'no tax_id in acc_trans'      => '',
   'not configured'              => '',
@@ -3594,18 +3723,22 @@ $self->{texts} = {
   'saved'                       => '',
   'saved!'                      => '',
   'saving data'                 => '',
+  'searched part not for purchase' => '',
+  'searched part not for sale'  => '',
   'semiannually'                => '',
   'sent'                        => '',
   'sent to printer'             => '',
   'service'                     => '',
   'service_list'                => '',
   'shipped'                     => '',
+  'shipped_br'                  => '',
   'singular first char'         => '',
-  'soldtotal'                   => '',
   'sort items'                  => '',
   'stock'                       => '',
+  'stock_br'                    => '',
   'submit'                      => '',
   'succeeded'                   => '',
+  'sum'                         => '',
   'tax_chartaccno'              => '',
   'tax_percent'                 => '',
   'tax_rate'                    => '',
@@ -3626,6 +3759,7 @@ $self->{texts} = {
   'transferred in / out'        => '',
   'transferred out'             => '',
   'trial_balance'               => '',
+  'true'                        => '',
   'uncleared'                   => '',
   'unconfigured'                => '',
   'uncorrect partnumber '       => '',
@@ -3633,6 +3767,7 @@ $self->{texts} = {
   'use program settings'        => '',
   'use user config'             => '',
   'used'                        => '',
+  'used_br'                     => '',
   'valid from'                  => '',
   'vendor'                      => '',
   'vendor_invoice_list'         => '',
@@ -3640,9 +3775,12 @@ $self->{texts} = {
   'waiting for job to be started' => '',
   'warehouse_journal_list'      => '',
   'warehouse_report_list'       => '',
+  'warehouse_usage_list'        => '',
   'with amount'                 => '',
   'with skonto acc. to pt'      => '',
+  'with_skonto_pt'              => '',
   'without skonto'              => '',
+  'without_skonto'              => '',
   'working copy'                => '',
   'wrongformat'                 => '',
   'yearly'                      => '',
diff --git a/menus/user/13-parts-classification.yaml b/menus/user/13-parts-classification.yaml
new file mode 100644 (file)
index 0000000..5cffd75
--- /dev/null
@@ -0,0 +1,14 @@
+#
+# opendynamic features
+#
+---
+#
+# parts classification
+#
+- parent: system
+  id: system_parts_classification
+  name: Parts Classification
+  icon: partsclassific
+  order: 1100
+  params:
+    action: PartsClassification/list
diff --git a/sql/Pg-upgrade2/parts_classifications.sql b/sql/Pg-upgrade2/parts_classifications.sql
new file mode 100644 (file)
index 0000000..1f65d72
--- /dev/null
@@ -0,0 +1,19 @@
+-- @tag: parts_classifications
+-- @description: "zusätzliche Tabelle mit Flags zur Klassifizierung von Artikeln"
+-- @depends: release_3_4_1
+CREATE TABLE parts_classifications (
+    id SERIAL PRIMARY KEY,
+    description text,
+    abbreviation text,
+    used_for_purchase BOOLEAN DEFAULT 't',
+    used_for_sale     BOOLEAN DEFAULT 't'
+);
+
+INSERT INTO parts_classifications values(0,'-------'    ,'None (typeabbreviation)','f','f');
+INSERT INTO parts_classifications values(1,'Purchase'   ,'Purchase (typeabbreviation)'   ,'t','f');
+INSERT INTO parts_classifications values(2,'Sales'      ,'Sales (typeabbreviation)'      ,'f','t');
+INSERT INTO parts_classifications values(3,'Merchandise','Merchandise (typeabbreviation)','t','t');
+INSERT INTO parts_classifications values(4,'Production' ,'Production (typeabbreviation)' ,'f','t');
+SELECT setval('parts_classifications_id_seq',4);
+ALTER TABLE parts ADD COLUMN classification_id integer DEFAULT 0;
+ALTER TABLE parts ADD CONSTRAINT classification_fkey FOREIGN KEY (classification_id) REFERENCES parts_classifications(id);
index 8a27d79..8937208 100644 (file)
@@ -1,4 +1,4 @@
-use Test::More tests => 33;
+use Test::More tests => 43;
 
 use strict;
 
@@ -71,7 +71,7 @@ sub reset_state {
     module   => 'IC',
     name     => 'mycvar',
     type     => 'text',
-    description => 'mein schattz',
+    description => 'mein Schatz',
     searchable  => 1,
     sortkey => 1,
     includeable => 0,
@@ -94,6 +94,7 @@ sub test_import {
     controller => $controller,
     file       => $file,
   );
+  #print "profile param type=".$csv_part_import->settings->{parts_type}."\n";
 
   $csv_part_import->test_run(0);
   $csv_part_import->csv(SL::Helper::Csv->new(file                    => $csv_part_import->file,
@@ -153,6 +154,7 @@ my $settings1 = {
                        article_number_policy     => 'update_prices',
                        shoparticle_if_missing    => '0',
                        part_type                 => 'part',
+                       parts_classification      => 3,
                        default_buchungsgruppe    => ($bugru ? $bugru->id : undef),
                        apply_buchungsgruppe      => 'all',
                 };
@@ -162,7 +164,8 @@ my $settings2 = {
                        sellprice_adjustment_type => 'percent',
                        article_number_policy     => 'update_parts',
                        shoparticle_if_missing    => '0',
-                       part_type                 => 'part',
+                       part_type                 => 'mixed',
+                       parts_classification      => 4,
                        default_buchungsgruppe    => ($bugru ? $bugru->id : undef),
                        apply_buchungsgruppe      => 'missing',
                        default_unit              => 'Stck',
@@ -204,29 +207,31 @@ is $entry->{object}->listprice,  '97.3', 'updated listprice';
 ##### insert parts with warehouse,bin name
 
 $file = \<<EOL;
-partnumber;description;warehouse;bin
-P1000;Teil 1000;Lager1;Ort1_von_Lager1
-P1001;Teil 1001;Lager1;Ort2_von_Lager1
-P1002;Teil 1002;Lager2;Ort1_von_Lager2
-P1003;Teil 1003;Lager2;Ort2_von_Lager2
+partnumber;description;warehouse;bin;part_type
+P1000;Teil 1000;Lager1;Ort1_von_Lager1;part
+P1001;Teil 1001;Lager1;Ort2_von_Lager1;service
+P1002;Teil 1002;Lager2;Ort1_von_Lager2;service
+P1003;Teil 1003;Lager2;Ort2_von_Lager2;part
 EOL
 $entries = test_import($file,$settings2);
 $entry = $entries->[0];
 is $entry->{object}->description, 'Teil 1000', 'Teil 1000 set';
 is $entry->{object}->warehouse_id, $wh1->id, 'Lager1';
 is $entry->{object}->bin_id, $bin1_1->id, 'Lagerort1';
+is $entry->{object}->part_type, 'part', 'Typ ist part';
 $entry = $entries->[2];
 is $entry->{object}->description, 'Teil 1002', 'Teil 1002 set';
 is $entry->{object}->warehouse_id, $wh2->id, 'Lager2';
 is $entry->{object}->bin_id, $bin2_1->id, 'Lagerort1';
+is $entry->{object}->part_type, 'service', 'Typ ist service';
 
 ##### update warehouse and bin
 $file = \<<EOL;
-partnumber;description;warehouse;bin
-P1000;Teil 1000;Lager2;Ort1_von_Lager2
-P1001;Teil 1001;Lager1;Ort1_von_Lager1
-P1002;Teil 1002;Lager2;Ort1_von_Lager1
-P1003;Teil 1003;Lager2;kein Lagerort
+partnumber;description;warehouse;bin;part_type
+P1000;Teil 1000;Lager2;Ort1_von_Lager2;part
+P1001;Teil 1001;Lager1;Ort1_von_Lager1;part
+P1002;Teil 1002;Lager2;Ort1_von_Lager1;part
+P1003;Teil 1003;Lager2;kein Lagerort;part
 EOL
 $entries = test_import($file,$settings2);
 $entry = $entries->[0];
@@ -269,16 +274,41 @@ is $l->longdescription, 'notes IT','IT notes set';
 ##### add customvar
 $file = \<<EOL;
 partnumber;cvar_mycvar
-P1000;das ist der ring
-P1001;nicht der nibelungen
+P1000;das ist der Ring
+P1001;nicht der Nibelungen
 P1002;sondern vom
 P1003;Herr der Ringe
 EOL
 $entries = test_import($file,$settings2);
 $entry = $entries->[0];
 is $entry->{object}->partnumber, 'P1000', 'P1000 set';
-is $entry->{raw_data}->{cvar_mycvar},'das ist der ring','CVAR set';
-is @{$entry->{object}->custom_variables}[0]->text_value,'das ist der ring','Cvar mit richtigem Weert';
+is $entry->{raw_data}->{cvar_mycvar},'das ist der Ring','CVAR set';
+is @{$entry->{object}->custom_variables}[0]->text_value,'das ist der Ring','Cvar mit richtigem Wert';
+
+# set locale to de so we can match abbreviations
+$::locale = $old_locale;
+##### import part classification
+$file = \<<EOL;
+partnumber;pclass;description
+W1000;WE;Teil 1000
+W1001;WV;Teil 1001
+D1002;DV;Dienstleistung 1002
+D1003;DH;Dienstleistung 1003
+EOL
+$entries = test_import($file,$settings2);
+$entry = $entries->[0];
+is $entry->{object}->classification_id, '1', 'W1000 von Klasse Einkauf';
+is $entry->{object}->type, 'part', 'W1000 vom Type part';
+$entry = $entries->[1];
+is $entry->{object}->classification_id, '2', 'W1001 von Klasse Verkauf';
+is $entry->{object}->type, 'part', 'W1001 vom Type part';
+$entry = $entries->[2];
+is $entry->{object}->classification_id, '2', 'D1002 von Klasse Verkauf';
+is $entry->{object}->type, 'service', 'D1002 vom Type service';
+$entry = $entries->[3];
+is $entry->{object}->classification_id, '3', 'D1003 von Klasse Handelsware';
+is $entry->{object}->type, 'service', 'D1003 vom Type service';
+
 
 clear_up(); # remove all data at end of tests
 
index a54dd93..ba067ab 100644 (file)
@@ -1,5 +1,6 @@
 [% USE LxERP %]
 [% USE L %]
+[% USE P %]
 <tr>
  <th align="right">[%- LxERP.t8('Parts with existing part numbers') %]:</th>
  <td colspan="10">
 <tr>
  <th align="right">[%- LxERP.t8('Type') %]:</th>
  <td colspan="10">
-  [% opts = [ [ 'part', LxERP.t8('Parts') ], [ 'service', LxERP.t8('Services') ], [ 'mixed', LxERP.t8('Mixed (requires column "type")') ] ] %]
+  [% opts = [ [ 'part', LxERP.t8('Parts') ], [ 'service', LxERP.t8('Services') ], [ 'mixed', LxERP.t8('Mixed (requires column "type" or "pclass")') ] ] %]
   [% L.select_tag('settings.part_type', opts, default = SELF.profile.get('part_type'), style = 'width: 300px') %]
  </td>
 </tr>
+<tr>
+ <th align="right">[%- LxERP.t8('Parts Classification') %]:</th>
+ <td colspan="10">
+  [% P.select_classification('settings.parts_classification', default = SELF.profile.get('parts_classification'), style = 'width: 300px') %]
+ </td>
+</tr>
 
 <tr>
  <th align="right" valign="top">[%- LxERP.t8('Default booking group') %]:</th>
index 3796ced..1ab5453 100644 (file)
    </p>
    <p>
     [3]:
-    [% LxERP.t8("If the article type is set to 'mixed' then a column called 'type' must be present.") %]
+    [% LxERP.t8("If the article type is set to 'mixed' then a column called 'part_type' or called 'pclass' must be present.") %]
     [% LxERP.t8("Type can be either 'part', 'service' or 'assembly'.") %]
-    [% LxERP.t8("Assemblies can not be imported (yet). But the type column is used for sanity checks on price updates in order to prevent that articles with the wrong type will be updated.") %]
+    [%- LxERP.t8("If column 'pclass' is present the article type is then irrelevant or used as default ") %]
+    [% LxERP.t8("The 'pclass' column has the same abbreviation like a part export. The first letter is for the type Part,Assembly or Service, the second(and third) for Part Classification") %]
    </p>
 
 [%- ELSIF SELF.type == 'inventories' %]
index ef7799b..d79f88a 100644 (file)
@@ -1,7 +1,15 @@
 [%- USE T8 %]
 [%- USE HTML %]
+<form method="post" action="ic.pl">
+[%- IF is_wrong_pclass == 1 %]
+<h4 class="error">[% 'searched part not for sale' | $T8 %]</h4>
+[%- ELSE %]
+[%- IF is_wrong_pclass == 2 %]
+<h4 class="error">[% 'searched part not for purchase' | $T8 %]</h4>
+[%- ELSE %]
+[% IF INSTANCE_CONF.get_create_part_if_notfound %]
 
-    <h4 class="error">[% 'Item not on file!' | $T8 %]
+    <h4 class="error">[% 'Item does not exists in File' | $T8 %]
 
     <p>[% 'What type of item is this?' | $T8 %]</h4>
 
 
       <input type="hidden" name="action" value="dispatcher">
       <input class="submit" type="submit" name="action_add" value="[% 'Continue' | $T8 %]">
+[%- ELSE %]
+    <h4 class="error">[% 'Item does not exists in File' | $T8 %]</h4>
+[%- END %]
+[%- END %]
+[%- END %]
       <input id='back_button' type='button' class="submit" value="[% 'Back' | $T8 %]">
-    </form>
+    </p>
+  </form>
 <script type='text/javascript'>
   $(function(){ $('#back_button').click(function(){ window.history.back(-1) }) })
 </script>
index 401b54a..6b5aa4e 100644 (file)
@@ -30,7 +30,7 @@
      </tr>
 [%- END %]
      <tr>
-      <td colspan="6"></td>
+      <td colspan="7"></td>
       <td>[% 'Totals' | $T8 %]</td>
       <td align="right">[%- LxERP.format_amount(assembly_purchase_price_total, 2) %]</td>
       <td align="right">[%- LxERP.format_amount(assemblytotal, 2) %]</td>
index ebd0b5b..0e8ac81 100644 (file)
               <th align="right">[% 'Part Number' | $T8 %]</th>
               <td><input id='partnumber' name="partnumber" value="[% HTML.escape(partnumber) %]" size="40" class="initial_focus"></td>
              </tr>
+             <tr>
+              <th align="right" nowrap>[% 'Parts Classification' | $T8 %]:</th>
+              <td>[% P.select_classification('classification_id', default=classification_id) %]</td>
+             </tr>
              <tr>
               <th align="right">[% 'Part Description' | $T8 %]</th>
               <td>
index 499cdcb..885b83d 100644 (file)
@@ -3,15 +3,11 @@
 
  <input name="callback" type="hidden" value="[% HTML.escape(callback) %]">
 
- <input type="hidden" name="item" value="[% HTML.escape(searchitems) %]">
+ <input type="hidden" name="item" id="item_id" value="part">
  <input type="hidden" name="action" value="add">
 
- [% SWITCH searchitems %]
-   [% CASE 'part' %][% 'New part' | $T8 %]
-   [% CASE 'service' %][% 'New service' | $T8 %]
-   [% CASE 'assembly' %][% 'New assembly' | $T8 %]
- [% END %]
- <br>
- <input class="submit" type="submit" value="[%- 'Add' | $T8 %]">
+ <input class="submit" type="submit" onclick="$('#item_id').val('part');return true;" value="[%- 'Add Part' | $T8 %]">
+ <input class="submit" type="submit" onclick="$('#item_id').val('service');return true;" value="[%- 'Add Service' | $T8 %]">
+ <input class="submit" type="submit" onclick="$('#item_id').val('assembly');return true;" value="[%- 'Add Assembly' | $T8 %]">
 
 </form>
index fafcacc..aeb7eaf 100644 (file)
@@ -2,6 +2,7 @@
 [%- USE HTML %]
 [%- USE LxERP %]
 [%- USE L %]
+[%- USE P %]
 <h1>[% title %]</h1>
 
  <form method="post" action="ic.pl">
    <tr valign="top">
     <td>
      <table>
+      <tr>
+       <th align="right" nowrap>[% 'PType' | $T8 %]</th>
+       <td  colspan="4" ><table><tr>
+        <td>
+          <input name="l_part" id="l_part" class="checkbox" type="checkbox" value="Y" checked>
+          <label for="l_part">[% 'Part' | $T8 %]</label>
+        </td>
+        <td>
+          <input name="l_service" id="l_service" class="checkbox" type="checkbox" value="Y" checked>
+          <label for="l_service">[% 'Service' | $T8 %]</label>
+        </td>
+        <td>
+          <input name="l_assembly" id="l_assembly" class="checkbox" type="checkbox" value="Y" checked>
+          <label for="l_assembly">[% 'Assembly' | $T8 %]</label>
+        </td>
+       </tr></table></td>
+      </tr>
       <tr>
        <th align="right" nowrap>[% 'Part Number' | $T8 %]</th>
        <td><input name="partnumber" size="20"></td>
        <th align="right" nowrap>[% 'EAN' | $T8 %]</th>
        <td><input name="ean" size="20"></td>
       </tr>
-
+      <tr>
+       <th align="right" nowrap>[% 'Parts Classification' | $T8 %]:</th>
+       <td>[% P.select_classification('classification_id') %]</td>
+      </tr>
       <tr>
        <th align="right" nowrap>[% 'Part Description' | $T8 %]</th>
        <td colspan="3"><input name="description" size="40" class="initial_focus"></td>
       </tr>
-
       <tr>
        <th align="right" nowrap>[% 'Group' | $T8 %]</th>
        <td>
index 668b2e9..1388d31 100644 (file)
@@ -12,6 +12,7 @@
       <th>&nbsp;</th>
     [%- END %]
     <th>[% LxERP.t8('Number') %]</th>
+    <th>[% LxERP.t8('Part Classification') %]</th>
     <th>[% LxERP.t8('Part Description') %]</th>
     [%- IF INSTANCE_CONF.get_show_longdescription_select_item %]
       [% SET COLS = COLS + 1 %]
@@ -35,6 +36,7 @@
       <td><input name="select_item_id" class="radio" type="radio" value="[% HTML.escape(item.id) %]"[% IF loop.first %] checked[% END %]></td>
     [%- END %]
     <td>[% HTML.escape(item.partnumber) %]</td>
+    <td>[% HTML.escape(item.type_and_classific) %]</td>
     <td>[% HTML.escape(item.description) %]</td>
     [%- IF INSTANCE_CONF.get_show_longdescription_select_item %]
       <td>[% P.restricted_html(item.longdescription) %]</td>
index 1e2bbd1..7d262f5 100644 (file)
      [% IF SELF.orphaned %]
      <th class="listheading" style='text-align:center' nowrap width="1"><img src="image/updown.png" alt="[%- LxERP.t8('reorder item') %]"></th>
      [% END %]
-     <th id="partnumber_header_id"  class="listheading" nowrap width="15"><a href='#' onClick='javascript:kivi.Part.reorder_items("partnumber")' >[%- 'Partnumber'  | $T8 %]</a></th>
+     <th id="partnumber_header_id"  class="listheading" nowrap width="5"><a href='#' onClick='javascript:kivi.Part.reorder_items("partnumber")' >[%- 'Partnumber'  | $T8 %]</a></th>
+     <th class="listheading" nowrap width="5">[% 'Type' | $T8 %]</th>
      <th id="partdescription_header_id"  class="listheading" nowrap width="15"><a href='#' onClick='javascript:kivi.Part.reorder_items("description")' >[%- 'Description' | $T8 %]</a></th>
-     <th id="qty_header_id"         class="listheading" nowrap width="5" ><a href='#' onClick='javascript:kivi.Part.reorder_items("qty")'        >[%- 'Qty'         | $T8 %]</a></th>
+     <th id="qty_header_id"         class="listheading" nowrap width="15" ><a href='#' onClick='javascript:kivi.Part.reorder_items("qty")'        >[%- 'Qty'         | $T8 %]</a></th>
      <th class="listheading" nowrap width="5" >[%- 'Unit'         | $T8 %] </th>
      <th class="listheading" nowrap width="5" >[%- 'BOM'          | $T8 %] </th>
      <th class="listheading" nowrap width="5" >[%- 'Line Total'   | $T8 %] </th>
index 82e5c46..9254812 100644 (file)
@@ -24,6 +24,9 @@
     <td nowrap>
        [% P.part(ITEM.part) %]
     </td>
+    <td nowrap>
+       [% P.type_abbreviation(ITEM.part.part_type) %][% P.classification_abbreviation(ITEM.part.classification_id) %]
+    </td>
     <td>
        [% HTML.escape(ITEM.part.description) %]
     </td>
index 0fb2b3b..1131c20 100644 (file)
               [% UNLESS SELF.part.id %][% readonly = 0 %][% END %]
               <td>[% L.input_tag("part.partnumber", SELF.part.partnumber, size=40, readonly=readonly class="initial_focus") %]</td>
              </tr>
+             <tr>
+              <th align="right">[% 'Part Classification' | $T8 %]</th>
+              <td>[% P.select_classification('part.classification_id', default => SELF.part.classification_id) %]</td>
+             </tr>
              <tr>
               <th align="right">[% 'Part Description' | $T8 %]</th>
               <td>
diff --git a/templates/webpages/parts_classification/form.html b/templates/webpages/parts_classification/form.html
new file mode 100755 (executable)
index 0000000..26760b4
--- /dev/null
@@ -0,0 +1,37 @@
+[% USE HTML %][% USE L %][% USE LxERP %]
+<h1>[% FORM.title %]</h1>
+
+ <form method="post" action="controller.pl">
+
+[%- INCLUDE 'common/flash.html' %]
+
+  <table>
+   <tr>
+    <td>[% LxERP.t8('Description') %]</td>
+    <td>[% L.input_tag("parts_classification.description",  LxERP.t8(SELF.parts_classification.description)) %]</td>
+   </tr>
+   <tr>
+    <td>[% LxERP.t8('TypeAbbreviation') %]</td>
+    <td>[% L.input_tag("parts_classification.abbreviation",  LxERP.t8(SELF.parts_classification.abbreviation),size=>"2",maxlength=>"2" ) %]</td>
+   </tr>
+   <tr>
+    <td>[% LxERP.t8('Used for Purchase') %]</td>
+    <td>[% L.checkbox_tag("parts_classification.used_for_purchase", checked=(SELF.parts_classification.used_for_purchase ? 1:'')) %]</td>
+   </tr>
+   <tr>
+    <td>[% LxERP.t8('Used for Sale') %]</td>
+    <td>[% L.checkbox_tag("parts_classification.used_for_sale", checked=(SELF.parts_classification.used_for_sale ? 1:'')) %]</td>
+   </tr>
+  </table>
+
+  <p>
+   [% L.hidden_tag("id", SELF.parts_classification.id) %]
+   [% L.hidden_tag("action", "PartsClassification/dispatch") %]
+   [% L.submit_tag("action_" _ (SELF.parts_classification.id ? 'update' : 'create'), LxERP.t8('Save')) %]
+   [%- IF SELF.parts_classification.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>
diff --git a/templates/webpages/parts_classification/list.html b/templates/webpages/parts_classification/list.html
new file mode 100644 (file)
index 0000000..9226dc2
--- /dev/null
@@ -0,0 +1,47 @@
+[% USE HTML %][% USE L %][% USE LxERP %]
+<h1>[% FORM.title %]</h1>
+
+[%- INCLUDE 'common/flash.html' %]
+
+ <form method="post" action="controller.pl">
+  [% IF !PARTS_CLASSIFICATIONS.size %]
+   <p>
+    [%-  LxERP.t8('No parts classification has been created yet.') %]
+   </p>
+
+  [%- ELSE %]
+   <table id="parts_classification_list">
+    <thead>
+    <tr class="listheading">
+     <th align="center"><img src="image/updown.png" alt="[%- LxERP.t8('reorder item') %]"></th>
+     <th>[%- LxERP.t8('Description') %]</th>
+     <th>[%- LxERP.t8('TypeAbbreviation') %]</th>
+     <th>[%- LxERP.t8('Used for Purchase') %]</th>
+     <th>[%- LxERP.t8('Used for Sale') %]</th>
+    </tr>
+    </thead>
+
+    <tbody>
+    [%- FOREACH parts_classification = PARTS_CLASSIFICATIONS %]
+    <tr class="listrow[% loop.count % 2 %]" id="parts_classification_id_[% parts_classification.id %]">
+     <td align="center" class="dragdrop"><img src="image/updown.png" alt="[%- LxERP.t8('reorder item') %]"></td>
+     <td>
+      <a href="[% SELF.url_for(action => 'edit', id => parts_classification.id) %]">
+       [%- HTML.escape(LxERP.t8(parts_classification.description)) %]
+      </a>
+     </td>
+     <td>[%- HTML.escape(LxERP.t8(parts_classification.abbreviation)) %]</td>
+     <td>[% IF parts_classification.used_for_purchase %][% LxERP.t8('Yes') %][% ELSE %][%  LxERP.t8('No') %][% END %]</td>
+     <td>[% IF parts_classification.used_for_sale     %][% LxERP.t8('Yes') %][% ELSE %][%  LxERP.t8('No') %][% END %]</td>
+    </tr>
+    [%- END %]
+    </tbody>
+   </table>
+  [%- END %]
+
+  <p>
+   <a href="[% SELF.url_for(action => 'new') %]">[%- LxERP.t8('Create a new parts classification') %]</a>
+  </p>
+ </form>
+
+ [% L.sortable_element('#parts_classification_list tbody', url => 'controller.pl?action=PartsClassification/reorder', with => 'parts_classification_id') %]
index 07ec2e7..8b342d7 100644 (file)
@@ -1,5 +1,6 @@
 [%- USE T8 %]
 [%- USE L %]
+[%- USE P %]
 [%- USE HTML %][%- USE JavaScript %]
 <h1>[% 'Report about warehouse transactions' | $T8 %]</h1>
 
         <th align="right" nowrap>[% 'Part Number' | $T8 %]:</th>
         <td><input name="partnumber" id="partnumber" size=20></td>
        </tr>
+       <tr>
+        <th align="right" nowrap>[% 'Parts Classification' | $T8 %]:</th>
+        <td>[% P.select_classification('classification_id') %]</td>
+       </tr>
        <tr>
         <th align="right" nowrap>[% 'Part Description' | $T8 %]:</th>
         <td><input name="description" size=40></td>
index 70fb4f1..d68ebea 100644 (file)
@@ -1,5 +1,6 @@
 [%- USE T8 %]
 [%- USE L %]
+[%- USE P %]
 [%- USE HTML %][%- USE JavaScript %]
 <h1>[% 'Report about warehouse contents' | $T8 %]</h1>
 
         <th align="right" nowrap>[% 'Part Number' | $T8 %]:</th>
         <td><input name="partnumber" size=20></td>
        </tr>
+       <tr>
+        <th align="right" nowrap>[% 'Parts Classification' | $T8 %]:</th>
+        <td>[% P.select_classification('classification_id') %]</td>
+       </tr>
        <tr>
         <th align="right" nowrap>[% 'Part Description' | $T8 %]:</th>
         <td><input name="description" size=40></td>