Artikel-Klassifizierung
authorMartin Helmling martin.helmling@octosoft.eu <martin.helmling@octosoft.eu>
Thu, 22 Dec 2016 07:37:45 +0000 (08:37 +0100)
committerMartin Helmling martin.helmling@octosoft.eu <martin.helmling@octosoft.eu>
Wed, 11 Jan 2017 07:20:38 +0000 (08:20 +0100)
    Die Klassifizierung von Artikeln 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)

    Anpassung des CSV Import,
    nun wird alternativ zur 'part_type'-Spalte die 'pclass'-Spalte mit zwei Buchstaben geparsed und entsprechend
    classification_id, part_type gesetzt.

37 files changed:
SL/Controller/CsvImport/Part.pm
SL/Controller/Inventory.pm
SL/Controller/PartClassification.pm [new file with mode: 0644]
SL/DB/Helper/ALL.pm
SL/DB/Helper/Mappings.pm
SL/DB/Manager/PartClassification.pm [new file with mode: 0644]
SL/DB/MetaSetup/Part.pm
SL/DB/MetaSetup/PartClassification.pm [new file with mode: 0644]
SL/DB/PartClassification.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
js/locale/en.js [new file with mode: 0644]
locale/de/all
locale/en/all
menus/user/00-erp.yaml
sql/Pg-upgrade2/part_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/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/part_classification/form.html [new file with mode: 0755]
templates/webpages/part_classification/list.html [new file with mode: 0644]
templates/webpages/wh/journal_filter.html
templates/webpages/wh/report_filter.html

index ef7e238..069dad2 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',
+                       part_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::PartClassification->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') {
-    $part_type = $entry->{raw_data}->{part_type} =~ m/^p/i ? 'part'
-               : $entry->{raw_data}->{part_type} =~ m/^s/i ? 'service'
+  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 => 'part_classification',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')                                               },
                                 );
 
index c7df4e9..6a7dadc 100644 (file)
@@ -59,7 +59,6 @@ sub action_stock_usage {
   $self->render('inventory/warehouse_usage',
                 title => $::form->{title},
                 year => DateTime->today->year,
-  #              PARTSCLASSIFICATIONS => SL::DB:Manager::PartsClassification->get_all_classifications_by_name() ,
                 WAREHOUSES => $::form->{WAREHOUSES},
                 WAREHOUSE_FILTER => 1,
                 warehouse_id => 0,
diff --git a/SL/Controller/PartClassification.pm b/SL/Controller/PartClassification.pm
new file mode 100644 (file)
index 0000000..5bc4e84
--- /dev/null
@@ -0,0 +1,229 @@
+package SL::Controller::PartClassification;
+
+use strict;
+
+use parent qw(SL::Controller::Base);
+
+use SL::DB::PartClassification;
+use SL::Helper::Flash;
+
+use Rose::Object::MakeMethods::Generic
+(
+ scalar => [ qw(part_classification) ],
+);
+
+__PACKAGE__->run_before('check_auth');
+__PACKAGE__->run_before('load_part_classification', only => [ qw(edit update destroy) ]);
+
+#
+# This Controller is responsible for creating,editing or deleting
+# Part Classifications.
+#
+# The use of Part Classifications is described in SL::DB::PartClassification
+#
+#
+
+# List all available part classifications
+#
+
+sub action_list {
+  my ($self) = @_;
+
+  $self->render('part_classification/list',
+                title                => $::locale->text('Parts Classifications'),
+                PART_CLASSIFICATIONS => SL::DB::Manager::PartClassification->get_all_sorted);
+}
+
+# A Form for a new creatable part classifications is generated
+#
+sub action_new {
+  my ($self) = @_;
+
+  $self->{part_classification} = SL::DB::PartClassification->new;
+  $self->render('part_classification/form', title => $::locale->text('Create a new parts classification'));
+}
+
+# Edit an existing part classifications
+#
+sub action_edit {
+  my ($self) = @_;
+  $self->render('part_classification/form', title => $::locale->text('Edit parts classification'));
+}
+
+# A new part classification is saved
+#
+sub action_create {
+  my ($self) = @_;
+
+  $self->{part_classification} = SL::DB::PartClassification->new;
+  $self->create_or_update;
+}
+
+# An existing part classification is saved
+#
+sub action_update {
+  my ($self) = @_;
+  $self->create_or_update;
+}
+
+# An existing part classification is deleted
+#
+# The basic classifications cannot be deleted, also classifications which are in use
+#
+sub action_destroy {
+  my ($self) = @_;
+
+  if ( $self->{part_classification}->id < 5 ) {
+    flash_later('error', $::locale->text('The basic parts classification cannot be deleted.'));
+  }
+  elsif (eval { $self->{part_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');
+}
+# reordering the lines
+#
+sub action_reorder {
+  my ($self) = @_;
+
+  SL::DB::PartClassification->reorder_list(@{ $::form->{part_classification_id} || [] });
+
+  $self->render(\'', { type => 'json' });
+}
+
+#
+# filters
+#
+
+# check authentication, only "config" is allowed
+#
+sub check_auth {
+  $::auth->assert('config');
+}
+
+#
+# helpers
+#
+
+# submethod for update the database
+#
+sub create_or_update {
+  my $self   = shift;
+  my $is_new = !$self->{part_classification}->id;
+
+  $::form->{part_classification}->{used_for_purchase} = 0 if ! $::form->{part_classification}->{used_for_purchase};
+  $::form->{part_classification}->{used_for_sale}     = 0 if ! $::form->{part_classification}->{used_for_sale};
+
+  my $params = delete($::form->{part_classification}) || { };
+
+  $self->{part_classification}->assign_attributes(%{ $params });
+
+  my @errors = $self->{part_classification}->validate;
+
+  if (@errors) {
+    flash('error', @errors);
+    $self->render('part_classification/form', title => $is_new ? $::locale->text('Create a new parts classification') : $::locale->text('Edit parts classification'));
+    return;
+  }
+
+  $self->{part_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');
+}
+
+# submethod for loading one item from the database
+#
+sub load_part_classification {
+  my ($self) = @_;
+  $self->{part_classification} = SL::DB::PartClassification->new(id => $::form->{id})->load;
+}
+
+1;
+
+
+
+__END__
+
+=encoding utf-8
+
+=head1 NAME
+
+SL::Controller::PartClassification
+
+=head1 SYNOPSIS
+
+This Controller is responsible for creating,editing or deleting
+Part Classifications.
+
+=head1 DESCRIPTION
+
+The use of Part Classifications is described in L<SL::DB::PartClassification>
+
+=head1 METHODS
+
+=head2 action_create
+
+ $self->action_create();
+
+A new part classification is saved
+
+
+
+=head2 action_destroy
+
+ $self->action_destroy();
+
+An existing part classification is deleted
+
+The basic classifications cannot be deleted, also classifications which are in use
+
+
+
+=head2 action_edit
+
+ $self->action_edit();
+
+Edit an existing part classifications
+
+
+
+=head2 action_list
+
+ $self->action_list();
+
+List all available part classifications
+
+
+
+=head2 action_new
+
+ $self->action_new();
+
+A Form for a new creatable part classifications is generated
+
+
+
+=head2 action_reorder
+
+ $self->action_reorder();
+
+reordering the lines
+
+
+
+=head2 action_update
+
+ $self->action_update();
+
+An existing part classification is saved
+
+
+=head1 AUTHOR
+
+Martin Helmling E<lt>martin.helmling@opendynamic.deE<gt>
+
+=cut
index bf6bd40..a896cc3 100644 (file)
@@ -71,6 +71,7 @@ use SL::DB::Object;
 use SL::DB::Order;
 use SL::DB::OrderItem;
 use SL::DB::Part;
+use SL::DB::PartClassification;
 use SL::DB::PartsGroup;
 use SL::DB::PartsPriceHistory;
 use SL::DB::PaymentTerm;
index 7d1907d..3a6f965 100644 (file)
@@ -153,6 +153,7 @@ my %kivitendo_package_names = (
   oe                             => 'order',
   parts                          => 'part',
   partsgroup                     => 'parts_group',
+  part_classifications           => 'PartClassification',
   parts_price_history            => 'PartsPriceHistory',
   payment_terms                  => 'payment_term',
   periodic_invoices              => 'periodic_invoice',
diff --git a/SL/DB/Manager/PartClassification.pm b/SL/DB/Manager/PartClassification.pm
new file mode 100644 (file)
index 0000000..0924011
--- /dev/null
@@ -0,0 +1,56 @@
+package SL::DB::Manager::PartClassification;
+
+use strict;
+
+use parent qw(SL::DB::Helper::Manager);
+use SL::DB::Helper::Sorted;
+
+sub object_class { 'SL::DB::PartClassification' }
+
+__PACKAGE__->make_manager_methods;
+
+#
+# get the one/two character shortcut of the parts classification
+#
+sub get_abbreviation {
+  my ($class,$id) = @_;
+  my $obj = $class->get_first(query => [ id => $id ]);
+  return '' unless $obj;
+  return $obj->abbreviation?$obj->abbreviation:'';
+}
+
+1;
+
+
+__END__
+
+=encoding utf-8
+
+=head1 NAME
+
+SL::DB::Manager::PartClassification
+
+
+=head1 SYNOPSIS
+
+This class has wrapper methodes to get the shortcuts
+
+=head1 METHODS
+
+=head2 get_abbreviation
+
+ $class->get_abbreviation($classification_id);
+
+get the one/two character shortcut of the parts classification
+
+=head2 get_separate_abbreviation
+
+ $class->get_separate_abbreviation($classification_id);
+
+get the one/two character shortcut of the parts classification if it is a separate article
+
+=head1 AUTHOR
+
+Martin Helmling E<lt>martin.helmling@opendynamic.deE<gt>
+
+=cut
index 65cd85d..6e758e4 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::PartClassification',
+    key_columns => { classification_id => 'id' },
+  },
+
   partsgroup => {
     class       => 'SL::DB::PartsGroup',
     key_columns => { partsgroup_id => 'id' },
diff --git a/SL/DB/MetaSetup/PartClassification.pm b/SL/DB/MetaSetup/PartClassification.pm
new file mode 100644 (file)
index 0000000..676fa21
--- /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::PartClassification;
+
+use strict;
+
+use parent qw(SL::DB::Object);
+
+__PACKAGE__->meta->table('part_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/PartClassification.pm b/SL/DB/PartClassification.pm
new file mode 100644 (file)
index 0000000..73f11da
--- /dev/null
@@ -0,0 +1,73 @@
+
+package SL::DB::PartClassification;
+
+use strict;
+
+use SL::DB::MetaSetup::PartClassification;
+use SL::DB::Manager::PartClassification;
+
+__PACKAGE__->meta->initialize;
+
+# check if the description and abbreviation is present
+#
+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;
+
+__END__
+
+=encoding utf-8
+
+=head1 NAME
+
+SL::DB::PartClassification
+
+=head1 SYNOPSIS
+
+Additional to the article types "part", "assembly", "service" and "assortement"
+the parts classification specifies other ortogonal attributes
+
+=head1 DESCRIPTION
+
+The primary attributes are the rule
+of the article as "used_for_sales" or "used_for_purchase".
+
+Additional other attributes may follow
+
+To see this attributes in a short way there are shortcuts of one (or two characters, if needed for compare )
+which may be translated in the specified language
+
+The type of the article is also as shortcut available, so this combined type and classification shortcut
+is used short as "Type"
+
+English type shortcuts are 'P','A','S'
+German  type shortcuts are 'W','E','D'
+The can set in the language-files
+
+To get the localized abbreviations you can use L<SL::Presenter::Part> .
+
+=head1 METHODS
+
+=head2 validate
+
+ $self->validate();
+
+check if the description and abbreviation is present
+
+
+=head1 AUTHOR
+
+Martin Helmling E<lt>martin.helmling@opendynamic.deE<gt>
+
+
+=cut
index a39e8ae..d352a56 100644 (file)
--- a/SL/IC.pm
+++ b/SL/IC.pm
@@ -92,6 +92,7 @@ sub retrieve_buchungsgruppen {
   $main::lxdebug->leave_sub();
 }
 
+
 sub assembly_item {
   $main::lxdebug->enter_sub();
 
@@ -129,6 +130,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
@@ -210,7 +212,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     = ();
@@ -287,7 +289,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;
   }
 
@@ -378,9 +380,14 @@ sub all_parts {
   }
 
   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/;
+    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/;
+    push @where_tokens, "p.part_type = 'assortment'" if /assortment/;
+  }
+  if ( $form->{classification_id} > 0 ) {
+    push @where_tokens, "p.classification_id = ?";
+    push @bind_vars, $form->{classification_id};
   }
 
   for ($form->{itemstatus}) {
@@ -438,7 +445,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};
@@ -536,7 +543,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,
@@ -583,8 +590,7 @@ SQL
       }
       $sth->finish;
     }
-  };
-
+  }
 
   $main::lxdebug->leave_sub();
 
@@ -815,7 +821,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|;
 
@@ -828,6 +835,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};
@@ -1122,12 +1131,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 {
index 57cfb5a..f25aa92 100644 (file)
--- a/SL/IR.pm
+++ b/SL/IR.pm
@@ -52,6 +52,9 @@ use SL::DB;
 use List::Util qw(min);
 
 use strict;
+use constant PCLASS_OK             =>   0;
+use constant PCLASS_NOTFORSALE     =>   1;
+use constant PCLASS_NOTFORPURCHASE =>   2;
 
 sub post_invoice {
   my ($self, $myconfig, $form, $provided_dbh, $payments_only) = @_;
@@ -1021,6 +1024,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)
@@ -1280,6 +1284,7 @@ sub retrieve_item {
          p.notes AS partnotes, p.notes AS longdescription, p.not_discountable,
          p.price_factor_id,
          p.ean,
+         p.classification_id,
 
          pfac.factor AS price_factor,
 
@@ -1295,6 +1300,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
@@ -1311,6 +1317,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 part_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);
@@ -1329,6 +1336,7 @@ sub retrieve_item {
   map { push @{ $_ }, prepare_query($form, $dbh, $_->[0]) } @translation_queries;
 
   $form->{item_list} = [];
+  my $has_wrong_pclass = PCLASS_OK;
   while (my $ref = $sth->fetchrow_hashref("NAME_lc")) {
 
     if ($mm_by_id{$ref->{id}}) {
@@ -1339,7 +1347,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 = PCLASS_NOTFORPURCHASE;
+       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.
@@ -1409,12 +1423,15 @@ sub retrieve_item {
   $sth->finish();
   $_->[1]->finish for @translation_queries;
 
+  $form->{is_wrong_pclass} = $has_wrong_pclass;
+  $form->{NOTFORSALE}      = PCLASS_NOTFORSALE;
+  $form->{NOTFORPURCHASE}  = PCLASS_NOTFORPURCHASE;
   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} = PCLASS_OK; # one correct type
     map { $item->{"ic_cvar_" . $_->{name} } = $_->{value} } @{ $custom_variables };
   }
 
index 2f0896e..bbb1895 100644 (file)
--- a/SL/IS.pm
+++ b/SL/IS.pm
@@ -59,6 +59,9 @@ use SL::DB;
 use Data::Dumper;
 
 use strict;
+use constant PCLASS_OK             =>   0;
+use constant PCLASS_NOTFORSALE     =>   1;
+use constant PCLASS_NOTFORPURCHASE =>   2;
 
 sub invoice_details {
   $main::lxdebug->enter_sub();
@@ -2003,6 +2006,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 +2304,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 +2324,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 +2341,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 part_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 +2359,7 @@ sub retrieve_item {
                                    LIMIT 1| ] );
   map { push @{ $_ }, prepare_query($form, $dbh, $_->[0]) } @translation_queries;
 
+  my $has_wrong_pclass = PCLASS_OK;
   while (my $ref = $sth->fetchrow_hashref('NAME_lc')) {
 
     if ($mm_by_id{$ref->{id}}) {
@@ -2364,6 +2371,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 = PCLASS_NOTFORSALE ;
+      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 +2460,17 @@ sub retrieve_item {
   $sth->finish;
   $_->[1]->finish for @translation_queries;
 
+  $form->{is_wrong_pclass} = $has_wrong_pclass;
+  $form->{NOTFORSALE}      = PCLASS_NOTFORSALE;
+  $form->{NOTFORPURCHASE}  = PCLASS_NOTFORPURCHASE;
   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} = PCLASS_OK; # one correct type
     map { $item->{"ic_cvar_" . $_->{name} } = $_->{value} } @{ $custom_variables };
   }
-
   $main::lxdebug->leave_sub();
 }
 
index d1eaa56..a51d0cb 100644 (file)
--- a/SL/OE.pm
+++ b/SL/OE.pm
@@ -1090,6 +1090,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..c3b366a 100644 (file)
@@ -3,9 +3,11 @@ package SL::Presenter::Part;
 use strict;
 
 use SL::DB::Part;
+use SL::DB::PartClassification;
+use SL::Locale::String qw(t8);
 
 use Exporter qw(import);
-our @EXPORT = qw(part_picker part);
+our @EXPORT = qw(part_picker part select_classification classification_abbreviation type_abbreviation separate_abbreviation);
 
 use Carp;
 
@@ -46,6 +48,55 @@ sub part_picker {
   $self->html_tag('span', $ret, class => 'part_picker');
 }
 
+#
+# shortcut for article type
+#
+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')
+
+#
+# shortcut for article type
+#
+sub classification_abbreviation {
+  my ($self, $id) = @_;
+  SL::DB::Manager::PartClassification->cache_all();
+  my $obj = SL::DB::PartClassification->load_cached($id);
+  $obj && $obj->abbreviation ? t8($obj->abbreviation) : '';
+}
+
+#
+# generate selection tag
+#
+sub select_classification {
+  my ($self, $name, %attributes) = @_;
+  $attributes{value_key} = 'id';
+  $attributes{title_key} = 'description';
+  my $collection = SL::DB::Manager::PartClassification->get_all_sorted();
+  $_->description($::locale->text($_->description)) for @{ $collection };
+  return $self->select_tag( $name, $collection, %attributes );
+}
+
 1;
 
 __END__
@@ -93,6 +144,32 @@ to the corresponding 'edit' action.
 
 =over 2
 
+=item C<classification_abbreviation $classification_id>
+
+Returns the shortcut of the classification
+
+=back
+
+=over 2
+
+=item C<select_classification $name,%params>
+
+Returns a HTML Select Tag with all available Classifications
+
+C<%params> can include:
+
+=over 4
+
+=item * default
+
+The Id of the selected item .
+
+=back
+
+=back
+
+=over 2
+
 =item C<part_picker $name, $value, %params>
 
 All-in-one picker widget for parts. The name will be both id and name
@@ -214,4 +291,6 @@ None atm :)
 
 Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>
 
+Martin Helmling E<lt>martin.helmling@opendynamic.deE<gt>
+
 =cut
index 6f693d2..df5bad9 100644 (file)
--- a/SL/WH.pm
+++ b/SL/WH.pm
@@ -369,6 +369,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});
@@ -427,6 +432,8 @@ sub get_warehouse_journal {
      "qty"                  => "ABS(SUM(i1.qty))",
      "partnumber"           => "p.partnumber",
      "partdescription"      => "p.description",
+     "classification_id"    => "p.classification_id",
+     "part_type"            => "p.part_type",
      "bindescription"       => "b.description",
      "chargenumber"         => "i1.chargenumber",
      "bestbefore"           => "i1.bestbefore",
@@ -458,6 +465,7 @@ sub get_warehouse_journal {
      "warehouse_from"       => "'$filter{na}'",
      };
 
+  $form->{l_classification_id}  = 'Y';
   $form->{l_invoice_id} = $form->{l_oe_id} if $form->{l_oe_id};
 
   # build the select clauses.
@@ -615,6 +623,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
@@ -666,6 +675,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});
@@ -738,6 +752,8 @@ sub get_warehouse_report {
      "warehouseid"          => "i.warehouse_id",
      "partnumber"           => "p.partnumber",
      "partdescription"      => "p.description",
+     "classification_id"    => "p.classification_id",
+     "part_type"            => "p.part_type",
      "bindescription"       => "b.description",
      "binid"                => "b.id",
      "chargenumber"         => "i.chargenumber",
@@ -748,6 +764,9 @@ sub get_warehouse_report {
      "partunit"             => "p.unit",
      "stock_value"          => "p.lastcost / COALESCE(pfac.factor, 1)",
   );
+  $form->{l_classification_id}  = 'Y';
+  $form->{l_part_type}          = '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 e6b1ff3..7cc64ac 100644 (file)
@@ -84,9 +84,10 @@ 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);
+  my %is_xyz     = map { +"is_$_" => ($form->{searchitems} eq $_) } qw(part service assembly assortment);
 
   $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});
 
@@ -217,7 +218,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:
@@ -284,6 +285,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'), },
   );
@@ -408,6 +410,7 @@ 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') {
 
@@ -456,7 +459,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
@@ -489,6 +492,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),
@@ -515,10 +519,12 @@ 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'),
+                       'raw_bottom_info_text'  => $form->parse_html_template('ic/generate_report_bottom' ,
+                                                  { PART_CLASSIFICATIONS => SL::DB::Manager::PartClassification->get_all_sorted }),
                        'output_format'         => 'HTML',
                        'title'                 => $form->{title},
                        'attachment_basename'   => $attachment_basenames{$form->{searchitems}} . strftime('_%Y%m%d', localtime time),
@@ -631,6 +637,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);
 
index b150a3c..0d9072f 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,14 @@ 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"});
+    $column_data{type_and_classific} = $::request->presenter->type_abbreviation($form->{"part_type_$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 +669,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
@@ -1759,9 +1762,11 @@ sub _update_part_information {
   foreach my $i (1..$form->{rowcount}) {
     next unless ($form->{"id_${i}"});
 
-    my $info                 = $form->{PART_INFORMATION}->{$form->{"id_${i}"}} || { };
-    $form->{"partunit_${i}"} = $info->{unit};
-    $form->{"weight_$i"}     = $info->{weight};
+    my $info                        = $form->{PART_INFORMATION}->{$form->{"id_${i}"}} || { };
+    $form->{"partunit_${i}"}        = $info->{unit};
+    $form->{"weight_$i"}            = $info->{weight};
+    $form->{"part_type_$i"}         = $info->{part_type};
+    $form->{"classification_id_$i"} = $info->{classification_id};
   }
 
   $main::lxdebug->leave_sub();
index fe37c26..42639f5 100644 (file)
@@ -678,10 +678,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}) {
@@ -697,6 +697,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'), },
@@ -708,6 +709,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'), },
@@ -726,6 +729,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);
@@ -753,17 +758,19 @@ sub generate_journal {
                     'purchase_invoice'        => { script => 'ir', title => $locale->text('Purchase Invoice') },
                   );
 
-   my $allrows = 0;
-   $allrows = 1 if $form->{report_generator_output_format} ne 'HTML' ;
+  my $allrows = 0;
+  $allrows = 1 if $form->{report_generator_output_format} ne 'HTML' ;
 
-   # manual paginating
-   my $pages = {};
-   $pages->{per_page}        = $::form->{per_page} || 15;
-   my $first_nr = ($page - 1) * $pages->{per_page};
-   my $last_nr  = $first_nr + $pages->{per_page};
-   my $idx       = 0;
+  # manual paginating
+  my $pages = {};
+  $pages->{per_page}        = $::form->{per_page} || 15;
+  my $first_nr = ($page - 1) * $pages->{per_page};
+  my $last_nr  = $first_nr + $pages->{per_page};
+  my $idx       = 0;
 
   foreach my $entry (@contents) {
+    $entry->{type_and_classific} = $::request->presenter->type_abbreviation($entry->{part_type}).
+                                   $::request->presenter->classification_abbreviation($entry->{classification_id});
     $entry->{qty}        = $form->format_amount_units('amount'     => $entry->{qty},
                                                       'part_unit'  => $entry->{partunit},
                                                       'conv_units' => 'convertible');
@@ -853,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') . " : " .
@@ -867,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::PartClassification->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});
@@ -896,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'), },
@@ -917,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);
 
@@ -951,6 +969,9 @@ sub generate_report {
   my $last_nr  = $first_nr + $pages->{per_page};
 
   foreach my $entry (@contents) {
+
+    $entry->{type_and_classific} = $::request->presenter->type_abbreviation($entry->{part_type}).
+                                   $::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});
diff --git a/js/locale/en.js b/js/locale/en.js
new file mode 100644 (file)
index 0000000..a0cdea5
--- /dev/null
@@ -0,0 +1,105 @@
+namespace("kivi").setupLocale({
+"A transaction description is required.":"",
+"Add function block":"",
+"Add linked record":"",
+"Add multiple items":"",
+"Add picture":"",
+"Add picture to text block":"",
+"Add section":"",
+"Add sub function block":"",
+"Add text block":"",
+"Additional articles actions":"",
+"Are you sure?":"",
+"Assign invoice":"",
+"Basic settings actions":"",
+"Cancel":"",
+"Chart picker":"",
+"Copy":"",
+"Copy requirement spec":"",
+"Copy template":"",
+"Create":"",
+"Create HTML":"",
+"Create PDF":"",
+"Create a new version":"",
+"Create and print all invoices":"",
+"Create invoice":"",
+"Create new quotation/order":"",
+"Create new qutoation/order":"",
+"Create new version":"",
+"Database Connection Test":"",
+"Delete":"",
+"Delete picture":"",
+"Delete quotation/order":"",
+"Delete requirement spec":"",
+"Delete template":"",
+"Delete text block":"",
+"Do you really want do continue?":"",
+"Do you really want to cancel?":"",
+"Do you really want to delete this draft?":"",
+"Do you really want to revert to this version?":"",
+"Do you really want to save?":"",
+"Do you want to set the account number \"#1\" to \"#2\" and the name \"#3\" to \"#4\"?":"",
+"Download picture":"",
+"Edit":"",
+"Edit article/section assignments":"",
+"Edit picture":"",
+"Edit project link":"",
+"Edit text block":"",
+"Enter longdescription":"",
+"Function block actions":"",
+"Hide all details":"",
+"Hide details":"",
+"History":"",
+"If you switch to a different tab without saving you will lose the data you've entered in the current tab.":"",
+"Map":"",
+"No":"",
+"No delivery orders have been selected.":"",
+"No invoices have been selected.":"",
+"Part picker":"",
+"Paste":"",
+"Paste template":"",
+"Please select a customer.":"",
+"Please select a vendor.":"",
+"Price Types":"",
+"Print options":"",
+"Project link actions":"",
+"Quotations/Orders actions":"",
+"Re-numbering all sections and function blocks in the order they are currently shown cannot be undone.":"",
+"Remove article":"",
+"Renumber sections and function blocks":"",
+"Requirement spec actions":"",
+"Requirement spec template actions":"",
+"Revert to version":"",
+"Save":"",
+"Save and keep open":"",
+"Section/Function block actions":"",
+"Select template to paste":"",
+"Show all details":"",
+"Show details":"",
+"Subject":"",
+"Text block actions":"",
+"Text block picture actions":"",
+"The IBAN is missing.":"",
+"The description is missing.":"",
+"The name is missing.":"",
+"The name must only consist of letters, numbers and underscores and start with a letter.":"",
+"The option field is empty.":"",
+"The recipient, subject or body is missing.":"",
+"The selected database is still configured for client \"#1\". If you delete the database that client will stop working until you re-configure it. Do you still want to delete the database?":"",
+"There are duplicate parts at positions":"",
+"There are still transfers not matching the qty of the delivery order. Stock operations can not be changed later. Do you really want to proceed?":"",
+"There is no connected chart.":"",
+"There is one or more sections for which no part has been assigned yet; therefore creating the new record is not possible yet.":"",
+"This sales order has an active configuration for periodic invoices. If you save then all subsequently created invoices will contain those changes as well, but not those that have already been created. Do you want to continue?":"",
+"Time/cost estimate actions":"",
+"Title":"",
+"Toggle marker":"",
+"Transaction description":"",
+"Update":"",
+"Update quotation/order":"",
+"Version actions":"",
+"Yes":"",
+"flat-rate position":"",
+"sort items":"",
+"time and effort based position":""
+});
index afffd71..5553b5f 100755 (executable)
@@ -85,6 +85,7 @@ $self->{texts} = {
   'ATTENTION! If you enabled this feature you can not simply turn it off again without taking care that best_before fields are emptied in the database.' => 'ACHTUNG! Wenn Sie diese Einstellung aktivieren, dann können Sie sie später nicht ohne Weiteres deaktivieren, ohne dafür zu sorgen, dass die Felder der Mindeshaltbarkeitsdaten in der Datenbank leer gemacht werden.',
   'ATTENTION! You can not simply change it from periodic to perpetual once you started posting.' => 'ACHTUNG! Es kann nicht ohne Weiteres im laufenden Betrieb von der Aufwandsmethode zur Bestandsmethode gewechselt werden.',
   'AUTOMATICALLY MATCH BINS'    => 'LAGERPLÄTZE AUTOMATISCH ZUWEISEN',
+  'Abbreviation Legend'         => 'Beschreibung der Typ-Abkürzungen (1 Zeichen Typ, 1-2 Zeichen Klassifizierung)',
   'Abort'                       => 'Abbrechen',
   'Abrechnungsnummer'           => 'Abrechnungsnummer',
   'Absolute BB Balance'         => 'Gesamtsaldo laut Bankbuchungen',
@@ -296,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!',
@@ -316,6 +318,7 @@ $self->{texts} = {
   'Assignment of articles to sections' => 'Zuweisung von Artikeln zu Abschnitten',
   'Assistant for general ledger corrections' => 'Assistent für die Korrektur von Hauptbucheinträgen',
   'Assortment'                  => 'Sortiment',
+  'Assortment (typeabbreviation)' => 'K',
   'Assortment items'            => 'Sortimentsartikel',
   'Assortments'                 => 'Sortimente',
   'Assume Tax Consultant Data in Tax Computation?' => 'Beraterdaten in UStVA übernehmen?',
@@ -662,6 +665,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',
@@ -1106,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 partsgroup'             => 'Warengruppe bearbeiten',
   'Edit payment term'           => 'Zahlungsbedingungen bearbeiten',
   'Edit picture'                => 'Bild bearbeiten',
@@ -1440,6 +1445,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.',
@@ -1451,7 +1457,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 \'part_type\' or called \'pclass\' must be present.' => 'Falls der Rtikeltyp auf \'mixed\' gesetzt ist muss entweder eine Spalte \'part_type\' oder \'pclass\' im Import 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',
@@ -1566,9 +1572,9 @@ $self->{texts} = {
   'It will not be further modified by any other source, and will be offered in records like this.' => 'Er wird nicht weiter verändert werden und genau so im Beleg vorgeschlagen werden.',
   '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 does not exists in the database' => 'Den Artikel gibt es nicht',
   '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',
@@ -1729,6 +1735,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',
@@ -1745,7 +1753,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',
@@ -1783,7 +1791,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',
@@ -1796,7 +1803,6 @@ $self->{texts} = {
   'New row, partnumber'         => 'Neue Zeile, Nummer',
   'New row, qty'                => 'Neue Zeile, Menge',
   'New sales order'             => 'Neuer Auftrag',
-  'New service'                 => 'Neue Dienstleistung',
   'New shipto'                  => 'Neue Lieferadresse',
   'New vendor'                  => 'Neuer Lieferant',
   'New window/tab'              => 'Neues Fenster/Tab',
@@ -1842,6 +1848,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',
@@ -1885,6 +1892,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',
@@ -2007,6 +2015,7 @@ $self->{texts} = {
   'POSTED'                      => 'Gebucht',
   'POSTED AS NEW'               => 'Als neu gebucht',
   'PRINTED'                     => 'Gedruckt',
+  'PType'                       => 'Typ',
   'Package name'                => 'Paketname',
   'Packing Lists'               => 'Lieferschein',
   'Page'                        => 'Seite',
@@ -2016,17 +2025,23 @@ $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 Type'                   => 'Artikel-Typ',
+  'Part Unit'                   => 'Einheit des Artikels',
   'Part picker'                 => 'Artikelauswahl',
+  'PartClassAbbreviation'       => 'Abkürzung der Artikel-Klassifizierung',
   'Part_br_Description'         => 'Beschreibung',
   'Partial invoices'            => 'Teilrechnungen',
   'Partnumber'                  => 'Artikelnummer',
   'Parts'                       => 'Waren',
+  'Parts Classification'        => 'Artikel-Klassifizierung',
+  'Parts Classifications'       => 'Artikel-Klassifizierung',
   'Parts Inventory'             => 'Warenliste',
   'Parts Master Data'           => 'Artikelstammdaten',
   'Parts with existing part numbers' => 'Artikel mit existierender Artikelnummer',
@@ -2178,6 +2193,7 @@ $self->{texts} = {
   'Print template base file name' => 'Druckvorlagen-Basisdateiname',
   'Print templates'             => 'Druckvorlagen',
   'Print templates to use'      => 'Zu verwendende Druckvorlagen',
+  'Printdate'                   => 'Druckdatum',
   'Printer'                     => 'Drucker',
   'Printer Command'             => 'Druckbefehl',
   'Printer Description'         => 'Druckerbeschreibung',
@@ -2191,6 +2207,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',
@@ -2214,6 +2232,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',
@@ -2413,6 +2433,8 @@ $self->{texts} = {
   'Saldo Debit'                 => 'Saldo Soll',
   'Saldo neu'                   => 'Saldo neu',
   'Saldo per'                   => 'Saldo per',
+  'Sales'                       => 'Verkauf',
+  'Sales (typeabbreviation)'    => 'V',
   'Sales Delivery Order'        => 'Verkaufslieferschein',
   'Sales Delivery Orders'       => 'Verkaufslieferscheine',
   'Sales Delivery Orders deleteable' => 'Verkaufslieferscheine löschbar',
@@ -2535,6 +2557,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',
@@ -2789,6 +2812,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.',
@@ -2805,6 +2829,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.',
@@ -2839,6 +2864,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.' => 'Eine Basis Artikel-Klassifizierung darf nicht gelöscht werden.',
   '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.',
@@ -2909,7 +2935,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.',
@@ -2974,6 +3000,10 @@ $self->{texts} = {
   'The partnumber already exists!' => 'Die Artikelnummer wird bereits verwendet.',
   'The partnumber already exists.' => 'Die Artikelnummer wird bereits verwndet.',
   'The partnumber is missing.'  => 'Die Artikelnummer fehlt.',
+  '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.',
@@ -3281,11 +3311,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.',
+  'TypAbbreviation'             => 'Abkürzung des Artikel-Typs',
   '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',
@@ -3360,6 +3392,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',
@@ -3424,7 +3458,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',
@@ -3446,7 +3480,6 @@ $self->{texts} = {
   'Weight unit'                 => 'Gewichtseinheit',
   'What <b>term</b> you are looking for?' => 'Nach welchem <b>Begriff</b> wollen Sie suchen?',
   'What this template contains' => 'Was diese Vorlage enthält',
-  'What type of item is this?'  => 'Was ist dieser Artikel?',
   'When converting a requirement spec into a quotation or an oder each section gets converted into a line position in the new record. This is the article used by default for this conversion.' => 'Wenn ein Pflichtenheft in ein Angebot oder Auftrag umgewandelt wird, wird für jeden Abschnitt eine Position im neuen Beleg angelegt. Dies ist der Artikel, der standardmäßig bei dieser Umwandlung genutzt wird.',
   'Which is located at doc/kivitendo-Dokumentation.pdf. Click here: ' => 'Diese befindet sich unter doc/kivitendo-Dokumentation.pdf. Klicken Sie hier:',
   'With Attachments'            => 'Journal mit Anhängen',
@@ -3533,6 +3566,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',
@@ -3719,6 +3753,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 bc30d21..b2e9a00 100644 (file)
@@ -296,12 +296,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)' => 'A',
   'Assembly Description'        => '',
   'Assembly Number'             => '',
   'Assembly Number missing!'    => '',
@@ -316,6 +317,7 @@ $self->{texts} = {
   'Assignment of articles to sections' => '',
   'Assistant for general ledger corrections' => '',
   'Assortment'                  => '',
+  'Assortment (typeabbreviation)' => 'As',
   'Assortment items'            => '',
   'Assortments'                 => '',
   'Assume Tax Consultant Data in Tax Computation?' => '',
@@ -662,6 +664,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'     => '',
@@ -1067,7 +1070,6 @@ $self->{texts} = {
   'Edit Follow-Up'              => '',
   'Edit Follow-Up for #1'       => '',
   'Edit General Ledger Transaction' => '',
-  'Edit Group'                  => '',
   'Edit Language'               => '',
   'Edit Lead'                   => '',
   'Edit Letter'                 => '',
@@ -1107,6 +1109,7 @@ $self->{texts} = {
   'Edit general settings'       => '',
   'Edit greetings'              => '',
   'Edit note'                   => '',
+  'Edit parts classification'   => '',
   'Edit partsgroup'             => '',
   'Edit payment term'           => '',
   'Edit picture'                => '',
@@ -1392,12 +1395,8 @@ $self->{texts} = {
   'Group Invoices'              => '',
   'Group Items'                 => '',
   'Group assignment'            => '',
-  'Group deleted!'              => '',
   'Group list'                  => '',
   'Group membership'            => '',
-  'Group missing!'              => '',
-  'Group saved!'                => '',
-  'Groups'                      => '',
   'Groups that are valid for this client for access rights' => '',
   'Groups this user is a member in' => '',
   'Groups valid for this client' => '',
@@ -1445,18 +1444,21 @@ $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.' => '',
   'If disabled sales orders cannot be converted into sales invoices directly.' => '',
   'If disabled sales quotations cannot be converted into sales invoices directly.' => '',
   'If enabled a column will be shown in sales and purchase orders that lists both the amount and the value not shipped yet for each item.' => '',
-  'If enabled a warning will be shown in sales and purchase orders if there are two or more positions of the same part (new controller only).' => 'Falls eingeschaltet, wird eine Warnung angezeigt, wenn der Auftrag mehrere gleiche Artikel enthält (nur neuer Controller).',
+  'If enabled a warning will be shown in sales and purchase orders if there are two or more positions of the same part (new controller only).' => '',
   'If enabled only those projects that are assigned to the currently selected customer are offered for selection in sales records.' => '',
   'If enabled purchase and sales records cannot be saved if no transaction description has been entered.' => '',
+  'If item not found, allow creation of new item' => '',
   '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 searching a part from a document and no part is found then offer to create a new part.' => '',
+  'If the article type is set to \'mixed\' then a column called \'part_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.' => '',
@@ -1571,9 +1573,9 @@ $self->{texts} = {
   'It will not be further modified by any other source, and will be offered in records like this.' => '',
   'It will simply set the taxkey to 0 (meaning "no taxes") which is the correct value for such inventory transactions.' => '',
   'Italy'                       => '',
+  'Item does not exists in the database' => '',
   'Item mode'                   => '',
   'Item multi selection with qty' => '',
-  'Item not on file!'           => '',
   'Item values'                 => '',
   'Item variables'              => '',
   'Jahresverkehrszahlen neu'    => '',
@@ -1733,6 +1735,8 @@ $self->{texts} = {
   'Media \'#1\' is not supported yet/anymore.' => '',
   'Medium Number'               => '',
   'Memo'                        => '',
+  'Merchandise'                 => 'Merchandise',
+  'Merchandise (typeabbreviation)' => 'M',
   'Message'                     => '',
   'Method'                      => '',
   'Microfiche'                  => '',
@@ -1749,7 +1753,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'                     => '',
@@ -1787,7 +1791,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'                 => '',
@@ -1799,7 +1802,6 @@ $self->{texts} = {
   'New row, description'        => '',
   'New row, partnumber'         => '',
   'New sales order'             => '',
-  'New service'                 => '',
   'New shipto'                  => '',
   'New vendor'                  => '',
   'New window/tab'              => '',
@@ -1845,6 +1847,7 @@ $self->{texts} = {
   'No or an unknown authenticantion module specified in "config/kivitendo.conf".' => '',
   'No part was found matching the search parameters.' => '',
   'No part was selected.'       => '',
+  'No parts classification has been created yet.' => '',
   'No payment term has been created yet.' => '',
   'No picture has been uploaded' => '',
   'No picture uploaded yet'     => '',
@@ -1888,6 +1891,7 @@ $self->{texts} = {
   'None'                        => '',
   'None (PriceSource Discount)' => '',
   'None (PriceSource)'          => '',
+  'None (typeabbreviation)'     => '-',
   'Normal'                      => '',
   'Normal users cannot log in.' => '',
   'Normalize Customer / Vendor names' => '',
@@ -2010,6 +2014,7 @@ $self->{texts} = {
   'POSTED'                      => '',
   'POSTED AS NEW'               => '',
   'PRINTED'                     => '',
+  'PType'                       => '',
   'Package name'                => '',
   'Packing Lists'               => '',
   'Page'                        => '',
@@ -2019,6 +2024,8 @@ $self->{texts} = {
   'Part'                        => '',
   'Part "#1" has chargenumber or best before date set. So it cannot be transfered automatically.' => '',
   'Part (database ID)'          => '',
+  'Part (typeabbreviation)'     => 'P',
+  'Part Classification'         => '',
   'Part Description'            => '',
   'Part Description missing!'   => '',
   'Part Notes'                  => '',
@@ -2030,6 +2037,8 @@ $self->{texts} = {
   'Partial invoices'            => '',
   'Partnumber'                  => '',
   'Parts'                       => '',
+  'Parts Classification'        => '',
+  'Parts Classifications'       => '',
   'Parts Inventory'             => '',
   'Parts Master Data'           => '',
   'Parts with existing part numbers' => '',
@@ -2181,6 +2190,7 @@ $self->{texts} = {
   'Print template base file name' => '',
   'Print templates'             => '',
   'Print templates to use'      => '',
+  'Printdate'                   => '',
   'Printer'                     => '',
   'Printer Command'             => '',
   'Printer Description'         => '',
@@ -2194,6 +2204,8 @@ $self->{texts} = {
   'Private Phone'               => '',
   'Problem'                     => '',
   'Produce Assembly'            => '',
+  'Production'                  => 'Production',
+  'Production (typeabbreviation)' => 'W',
   'Productivity'                => '',
   'Profit determination'        => '',
   'Proforma Invoice'            => '',
@@ -2217,6 +2229,8 @@ $self->{texts} = {
   'Proposal'                    => '',
   'Proposals'                   => '',
   'Prozentual/Absolut'          => '',
+  'Purchase'                    => 'Purchase',
+  'Purchase (typeabbreviation)' => 'P',
   'Purchase Delivery Order'     => '',
   'Purchase Delivery Orders'    => '',
   'Purchase Delivery Orders deleteable' => '',
@@ -2336,6 +2350,7 @@ $self->{texts} = {
   'Report and misc. Preferences' => '',
   'Report date'                 => '',
   'Report for'                  => '',
+  'Report separately'           => '',
   'Reports'                     => '',
   'Representative'              => '',
   'Representative for Customer' => '',
@@ -2416,6 +2431,8 @@ $self->{texts} = {
   'Saldo Debit'                 => '',
   'Saldo neu'                   => '',
   'Saldo per'                   => '',
+  'Sales'                       => 'Sales',
+  'Sales (typeabbreviation)'    => 'S',
   'Sales Delivery Order'        => '',
   'Sales Delivery Orders'       => '',
   'Sales Delivery Orders deleteable' => '',
@@ -2538,6 +2555,7 @@ $self->{texts} = {
   'Serial No.'                  => '',
   'Serial Number'               => '',
   'Service'                     => '',
+  'Service (typeabbreviation)'  => 'Sv',
   'Service Items'               => '',
   'Service Number missing!'     => '',
   'Service, assembly or part'   => '',
@@ -2791,6 +2809,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.' => '',
@@ -2807,6 +2826,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.' => '',
@@ -2841,6 +2861,7 @@ $self->{texts} = {
   'The base unit does not exist.' => '',
   '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.' => '',
   'The basic client tables have not been created for this client\'s database yet.' => '',
+  'The basic parts classification cannot be deleted.' => '',
   'The body is missing.'        => '',
   'The booking group has been created.' => '',
   'The booking group has been deleted.' => '',
@@ -2976,6 +2997,10 @@ $self->{texts} = {
   'The partnumber already exists!' => '',
   'The partnumber already exists.' => '',
   'The partnumber is missing.'  => '',
+  '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.' => '',
@@ -3288,6 +3313,7 @@ $self->{texts} = {
   'Type of Business'            => '',
   'Type of Customer'            => '',
   'Type of Vendor'              => '',
+  'TypeAbbreviation'            => '',
   'Types of Business'           => '',
   'USTVA'                       => '',
   'USTVA 2004'                  => '',
@@ -3362,6 +3388,8 @@ $self->{texts} = {
   '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'            => '',
@@ -3535,6 +3563,7 @@ $self->{texts} = {
   'ap_aging_list'               => '',
   'ar_aging_list'               => '',
   'ar_chart isn\'t a valid chart' => '',
+  'article_list'                => '',
   'as at'                       => '',
   'assembled'                   => '',
   'assembly'                    => '',
@@ -3721,6 +3750,8 @@ $self->{texts} = {
   'saved'                       => '',
   'saved!'                      => '',
   'saving data'                 => '',
+  'searched part not for purchase' => '',
+  'searched part not for sale'  => '',
   'semiannually'                => '',
   'sent'                        => '',
   'sent to printer'             => '',
index b8205b7..b0db0eb 100644 (file)
   order: 900
   params:
     action: PartsGroup/list
+- parent: system
+  id: system_part_classification
+  name: Parts Classification
+  icon: partsclassific
+  order: 1100
+  params:
+    action: PartClassification/list
 - parent: system
   id: system_pricegroups
   name: Pricegroups
-  order: 1000
+  order: 1120
   params:
     action: Pricegroup/list
 - parent: system
   id: system_edit_units
   name: Edit units
-  order: 1100
+  order: 1140
   module: am.pl
   params:
     action: edit_units
diff --git a/sql/Pg-upgrade2/part_classifications.sql b/sql/Pg-upgrade2/part_classifications.sql
new file mode 100644 (file)
index 0000000..5dc7af8
--- /dev/null
@@ -0,0 +1,19 @@
+-- @tag: part_classifications
+-- @description: "zusätzliche Tabelle mit Flags zur Klassifizierung von Artikeln"
+-- @depends: release_3_4_1
+CREATE TABLE part_classifications (
+    id SERIAL PRIMARY KEY,
+    description text,
+    abbreviation text,
+    used_for_purchase BOOLEAN DEFAULT 't' NOT NULL,
+    used_for_sale     BOOLEAN DEFAULT 't' NOT NULL
+);
+
+INSERT INTO part_classifications values(0,'-------'    ,'None (typeabbreviation)','t','t');
+INSERT INTO part_classifications values(1,'Purchase'   ,'Purchase (typeabbreviation)'   ,'t','f');
+INSERT INTO part_classifications values(2,'Sales'      ,'Sales (typeabbreviation)'      ,'f','t');
+INSERT INTO part_classifications values(3,'Merchandise','Merchandise (typeabbreviation)','t','t');
+INSERT INTO part_classifications values(4,'Production' ,'Production (typeabbreviation)' ,'f','t');
+SELECT setval('part_classifications_id_seq',4);
+ALTER TABLE parts ADD COLUMN classification_id integer DEFAULT 0;
+ALTER TABLE parts ADD CONSTRAINT part_classification_id_fkey FOREIGN KEY (classification_id) REFERENCES part_classifications(id);
index 8a27d79..d970088 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',
+                       part_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',
+                       part_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..3aaa813 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.part_classification', default = SELF.profile.get('part_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 65e061a..6682bfd 100644 (file)
@@ -1,30 +1,18 @@
 [%- USE T8 %]
 [%- USE HTML %]
-
-    <h4 class="error">[% 'Item not on file!' | $T8 %]
-
-    <p>[% 'What type of item is this?' | $T8 %]</h4>
-
-    <form method="post" action="controller.pl">
-
-      <p>
-
-      <input class="radio" type="radio" name="part_type" value="part" checked>&nbsp;[% 'Part'       | $T8 %]<br>
-      <input class="radio" type="radio" name="part_type" value="assembly">    &nbsp;[% 'Assembly'   | $T8 %]<br>
-      <input class="radio" type="radio" name="part_type" value="service">     &nbsp;[% 'Service'    | $T8 %]<br>
-      <input class="radio" type="radio" name="part_type" value="assortment">  &nbsp;[% 'Assortment' | $T8 %]
-      <p>
-
-      [%- FOREACH var = HIDDENS %]
-      <input type="hidden" name="[% HTML.escape(var.name) %]" value="[% HTML.escape(var.value) %]">
-      [%- END %]
-
-      <input type="hidden" name="action" value="Part/dispatch">
-      <input class="submit" type="submit" name="action_add" value="[% 'Continue' | $T8 %]">
+[%- IF is_wrong_pclass == NOTFORSALE %]
+<h4 class="error">[% 'searched part not for sale' | $T8 %]</h4>
+[%- ELSE %]
+[%- IF is_wrong_pclass == NOTFORPURCHASE %]
+<h4 class="error">[% 'searched part not for purchase' | $T8 %]</h4>
+[%- ELSE %]
+<h4 class="error">[% 'Item does not exists in the database' | $T8 %]
+</h4>
+[%- 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 499cdcb..5314fd9 100644 (file)
@@ -1,17 +1,61 @@
 [%- USE T8 %]
-[% USE HTML %]<form method="post" action="ic.pl">
+[% USE HTML %]
+<h4>[%- 'Abbreviation Legend' | $T8  %]</h4>
+<table valign="top">
+ <tr valign="top"><td>
+ <table valign="top">
+  <thead>
+    <tr class="listheading">
+     <th>[%- 'TypAbbreviation' | $T8  %]</th>
+     <th>[%- 'Description'     | $T8  %]</th>
+    </tr>
+  </thead>
+  <tbody>
+    <tr class="listrow0">
+     <td>[%- 'Part (typeabbreviation)'       | $T8 %]</td>
+     <td>[%- 'Part'                          | $T8 %]</td>
+    </tr>
+    <tr class="listrow1">
+     <td>[%- 'Assembly (typeabbreviation)'   | $T8 %]</td>
+     <td>[%- 'Assembly'                      | $T8 %]</td>
+    </tr>
+    <tr class="listrow0">
+     <td>[%- 'Service (typeabbreviation)'    | $T8 %]</td>
+     <td>[%- 'Service'                       | $T8 %]</td>
+    </tr>
+    <tr class="listrow1">
+     <td>[%- 'Assortment (typeabbreviation)' | $T8 %]</td>
+     <td>[%- 'Assortment'                    | $T8 %]</td>
+    </tr>
+ </tbody>
+ </table></td>
+ <td><table valign="top">
+  <thead>
+    <tr  valign="top" class="listheading">
+     <th>[%- 'PartClassAbbreviation' | $T8  %]</th>
+     <th>[%- 'Description'           | $T8  %]</th>
+    </tr>
+  </thead>
+  <tbody>
+  [%- FOREACH part_classification = PART_CLASSIFICATIONS %]
+    <tr class="listrow[% loop.count % 2 %]">
+     <td>[%- part_classification.abbreviation | $T8 %]</td>
+     <td>[%- part_classification.description  | $T8 %]</td>
+    </tr>
+  [%- END %]
+  </tbody>
+ </table></td></tr>
+</table>
+
+<form method="post" action="ic.pl">
 
  <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 e0df947..433a25e 100644 (file)
@@ -2,6 +2,7 @@
 [%- USE HTML %]
 [%- USE LxERP %]
 [%- USE L %]
+[%- USE P %]
 <h1>[% title %]</h1>
 
  <form method="post" action="ic.pl">
        <th align="right" nowrap>[% 'EAN' | $T8 %]</th>
        <td><input name="ean" size="20"></td>
       </tr>
-
+      <tr>
+       <th align="right" nowrap>[% 'Part 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>[% 'Partsgroup' | $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 e594fc4..92a0d0d 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 1b04cd9..6b79e51 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/part_classification/form.html b/templates/webpages/part_classification/form.html
new file mode 100755 (executable)
index 0000000..0c52727
--- /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("part_classification.description",  LxERP.t8(SELF.part_classification.description)) %]</td>
+   </tr>
+   <tr>
+    <td>[% LxERP.t8('TypeAbbreviation') %]</td>
+    <td>[% L.input_tag("part_classification.abbreviation",  LxERP.t8(SELF.part_classification.abbreviation),size=>"2",maxlength=>"2" ) %]</td>
+   </tr>
+   <tr>
+    <td>[% LxERP.t8('Used for Purchase') %]</td>
+    <td>[% L.checkbox_tag("part_classification.used_for_purchase", checked=(SELF.part_classification.used_for_purchase ? 1:'')) %]</td>
+   </tr>
+   <tr>
+    <td>[% LxERP.t8('Used for Sale') %]</td>
+    <td>[% L.checkbox_tag("part_classification.used_for_sale", checked=(SELF.part_classification.used_for_sale ? 1:'')) %]</td>
+   </tr>
+  </table>
+
+  <p>
+   [% L.hidden_tag("id", SELF.part_classification.id) %]
+   [% L.hidden_tag("action", "PartClassification/dispatch") %]
+   [% L.submit_tag("action_" _ (SELF.part_classification.id ? 'update' : 'create'), LxERP.t8('Save')) %]
+   [%- IF SELF.part_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/part_classification/list.html b/templates/webpages/part_classification/list.html
new file mode 100644 (file)
index 0000000..52ded51
--- /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 !PART_CLASSIFICATIONS.size %]
+   <p>
+    [%-  LxERP.t8('No parts classification has been created yet.') %]
+   </p>
+
+  [%- ELSE %]
+   <table id="part_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 part_classification = PART_CLASSIFICATIONS %]
+    <tr class="listrow[% loop.count % 2 %]" id="part_classification_id_[% part_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 => part_classification.id) %]">
+       [%- HTML.escape(LxERP.t8(part_classification.description)) %]
+      </a>
+     </td>
+     <td>[%- HTML.escape(LxERP.t8(part_classification.abbreviation)) %]</td>
+     <td>[% IF part_classification.used_for_purchase %][% LxERP.t8('Yes') %][% ELSE %][%  LxERP.t8('No') %][% END %]</td>
+     <td>[% IF part_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('#part_classification_list tbody', url => 'controller.pl?action=PartClassification/reorder', with => 'part_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>