From: Martin Helmling martin.helmling@octosoft.eu Date: Thu, 22 Dec 2016 07:37:45 +0000 (+0100) Subject: Artikel-Klassifizierung X-Git-Tag: release-3.5.4~1749 X-Git-Url: http://wagnertech.de/git?a=commitdiff_plain;h=65d2537d658b99b005a18c6663bc1293b41a1d83;p=kivitendo-erp.git Artikel-Klassifizierung 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. --- diff --git a/SL/Controller/CsvImport/Part.pm b/SL/Controller/CsvImport/Part.pm index ef7e2389a..069dad2bd 100644 --- a/SL/Controller/CsvImport/Part.pm +++ b/SL/Controller/CsvImport/Part.pm @@ -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') }, ); diff --git a/SL/Controller/Inventory.pm b/SL/Controller/Inventory.pm index c7df4e945..6a7dadc4f 100644 --- a/SL/Controller/Inventory.pm +++ b/SL/Controller/Inventory.pm @@ -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 index 000000000..5bc4e848f --- /dev/null +++ b/SL/Controller/PartClassification.pm @@ -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 + +=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 Emartin.helmling@opendynamic.deE + +=cut diff --git a/SL/DB/Helper/ALL.pm b/SL/DB/Helper/ALL.pm index bf6bd40df..a896cc372 100644 --- a/SL/DB/Helper/ALL.pm +++ b/SL/DB/Helper/ALL.pm @@ -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; diff --git a/SL/DB/Helper/Mappings.pm b/SL/DB/Helper/Mappings.pm index 7d1907dfa..3a6f96575 100644 --- a/SL/DB/Helper/Mappings.pm +++ b/SL/DB/Helper/Mappings.pm @@ -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 index 000000000..0924011e7 --- /dev/null +++ b/SL/DB/Manager/PartClassification.pm @@ -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 Emartin.helmling@opendynamic.deE + +=cut diff --git a/SL/DB/MetaSetup/Part.pm b/SL/DB/MetaSetup/Part.pm index 65cd85da2..6e758e499 100644 --- a/SL/DB/MetaSetup/Part.pm +++ b/SL/DB/MetaSetup/Part.pm @@ -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 index 000000000..676fa2148 --- /dev/null +++ b/SL/DB/MetaSetup/PartClassification.pm @@ -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 index 000000000..73f11da3c --- /dev/null +++ b/SL/DB/PartClassification.pm @@ -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 . + +=head1 METHODS + +=head2 validate + + $self->validate(); + +check if the description and abbreviation is present + + +=head1 AUTHOR + +Martin Helmling Emartin.helmling@opendynamic.deE + + +=cut diff --git a/SL/IC.pm b/SL/IC.pm index a39e8ae8d..d352a560d 100644 --- 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 { diff --git a/SL/IR.pm b/SL/IR.pm index 57cfb5a5f..f25aa921e 100644 --- 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 }; } diff --git a/SL/IS.pm b/SL/IS.pm index 2f0896ecb..bbb18951c 100644 --- 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(); } diff --git a/SL/OE.pm b/SL/OE.pm index d1eaa568d..a51d0cb2e 100644 --- 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, diff --git a/SL/Presenter/Part.pm b/SL/Presenter/Part.pm index 1f96cbe7b..c3b366a99 100644 --- a/SL/Presenter/Part.pm +++ b/SL/Presenter/Part.pm @@ -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 + +Returns the shortcut of the classification + +=back + +=over 2 + +=item C + +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 All-in-one picker widget for parts. The name will be both id and name @@ -214,4 +291,6 @@ None atm :) Sven Schöling Es.schoeling@linet-services.deE +Martin Helmling Emartin.helmling@opendynamic.deE + =cut diff --git a/SL/WH.pm b/SL/WH.pm index 6f693d2fa..df5bad909 100644 --- 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) ); diff --git a/bin/mozilla/ic.pl b/bin/mozilla/ic.pl index e6b1ff33f..7cc64acf6 100644 --- a/bin/mozilla/ic.pl +++ b/bin/mozilla/ic.pl @@ -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); diff --git a/bin/mozilla/io.pl b/bin/mozilla/io.pl index b150a3c8d..0d9072f67 100644 --- a/bin/mozilla/io.pl +++ b/bin/mozilla/io.pl @@ -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|| . + $column_data{runningnumber} = q|| . q|| . $locale->text('Remove') . q| |; $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(); diff --git a/bin/mozilla/wh.pl b/bin/mozilla/wh.pl index fe37c26da..42639f579 100644 --- a/bin/mozilla/wh.pl +++ b/bin/mozilla/wh.pl @@ -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 index 000000000..a0cdea545 --- /dev/null +++ b/js/locale/en.js @@ -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":"" +}); diff --git a/locale/de/all b/locale/de/all index afffd7175..5553b5fd6 100755 --- a/locale/de/all +++ b/locale/de/all @@ -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önnen die gespeicherten Mengen entweder in ein Lager überführt werden, oder fü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 über Mahngebühren und Zinsen für ein Mahnlevel aktiviert ist, so werden die folgenden Konten fü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ö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ä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ü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ü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 term you are looking for?' => 'Nach welchem Begriff 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', diff --git a/locale/en/all b/locale/en/all index bc30d2116..b2e9a001a 100644 --- a/locale/en/all +++ b/locale/en/all @@ -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' => '', diff --git a/menus/user/00-erp.yaml b/menus/user/00-erp.yaml index b8205b70c..b0db0eb21 100644 --- a/menus/user/00-erp.yaml +++ b/menus/user/00-erp.yaml @@ -1102,16 +1102,23 @@ 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 index 000000000..5dc7af8d6 --- /dev/null +++ b/sql/Pg-upgrade2/part_classifications.sql @@ -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); diff --git a/t/controllers/csvimport/parts.t b/t/controllers/csvimport/parts.t index 8a27d79c6..d9700884b 100644 --- a/t/controllers/csvimport/parts.t +++ b/t/controllers/csvimport/parts.t @@ -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 = \<[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 = \<[0]; @@ -269,16 +274,41 @@ is $l->longdescription, 'notes IT','IT notes set'; ##### add customvar $file = \<[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 = \<[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 diff --git a/templates/webpages/csv_import/_form_parts.html b/templates/webpages/csv_import/_form_parts.html index a54dd9377..3aaa81333 100644 --- a/templates/webpages/csv_import/_form_parts.html +++ b/templates/webpages/csv_import/_form_parts.html @@ -1,5 +1,6 @@ [% USE LxERP %] [% USE L %] +[% USE P %] [%- LxERP.t8('Parts with existing part numbers') %]: @@ -35,10 +36,16 @@ [%- LxERP.t8('Type') %]: - [% 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') %] + + [%- LxERP.t8('Parts Classification') %]: + + [% P.select_classification('settings.part_classification', default = SELF.profile.get('part_classification'), style = 'width: 300px') %] + + [%- LxERP.t8('Default booking group') %]: diff --git a/templates/webpages/csv_import/form.html b/templates/webpages/csv_import/form.html index 3796ced6e..1ab545384 100644 --- a/templates/webpages/csv_import/form.html +++ b/templates/webpages/csv_import/form.html @@ -129,9 +129,10 @@

