Warengruppen - Umstellung auf Controller, sortkey, obsolete
authorG. Richardson <information@kivitendo-premium.de>
Wed, 7 Dec 2016 09:04:33 +0000 (10:04 +0100)
committerG. Richardson <information@kivitendo-premium.de>
Wed, 7 Dec 2016 10:45:22 +0000 (11:45 +0100)
Neuer CRUD-Controller nur für Warengruppen

Die Reihenfolge der Warengruppen kann nun eingestellt werden, und man kann
Warengruppen auf ungültig setzen, sofern sie nicht mehr aktiv bei Artikeln in
Verwendung sind, so daß sie bei neuen Waren nicht mehr ausgewählt werden
können.

SL/Controller/Part.pm
SL/Controller/PartsGroup.pm [new file with mode: 0644]
SL/DB/Manager/PartsGroup.pm
SL/DB/MetaSetup/PartsGroup.pm
SL/DB/PartsGroup.pm
locale/de/all
locale/en/all
menus/user/00-erp.yaml
sql/Pg-upgrade2/partsgroup_sortkey_obsolete.sql [new file with mode: 0644]
templates/webpages/partsgroup/form.html [new file with mode: 0644]

index 2741370..9458a83 100644 (file)
@@ -872,7 +872,8 @@ sub init_all_languages {
 }
 
 sub init_all_partsgroups {
-  SL::DB::Manager::PartsGroup->get_all_sorted;
+  my ($self) = @_;
+  SL::DB::Manager::PartsGroup->get_all_sorted(query => [ or => [ id => $self->part->partsgroup_id, obsolete => 0 ] ]);
 }
 
 sub init_all_buchungsgruppen {
diff --git a/SL/Controller/PartsGroup.pm b/SL/Controller/PartsGroup.pm
new file mode 100644 (file)
index 0000000..603aa12
--- /dev/null
@@ -0,0 +1,159 @@
+package SL::Controller::PartsGroup;
+
+use strict;
+
+use parent qw(SL::Controller::Base);
+
+use SL::Helper::Flash;
+use SL::Locale::String;
+use SL::DB::Default;
+use SL::DB::Manager::PartsGroup;
+
+use Rose::Object::MakeMethods::Generic (
+  scalar                  => [ qw(partsgroup) ],
+  'scalar --get_set_init' => [ qw(all_partsgroups) ],
+);
+
+__PACKAGE__->run_before('check_auth');
+__PACKAGE__->run_before('load_partsgroup', only => [ qw(edit update delete) ]);
+
+#
+# actions
+#
+
+sub action_list {
+  my ($self) = @_;
+
+  $self->render('partsgroup/list',
+                title   => t8('Partsgroups'),
+               );
+}
+
+sub action_new {
+  my ($self) = @_;
+
+  $self->partsgroup( SL::DB::PartsGroup->new );
+  $self->render('partsgroup/form',
+                 title => t8('Add partsgroup'),
+               );
+}
+
+sub action_edit {
+  my ($self) = @_;
+
+  $self->render('partsgroup/form',
+                 title   => t8('Edit partsgroup'),
+                );
+}
+
+sub action_create {
+  my ($self) = @_;
+
+  $self->partsgroup( SL::DB::PartsGroup->new );
+  $self->create_or_update;
+}
+
+sub action_update {
+  my ($self) = @_;
+  $self->create_or_update;
+}
+
+sub action_delete {
+  my ($self) = @_;
+
+  if ( !$self->partsgroup->orphaned ) {
+    flash_later('error', $::locale->text('The partsgroup has been used and cannot be deleted.'));
+  } elsif ( eval { $self->partsgroup->delete; 1; } ) {
+    flash_later('info',  $::locale->text('The partsgroup has been deleted.'));
+  } else {
+    flash_later('error', $::locale->text('The partsgroup has been used and cannot be deleted.'));
+  };
+  $self->redirect_to(action => 'list');
+}
+
+sub action_reorder {
+  my ($self) = @_;
+
+  SL::DB::PartsGroup->reorder_list(@{ $::form->{partsgroup_id} || [] });
+  $self->render(\'', { type => 'json' });
+}
+
+#
+# filters
+#
+
+sub check_auth {
+  $::auth->assert('config');
+}
+
+sub load_partsgroup {
+  my ($self) = @_;
+
+  $self->partsgroup( SL::DB::PartsGroup->new(id => $::form->{id})->load );
+}
+
+sub init_all_partsgroups { SL::DB::Manager::PartsGroup->get_all_sorted }
+
+#
+# helpers
+#
+
+sub create_or_update {
+  my ($self) = @_;
+  my $is_new = !$self->partsgroup->id;
+
+  my $params = delete($::form->{partsgroup}) || { };
+
+  $self->partsgroup->assign_attributes(%{ $params });
+
+  my @errors = $self->partsgroup->validate;
+
+  if (@errors) {
+    flash('error', @errors);
+    $self->render('partsgroup/form',
+                   title => $is_new ? t8('Add partsgroup') : t8('Edit partsgroup'),
+                 );
+    return;
+  }
+
+  $self->partsgroup->save;
+
+  flash_later('info', $is_new ? t8('The partsgroup has been created.') : t8('The partsgroup has been saved.'));
+  $self->redirect_to(action => 'list');
+}
+
+1;
+
+__END__
+
+=encoding utf-8
+
+=head1 NAME
+
+SL::Controller::PartsGroup - CRUD controller for partsgroups
+
+=head1 SYNOPSIS
+
+A new controller to create / edit / delete partsgroups.
+
+Partsgroups can only be deleted if they haven't been used anywhere.
+
+=head1 OBSOLETE PARTSGROUPS
+
+A partsgroup can be deleted if it hasn't been used anywhere / is orphaned.
+
+A partsgroup can be set to obsolete, which means new items can't be assigned
+that partsgroup, but old items with that partsgroup can keep it. And you can
+also still filter for these obsolete partsgroups in reports.
+
+=head1 ISSUES
+
+Unlike the old version (pe.pl/PE.pm), there is no way to filter/search the
+partsgroups in the overview page, it always shows the complete (ordered) list,
+ordered by sortkey.
+
+=head1 AUTHOR
+
+G. Richardson E<lt>grichardson@kivitendo-premium.deE<gt>
+
+=cut
index 24e016c..9b08133 100644 (file)
@@ -12,7 +12,7 @@ sub object_class { 'SL::DB::PartsGroup' }
 __PACKAGE__->make_manager_methods;
 
 sub _sort_spec {
-  return ( default => [ 'partsgroup', 1 ],
+  return ( default => [ 'sortkey', 1 ],
            columns => { SIMPLE => 'ALL' });
 }
 
index f9915d5..1e3ca3e 100644 (file)
@@ -12,7 +12,9 @@ __PACKAGE__->meta->columns(
   id         => { type => 'integer', not_null => 1, sequence => 'id' },
   itime      => { type => 'timestamp', default => 'now()' },
   mtime      => { type => 'timestamp' },
+  obsolete   => { type => 'boolean', default => 'false' },
   partsgroup => { type => 'text' },
+  sortkey    => { type => 'integer', not_null => 1 },
 );
 
 __PACKAGE__->meta->primary_key_columns([ 'id' ]);
index f40c5de..d612294 100644 (file)
@@ -7,6 +7,7 @@ use strict;
 
 use SL::DB::MetaSetup::PartsGroup;
 use SL::DB::Manager::PartsGroup;
+use SL::DB::Helper::ActsAsList;
 
 __PACKAGE__->meta->add_relationship(
   custom_variable_configs => {
@@ -23,4 +24,34 @@ sub displayable_name {
   return join ' ', grep $_, $self->id, $self->partsgroup;
 }
 
+sub validate {
+  my ($self) = @_;
+  require SL::DB::Customer;
+
+  my @errors;
+
+  push @errors, $::locale->text('The description is missing.') if $self->id and !$self->partsgroup;
+
+  return @errors;
+}
+
+sub orphaned {
+  my ($self) = @_;
+  die 'not an accessor' if @_ > 1;
+
+  return 1 unless $self->id;
+
+  my @relations = qw(
+    SL::DB::Part
+    SL::DB::CustomVariableConfigPartsgroup
+  );
+
+  for my $class (@relations) {
+    eval "require $class";
+    return 0 if $class->_get_manager_class->get_all_count(query => [ partsgroup_id => $self->id ]);
+  }
+
+  return 1;
+}
+
 1;
index 6830b87..9c31ec1 100755 (executable)
@@ -204,6 +204,7 @@ $self->{texts} = {
   'Add new price rule item'     => 'Neue Bedingung hinzufügen',
   'Add note'                    => 'Notiz erfassen',
   'Add part'                    => 'Artikel hinzufügen',
+  'Add partsgroup'              => 'Warengruppe hinzufügen',
   'Add picture'                 => 'Bild hinzufügen',
   'Add picture to text block'   => 'Bild dem Textblock hinzufügen',
   'Add pricegroup'              => 'Preisgruppe hinzufügen',
@@ -1109,6 +1110,7 @@ $self->{texts} = {
   'Edit general settings'       => 'Grundeinstellungen bearbeiten',
   'Edit greetings'              => 'Anreden bearbeiten',
   'Edit note'                   => 'Notiz bearbeiten',
+  'Edit partsgroup'             => 'Warengruppe bearbeiten',
   'Edit payment term'           => 'Zahlungsbedingungen bearbeiten',
   'Edit picture'                => 'Bild bearbeiten',
   'Edit predefined text'        => 'Vordefinierten Textblock bearbeiten',
@@ -2041,6 +2043,7 @@ $self->{texts} = {
   'Parts, services and assemblies' => 'Waren, Dienstleistungen und Erzeugnisse',
   'Partsgroup (database ID)'    => 'Warengruppe (Datenbank-ID)',
   'Partsgroup (name)'           => 'Warengruppe (Name)',
+  'Partsgroups'                 => 'Warengruppen',
   'Partsgroups where variables are shown' => 'Warengruppen, bei denen Variablen angezeigt werden',
   'Password'                    => 'Passwort',
   'Paste'                       => 'Einfügen',
@@ -2982,6 +2985,10 @@ $self->{texts} = {
   'The parts have been removed.' => 'Die Waren wurden aus dem Lager entnommen.',
   'The parts have been stocked.' => 'Die Artikel wurden eingelagert.',
   'The parts have been transferred.' => 'Die Waren wurden umgelagert.',
+  'The partsgroup has been created.' => 'Die Warengruppe wurde erstellt.',
+  'The partsgroup has been deleted.' => 'Die Warengruppe wurde gelöscht.',
+  'The partsgroup has been saved.' => 'Die Warengruppe wurde gespeichert.',
+  'The partsgroup has been used and cannot be deleted.' => 'Die Warengruppe wurde bereits verwendet und kann nicht gelöscht werden.',
   'The password is too long (maximum length: #1).' => 'Das Passwort ist zu lang (maximale Länge: #1).',
   'The password is too short (minimum length: #1).' => 'Das Password ist zu kurz (minimale Länge: #1).',
   'The password is weak (e.g. it can be found in a dictionary).' => 'Das Passwort ist schwach (z.B. wenn es in einem Wörterbuch steht).',
index b3a7c31..9ba415a 100644 (file)
@@ -158,7 +158,6 @@ $self->{texts} = {
   'Add Follow-Up'               => '',
   'Add Follow-Up for #1'        => '',
   'Add General Ledger Transaction' => '',
-  'Add Group'                   => '',
   'Add Language'                => '',
   'Add Lead'                    => '',
   'Add Letter'                  => '',
@@ -203,6 +202,7 @@ $self->{texts} = {
   'Add new price rule item'     => '',
   'Add note'                    => '',
   'Add part'                    => '',
+  'Add partsgroup'              => '',
   'Add picture'                 => '',
   'Add picture to text block'   => '',
   'Add pricegroup'              => '',
@@ -1106,6 +1106,7 @@ $self->{texts} = {
   'Edit general settings'       => '',
   'Edit greetings'              => '',
   'Edit note'                   => '',
+  'Edit partsgroup'             => '',
   'Edit payment term'           => '',
   'Edit picture'                => '',
   'Edit predefined text'        => '',
@@ -2032,8 +2033,10 @@ $self->{texts} = {
   'Parts Master Data'           => '',
   'Parts with existing part numbers' => '',
   'Parts, services and assemblies' => '',
+  'Partsgroup'                  => '',
   'Partsgroup (database ID)'    => '',
   'Partsgroup (name)'           => '',
+  'Partsgroups'                 => '',
   'Partsgroups where variables are shown' => '',
   'Password'                    => '',
   'Paste'                       => '',
@@ -2976,6 +2979,10 @@ $self->{texts} = {
   'The parts have been removed.' => '',
   'The parts have been stocked.' => '',
   'The parts have been transferred.' => '',
+  'The partsgroup has been created.' => '',
+  'The partsgroup has been deleted.' => '',
+  'The partsgroup has been saved.' => '',
+  'The partsgroup has been used and cannot be deleted.' => '',
   'The password is too long (maximum length: #1).' => '',
   'The password is too short (minimum length: #1).' => '',
   'The password is weak (e.g. it can be found in a dictionary).' => '',
index 8d8741f..9c4f977 100644 (file)
   params:
     action: BankAccount/list
 - parent: system
-  id: system_groups
-  name: Groups
+  id: system_partsgroups
+  name: Partsgroups
   order: 900
-  module: pe.pl
   params:
-    action: search
-    type: partsgroup
+    action: PartsGroup/list
 - parent: system
   id: system_pricegroups
   name: Pricegroups
diff --git a/sql/Pg-upgrade2/partsgroup_sortkey_obsolete.sql b/sql/Pg-upgrade2/partsgroup_sortkey_obsolete.sql
new file mode 100644 (file)
index 0000000..6b9ec79
--- /dev/null
@@ -0,0 +1,13 @@
+-- @tag: partsgroup_sortkey_obsolete
+-- @description: Sortierreihenfolge und ungültig für Warengruppen
+-- @charset: UTF-8
+-- @depends: release_3_4_1
+-- @ignore: 0
+
+ALTER TABLE partsgroup ADD COLUMN obsolete BOOLEAN DEFAULT FALSE;
+ALTER TABLE partsgroup ADD COLUMN sortkey INTEGER;
+
+CREATE SEQUENCE tmp_counter;
+UPDATE partsgroup SET sortkey = nextval('tmp_counter');
+DROP SEQUENCE tmp_counter;
+ALTER TABLE partsgroup ALTER COLUMN sortkey SET NOT NULL;
diff --git a/templates/webpages/partsgroup/form.html b/templates/webpages/partsgroup/form.html
new file mode 100644 (file)
index 0000000..60134b7
--- /dev/null
@@ -0,0 +1,50 @@
+[%- USE HTML -%][%- USE LxERP -%][%- USE L -%][%- USE T8 -%]
+
+[% SET style="width: 400px" %]
+[% SET size=15 %]
+
+<h1>[% HTML.escape(title) %]</h1>
+
+<form action="controller.pl" method="post">
+
+[%- INCLUDE 'common/flash.html' %]
+
+[%- L.hidden_tag("id", SELF.partsgroup.id) %]
+
+<table>
+  <tr>
+    <th align="right">[% 'Description' | $T8 %]</th>
+    <td>
+       [%- L.input_tag("partsgroup.partsgroup", SELF.partsgroup.partsgroup) %]
+   </td>
+  [% IF SELF.partsgroup.id %]
+  <tr>
+    <th align="right">[% 'Obsolete' | $T8 %]</th>
+    <td>[% L.checkbox_tag('partsgroup.obsolete', checked = SELF.partsgroup.obsolete, for_submit=1) %]</td>
+  </tr>
+  </tr>
+  [% END %]
+</table>
+
+ <p>
+  [% L.hidden_tag("action", "PartsGroup/dispatch") %]
+  [% L.submit_tag("action_" _  (SELF.partsgroup.id ? "update" : "create"), LxERP.t8('Save'), onclick="return check_prerequisites();") %]
+  [%- IF SELF.partsgroup.id AND SELF.partsgroup.orphaned -%]
+    [% L.submit_tag("action_delete", LxERP.t8('Delete')) %]
+  [%- END %]
+  <a href="[% SELF.url_for(action='list') %]">[%- LxERP.t8("Cancel") %]</a>
+ </p>
+
+ <hr>
+
+<script type="text/javascript">
+<!--
+function check_prerequisites() {
+  if ($('#partsgroup_partsgroup').val() === "") {
+    alert(kivi.t8('The description is missing.'));
+    return false;
+  }
+  return true;
+}
+-->
+</script>