[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") %]

[%- ELSIF SELF.type == 'inventories' %] diff --git a/templates/webpages/generic/new_item.html b/templates/webpages/generic/new_item.html index 65e061a31..6682bfddf 100644 --- a/templates/webpages/generic/new_item.html +++ b/templates/webpages/generic/new_item.html @@ -1,30 +1,18 @@ [%- USE T8 %] [%- USE HTML %] - -

[% 'Item not on file!' | $T8 %] - -

[% 'What type of item is this?' | $T8 %]

- -
- -

- -  [% 'Part' | $T8 %]
-  [% 'Assembly' | $T8 %]
-  [% 'Service' | $T8 %]
-  [% 'Assortment' | $T8 %] -

- - [%- FOREACH var = HIDDENS %] - - [%- END %] - - - +[%- IF is_wrong_pclass == NOTFORSALE %] +

[% 'searched part not for sale' | $T8 %]

+[%- ELSE %] +[%- IF is_wrong_pclass == NOTFORPURCHASE %] +

[% 'searched part not for purchase' | $T8 %]

+[%- ELSE %] +

[% 'Item does not exists in the database' | $T8 %] +

+[%- END %] +[%- END %] -
+

+ - - diff --git a/templates/webpages/ic/generate_report_bottom.html b/templates/webpages/ic/generate_report_bottom.html index 499cdcb8f..5314fd946 100644 --- a/templates/webpages/ic/generate_report_bottom.html +++ b/templates/webpages/ic/generate_report_bottom.html @@ -1,17 +1,61 @@ [%- USE T8 %] -[% USE HTML %]
+[% USE HTML %] +

[%- 'Abbreviation Legend' | $T8 %]

+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
[%- 'TypAbbreviation' | $T8 %][%- 'Description' | $T8 %]
[%- 'Part (typeabbreviation)' | $T8 %][%- 'Part' | $T8 %]
[%- 'Assembly (typeabbreviation)' | $T8 %][%- 'Assembly' | $T8 %]
[%- 'Service (typeabbreviation)' | $T8 %][%- 'Service' | $T8 %]
[%- 'Assortment (typeabbreviation)' | $T8 %][%- 'Assortment' | $T8 %]
+ + + + + + + + [%- FOREACH part_classification = PART_CLASSIFICATIONS %] + + + + + [%- END %] + +
[%- 'PartClassAbbreviation' | $T8 %][%- 'Description' | $T8 %]
[%- part_classification.abbreviation | $T8 %][%- part_classification.description | $T8 %]
+ + - + - [% SWITCH searchitems %] - [% CASE 'part' %][% 'New part' | $T8 %] - [% CASE 'service' %][% 'New service' | $T8 %] - [% CASE 'assembly' %][% 'New assembly' | $T8 %] - [% END %] -
- + + +
diff --git a/templates/webpages/ic/search.html b/templates/webpages/ic/search.html index e0df9472d..433a25e82 100644 --- a/templates/webpages/ic/search.html +++ b/templates/webpages/ic/search.html @@ -2,6 +2,7 @@ [%- USE HTML %] [%- USE LxERP %] [%- USE L %] +[%- USE P %]

[% title %]

@@ -27,12 +28,14 @@ [% 'EAN' | $T8 %] - + + [% 'Part Classification' | $T8 %]: + [% P.select_classification('classification_id') %] + [% 'Part Description' | $T8 %] - [% 'Partsgroup' | $T8 %] diff --git a/templates/webpages/io/select_item.html b/templates/webpages/io/select_item.html index 668b2e94b..1388d3165 100644 --- a/templates/webpages/io/select_item.html +++ b/templates/webpages/io/select_item.html @@ -12,6 +12,7 @@   [%- END %] [% LxERP.t8('Number') %] + [% LxERP.t8('Part Classification') %] [% LxERP.t8('Part Description') %] [%- IF INSTANCE_CONF.get_show_longdescription_select_item %] [% SET COLS = COLS + 1 %] @@ -35,6 +36,7 @@ [%- END %] [% HTML.escape(item.partnumber) %] + [% HTML.escape(item.type_and_classific) %] [% HTML.escape(item.description) %] [%- IF INSTANCE_CONF.get_show_longdescription_select_item %] [% P.restricted_html(item.longdescription) %] diff --git a/templates/webpages/part/_assembly.html b/templates/webpages/part/_assembly.html index e594fc43b..92a0d0db3 100644 --- a/templates/webpages/part/_assembly.html +++ b/templates/webpages/part/_assembly.html @@ -20,9 +20,10 @@ [% IF SELF.orphaned %] [%- LxERP.t8('reorder item') %] [% END %] - [%- 'Partnumber' | $T8 %] + [%- 'Partnumber' | $T8 %] + [% 'Type' | $T8 %] [%- 'Description' | $T8 %] - [%- 'Qty' | $T8 %] + [%- 'Qty' | $T8 %] [%- 'Unit' | $T8 %] [%- 'BOM' | $T8 %] [%- 'Line Total' | $T8 %] diff --git a/templates/webpages/part/_assembly_row.html b/templates/webpages/part/_assembly_row.html index 82e5c4618..925481240 100644 --- a/templates/webpages/part/_assembly_row.html +++ b/templates/webpages/part/_assembly_row.html @@ -24,6 +24,9 @@ [% P.part(ITEM.part) %] + + [% P.type_abbreviation(ITEM.part.part_type) %][% P.classification_abbreviation(ITEM.part.classification_id) %] + [% HTML.escape(ITEM.part.description) %] diff --git a/templates/webpages/part/_basic_data.html b/templates/webpages/part/_basic_data.html index 1b04cd94f..6b79e51aa 100644 --- a/templates/webpages/part/_basic_data.html +++ b/templates/webpages/part/_basic_data.html @@ -23,6 +23,10 @@ [% UNLESS SELF.part.id %][% readonly = 0 %][% END %] [% L.input_tag("part.partnumber", SELF.part.partnumber, size=40, readonly=readonly class="initial_focus") %] + + [% 'Part Classification' | $T8 %] + [% P.select_classification('part.classification_id', default => SELF.part.classification_id) %] + [% 'Part Description' | $T8 %] diff --git a/templates/webpages/part_classification/form.html b/templates/webpages/part_classification/form.html new file mode 100755 index 000000000..0c52727dc --- /dev/null +++ b/templates/webpages/part_classification/form.html @@ -0,0 +1,37 @@ +[% USE HTML %][% USE L %][% USE LxERP %] +

[% FORM.title %]

+ + + +[%- INCLUDE 'common/flash.html' %] + + + + + + + + + + + + + + + + + + +
[% LxERP.t8('Description') %][% L.input_tag("part_classification.description", LxERP.t8(SELF.part_classification.description)) %]
[% LxERP.t8('TypeAbbreviation') %][% L.input_tag("part_classification.abbreviation", LxERP.t8(SELF.part_classification.abbreviation),size=>"2",maxlength=>"2" ) %]
[% LxERP.t8('Used for Purchase') %][% L.checkbox_tag("part_classification.used_for_purchase", checked=(SELF.part_classification.used_for_purchase ? 1:'')) %]
[% LxERP.t8('Used for Sale') %][% L.checkbox_tag("part_classification.used_for_sale", checked=(SELF.part_classification.used_for_sale ? 1:'')) %]
+ +

+ [% 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 %] + [% LxERP.t8('Abort') %] +

+ +
diff --git a/templates/webpages/part_classification/list.html b/templates/webpages/part_classification/list.html new file mode 100644 index 000000000..52ded516b --- /dev/null +++ b/templates/webpages/part_classification/list.html @@ -0,0 +1,47 @@ +[% USE HTML %][% USE L %][% USE LxERP %] +

[% FORM.title %]

+ +[%- INCLUDE 'common/flash.html' %] + +
+ [% IF !PART_CLASSIFICATIONS.size %] +

+ [%- LxERP.t8('No parts classification has been created yet.') %] +

+ + [%- ELSE %] + + + + + + + + + + + + + [%- FOREACH part_classification = PART_CLASSIFICATIONS %] + + + + + + + + [%- END %] + +
[%- LxERP.t8('reorder item') %][%- LxERP.t8('Description') %][%- LxERP.t8('TypeAbbreviation') %][%- LxERP.t8('Used for Purchase') %][%- LxERP.t8('Used for Sale') %]
[%- LxERP.t8('reorder item') %] + + [%- HTML.escape(LxERP.t8(part_classification.description)) %] + + [%- HTML.escape(LxERP.t8(part_classification.abbreviation)) %][% IF part_classification.used_for_purchase %][% LxERP.t8('Yes') %][% ELSE %][% LxERP.t8('No') %][% END %][% IF part_classification.used_for_sale %][% LxERP.t8('Yes') %][% ELSE %][% LxERP.t8('No') %][% END %]
+ [%- END %] + +

+ [%- LxERP.t8('Create a new parts classification') %] +

+
+ + [% L.sortable_element('#part_classification_list tbody', url => 'controller.pl?action=PartClassification/reorder', with => 'part_classification_id') %] diff --git a/templates/webpages/wh/journal_filter.html b/templates/webpages/wh/journal_filter.html index 07ec2e7b3..8b342d7c2 100644 --- a/templates/webpages/wh/journal_filter.html +++ b/templates/webpages/wh/journal_filter.html @@ -1,5 +1,6 @@ [%- USE T8 %] [%- USE L %] +[%- USE P %] [%- USE HTML %][%- USE JavaScript %]

[% 'Report about warehouse transactions' | $T8 %]

@@ -97,6 +98,10 @@ [% 'Part Number' | $T8 %]: + + [% 'Parts Classification' | $T8 %]: + [% P.select_classification('classification_id') %] + [% 'Part Description' | $T8 %]: diff --git a/templates/webpages/wh/report_filter.html b/templates/webpages/wh/report_filter.html index 70fb4f126..d68ebeae3 100644 --- a/templates/webpages/wh/report_filter.html +++ b/templates/webpages/wh/report_filter.html @@ -1,5 +1,6 @@ [%- USE T8 %] [%- USE L %] +[%- USE P %] [%- USE HTML %][%- USE JavaScript %]

[% 'Report about warehouse contents' | $T8 %]

@@ -97,6 +98,10 @@ [% 'Part Number' | $T8 %]: + + [% 'Parts Classification' | $T8 %]: + [% P.select_classification('classification_id') %] + [% 'Part Description' | $T8 %]: