Briefe: Lieferantenbriefe
authorSven Schöling <s.schoeling@linet-services.de>
Wed, 3 Aug 2016 13:13:59 +0000 (15:13 +0200)
committerSven Schöling <s.schoeling@linet-services.de>
Wed, 3 Aug 2016 13:19:55 +0000 (15:19 +0200)
15 files changed:
SL/Controller/Letter.pm
SL/DB/Letter.pm
SL/DB/Manager/Letter.pm
SL/DB/MetaSetup/Letter.pm
SL/DB/MetaSetup/LetterDraft.pm
doc/UPGRADE
doc/changelog
locale/de/all
menus/user/00-erp.yaml
sql/Pg-upgrade2-auth/purchase_letter_rights.pl [new file with mode: 0644]
sql/Pg-upgrade2/letter_vendorletter.sql [new file with mode: 0644]
templates/print/RB/letter.tex
templates/webpages/letter/edit.html
templates/webpages/letter/load_drafts.html
templates/webpages/letter/search.html

index 553fe0a..2495b47 100644 (file)
@@ -23,7 +23,7 @@ use SL::Webdav;
 use SL::Webdav::File;
 
 use Rose::Object::MakeMethods::Generic (
-  'scalar --get_set_init' => [ qw(letter all_employees models webdav_objects) ],
+  'scalar --get_set_init' => [ qw(letter all_employees models webdav_objects is_sales) ],
 );
 
 __PACKAGE__->run_before('check_auth_edit');
@@ -37,9 +37,12 @@ my %sort_columns = (
   subject               => t8('Subject'),
   letternumber          => t8('Letternumber'),
   customer_id           => t8('Customer'),
+  vendor_id             => t8('Vendor'),
   contact               => t8('Contact'),
 );
 
+### actions
+
 sub action_add {
   my ($self, %params) = @_;
 
@@ -60,8 +63,10 @@ sub action_edit {
   return $self->action_add
     unless $::form->{letter} || $::form->{draft};
 
-  $self->letter(SL::DB::Letter->new_from_draft($::form->{draft}{id}))
-    if $::form->{draft};
+  if ($::form->{draft}) {
+    $self->letter(SL::DB::Letter->new_from_draft($::form->{draft}{id}));
+    $self->is_sales($self->letter->is_sales);
+  }
 
   $self->_display(
     title  => t8('Edit Letter'),
@@ -94,7 +99,7 @@ sub action_update_contacts {
 
   my $letter = $self->letter;
 
-  if (!$self->letter->customer_id || !$self->letter->customer) {
+  if (!$self->letter->has_customer_vendor) {
     return $self->js
       ->replaceWith(
         '#letter_cp_id',
@@ -103,12 +108,12 @@ sub action_update_contacts {
       ->render;
   }
 
-  my $contacts = $letter->customer->contacts;
+  my $contacts = $letter->customer_vendor->contacts;
 
   my $default;
   if (   $letter->contact
       && $letter->contact->cp_cv_id
-      && $letter->contact->cp_cv_id == $letter->customer_id) {
+      && $letter->contact->cp_cv_id == $letter->customer_vendor_id) {
     $default = $letter->contact->cp_id;
   } else {
     $default = '';
@@ -336,6 +341,8 @@ sub action_send_email {
   );
 }
 
+### internal methods
+
 sub _display {
   my ($self, %params) = @_;
 
@@ -346,7 +353,7 @@ sub _display {
  $params{title} ||= t8('Edit Letter');
 
   $::form->{type}             = 'letter';   # needed for print_options
-  $::form->{vc}               = 'customer'; # needs to be for _get_contacts...
+  $::form->{vc}               = $letter->is_sales ? 'customer' : 'vendor'; # needs to be for _get_contacts...
 
   $::request->layout->add_javascripts('customer_or_vendor_selection.js');
   $::request->layout->add_javascripts('edit_part_window.js');
@@ -386,8 +393,8 @@ sub prepare_report {
   my $report      = SL::ReportGenerator->new(\%::myconfig, $::form);
   $self->{report} = $report;
 
-  my @columns  = qw(date subject letternumber customer_id contact date);
-  my @sortable = qw(date subject letternumber customer_id contact date);
+  my @columns  = qw(date subject letternumber customer_id vendor_id contact date);
+  my @sortable = qw(date subject letternumber customer_id vendor_id contact date);
 
   my %column_defs = (
     date                  => { text => t8('Date'),         sub => sub { $_[0]->date_as_date } },
@@ -395,7 +402,8 @@ sub prepare_report {
                                obj_link => sub { $self->url_for(action => 'edit', 'letter.id' => $_[0]->id, callback => $self->models->get_callback) }  },
     letternumber          => { text => t8('Letternumber'), sub => sub { $_[0]->letternumber },
                                obj_link => sub { $self->url_for(action => 'edit', 'letter.id' => $_[0]->id, callback => $self->models->get_callback) }  },
-    customer_id           => { text => t8('Customer'),      sub => sub { SL::DB::Manager::Customer->find_by_or_create(id => $_[0]->customer_id)->displayable_name } },
+    customer_id           => { text => t8('Customer'),      sub => sub { SL::DB::Manager::Customer->find_by_or_create(id => $_[0]->customer_id)->displayable_name }, visible => $self->is_sales },
+    vendor_id             => { text => t8('Vendor'),        sub => sub { SL::DB::Manager::Vendor->find_by_or_create(id => $_[0]->vendor_id)->displayable_name }, visible => !$self->is_sales},
     contact               => { text => t8('Contact'),       sub => sub { $_[0]->contact ? $_[0]->contact->full_name : '' } },
   );
 
@@ -413,10 +421,11 @@ sub prepare_report {
 
   $report->set_columns(%column_defs);
   $report->set_column_order(@columns);
-  $report->set_export_options(qw(list filter));
+  $report->set_export_options(qw(list filter is_sales));
   $report->set_options_from_form;
 
   $self->models->disable_plugin('paginated') if $report->{options}{output_format} =~ /^(pdf|csv)$/i;
+  $self->models->add_additional_url_params(is_sales => $self->is_sales);
   $self->models->finalize;
   $self->models->set_report_generator_sort_options(report => $report, sortable_columns => \@sortable);
 
@@ -465,7 +474,11 @@ sub load_letter_draft {
 
   return 0 if $params{skip_drafts};
 
-  my $letter_drafts = SL::DB::Manager::LetterDraft->get_all;
+  my $letter_drafts = SL::DB::Manager::LetterDraft->get_all(
+    query => [
+      SL::DB::Manager::Letter->is_sales_filter($self->is_sales),
+    ]
+  );
 
   return unless @$letter_drafts;
 
@@ -552,7 +565,7 @@ sub init_letter {
                                            ->assign_attributes(%{ $::form->{letter} });
 
   if ($letter->cp_id) {
-#     $letter->customer_id($letter->contact->cp_cv_id);
+#     $letter->customer_vendor_id($letter->contact->cp_cv_id);
       # contacts don't have language_id yet
 #     $letter->greeting(GenericTranslations->get(
 #       translation_type => 'greetings::' . ($letter->contact->cp_gender eq 'f' ? 'female' : 'male'),
@@ -561,6 +574,8 @@ sub init_letter {
 #     ));
   }
 
+  $self->is_sales($letter->is_sales);
+
   $letter;
 }
 
@@ -570,6 +585,9 @@ sub init_models {
   SL::Controller::Helper::GetModels->new(
     controller   => $self,
     model        => 'Letter',
+    query        => [
+      SL::DB::Manager::Letter->is_sales_filter($self->is_sales),
+    ],
     sorted       => \%sort_columns,
     with_objects => [ 'contact', 'salesman', 'employee' ],
   );
@@ -600,6 +618,11 @@ sub init_webdav_objects {
   } @all_objects ];
 }
 
+sub init_is_sales {
+  die 'is_sales must be set' unless defined $::form->{is_sales};
+  $::form->{is_sales};
+}
+
 sub check_auth_edit {
   $::auth->assert('sales_letter_edit');
 }
@@ -622,14 +645,8 @@ SL::Controller::Letter - Letters CRUD and printing
 
 Simple letter CRUD controller with drafting capabilities.
 
-=head1 TODO
-
-  Customer/Vendor switch for dealing with vendor letters
-
 copy to webdav is crap
 
-customer/vendor stuff
-
 =head1 AUTHOR
 
 Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>
index 27b0cff..0753d67 100644 (file)
@@ -28,4 +28,28 @@ sub new_from_draft {
   $self;
 }
 
+sub is_sales {
+  die 'not an accessor' if @_ > 1;
+  $_[0]{customer_id} * 1;
+}
+
+sub has_customer_vendor {
+  my ($self) = @_;
+  die 'not an accessor' if @_ > 1;
+
+  return $self->is_sales
+    ? ($self->customer_id && $self->customer)
+    : ($self->vendor_id   && $self->vendor);
+}
+
+sub customer_vendor {
+  die 'not an accessor' if @_ > 1;
+  $_[0]->is_sales ? $_[0]->customer : $_[0]->vendor;
+}
+
+sub customer_vendor_id {
+  die 'not an accessor' if @_ > 1;
+  $_[0]->customer_id || $_[0]->vendor_id;
+}
+
 1;
index 62c71c0..a412cd8 100644 (file)
@@ -12,6 +12,20 @@ use SL::DB::Helper::Sorted;
 sub object_class { 'SL::DB::Letter' }
 
 __PACKAGE__->make_manager_methods;
+__PACKAGE__->add_filter_specs(
+  is_sales => sub {
+    my ($key, $value, $prefix) = @_;
+    __PACKAGE__->is_sales_filter($value, $prefix);
+  },
+);
+
+sub is_sales_filter {
+  my ($class, $value, $prefix) = @_;
+
+  return () if !defined $value;
+  return ($prefix . 'customer_id' => { gt => 0 }) if $value;
+  return ($prefix . 'vendor_id'   => { gt => 0 }) if !$value;
+}
 
 sub _sort_spec {
   return ( columns => { SIMPLE    => 'ALL',
index b4dbe10..64d513e 100644 (file)
@@ -11,7 +11,7 @@ __PACKAGE__->meta->table('letter');
 __PACKAGE__->meta->columns(
   body         => { type => 'text' },
   cp_id        => { type => 'integer' },
-  customer_id  => { type => 'integer', not_null => 1 },
+  customer_id  => { type => 'integer' },
   date         => { type => 'date' },
   employee_id  => { type => 'integer' },
   greeting     => { type => 'text' },
@@ -23,6 +23,7 @@ __PACKAGE__->meta->columns(
   reference    => { type => 'text' },
   salesman_id  => { type => 'integer' },
   subject      => { type => 'text' },
+  vendor_id    => { type => 'integer' },
 );
 
 __PACKAGE__->meta->primary_key_columns([ 'id' ]);
@@ -49,6 +50,11 @@ __PACKAGE__->meta->foreign_keys(
     class       => 'SL::DB::Employee',
     key_columns => { salesman_id => 'id' },
   },
+
+  vendor => {
+    class       => 'SL::DB::Vendor',
+    key_columns => { vendor_id => 'id' },
+  },
 );
 
 1;
index 8b9782e..d395cfb 100644 (file)
@@ -11,7 +11,7 @@ __PACKAGE__->meta->table('letter_draft');
 __PACKAGE__->meta->columns(
   body         => { type => 'text' },
   cp_id        => { type => 'integer' },
-  customer_id  => { type => 'integer', not_null => 1 },
+  customer_id  => { type => 'integer' },
   date         => { type => 'date' },
   employee_id  => { type => 'integer' },
   greeting     => { type => 'text' },
@@ -23,6 +23,7 @@ __PACKAGE__->meta->columns(
   reference    => { type => 'text' },
   salesman_id  => { type => 'integer' },
   subject      => { type => 'text' },
+  vendor_id    => { type => 'integer' },
 );
 
 __PACKAGE__->meta->primary_key_columns([ 'id' ]);
@@ -49,6 +50,11 @@ __PACKAGE__->meta->foreign_keys(
     class       => 'SL::DB::Employee',
     key_columns => { salesman_id => 'id' },
   },
+
+  vendor => {
+    class       => 'SL::DB::Vendor',
+    key_columns => { vendor_id => 'id' },
+  },
 );
 
 1;
index d3c84ef..3cb8989 100644 (file)
@@ -19,6 +19,10 @@ Upgrade auf v3.4.x Unstable
   das auf dem Zielsystem absolut nicht möglich ist, muss das Upgradescript
   sql/Pg-Upgrade2/trigram_indices.sql deaktiviert oder entfernt werden.
 
+* Für das neue Feature Lieferantenbriefe ist die Standardvorlage für Briefe
+  (letter.tex) angepasst worden. Statt letter.customer muss der Adressat jetzt
+  aus letter.custoemr_vendor erzeugt werden.
+
 
 Upgrade auf v3.4.1
 ==================
index b8ab552..af06fff 100644 (file)
@@ -15,6 +15,9 @@ kleinere neue Features und Detailverbesserungen:
   - Neues Recht "Verknüpfte Belege", standardmäßig erlaubt. Betrifft alle
     Belege und die Projektstammdaten
 
+  - Briefe sind jetzt auch für Lieferanten verfügbar. Die neuen Rechte dafür
+    sind für Gruppen vergeben, die auch Einkaufsbelege bearbeiten dürfen.
+
 Administrative Änderungen
 
   - Diverse Textsuchen werden jetzt durch eine neue Klasse Indizes
index 2bb37dd..b55d8ba 100755 (executable)
@@ -1089,6 +1089,7 @@ $self->{texts} = {
   'Edit project link'           => 'Projektverknüpfung bearbeiten',
   'Edit project status'         => 'Projektstatus bearbeiten',
   'Edit project type'           => 'Projekttypen bearbeiten',
+  'Edit purchase letters'       => 'Einkaufsbrief erstellen',
   'Edit purchase price rule'    => 'Einkaufspreisregel bearbeiten',
   'Edit requirement spec'       => 'Pflichtenheft bearbeiten',
   'Edit requirement spec status' => 'Pflichtenheftstatus bearbeiten',
@@ -2544,6 +2545,7 @@ $self->{texts} = {
   'Show overdue sales quotations and requests for quotations...' => 'Überfällige Angebote und Preisanfragen anzeigen...',
   'Show parts'                  => 'Artikel anzeigen',
   'Show parts longdescription (notes) in select list' => 'Langtext in Auswahlliste bei mehreren Treffern im Stammdaten-Bestand anzeigen',
+  'Show purchase letters report' => 'Einkaufsbriefe zeigen',
   'Show requirement spec'       => 'Pflichtenheft anzeigen',
   'Show requirement spec template' => 'Pflichtenheftvorlage anzeigen',
   'Show sales letters report'   => 'Verkaufsbrief anzeigen',
index 048fbce..8262d88 100644 (file)
   access: sales_letter_edit
   params:
     action: Letter/add
+    is_sales: 1
 - parent: ar
   id: ar_invoices
   name: Invoices
   access: sales_letter_report
   params:
     action: Letter/list
+    is_sales: 1
 - id: ap
   name: AP
   icon: ap
   params:
     action: add
     type: invoice
+- parent: ap
+  id: ap_add_letter
+  name: Add Letter
+  order: 450
+  access: purchase_letter_edit
+  params:
+    action: Letter/add
+    is_sales: 0
 - parent: ap
   id: ap_reports
   name: Reports
   params:
     action: DeliveryValueReport/list
     vc: vendor
+- parent: ap_reports
+  id: ap_reports_letters
+  name: Letters
+  order: 1100
+  access: purchase_letter_report
+  params:
+    action: Letter/list
+    is_sales: 0
 - id: warehouse
   name: Warehouse
   icon: warehouse
diff --git a/sql/Pg-upgrade2-auth/purchase_letter_rights.pl b/sql/Pg-upgrade2-auth/purchase_letter_rights.pl
new file mode 100644 (file)
index 0000000..9339dc0
--- /dev/null
@@ -0,0 +1,33 @@
+# @tag: purchase_letter_rights
+# @description: Neue Rechte für Lieferantenbriefe
+# @depends: release_3_2_0 sales_letter_rights
+# @locales: Edit purchase letters
+# @locales: Show purchase letters report
+package SL::DBUpgrade2::purchase_letter_rights;
+
+use strict;
+use utf8;
+
+use parent qw(SL::DBUpgrade2::Base);
+
+use SL::DBUtils;
+
+sub run {
+  my ($self) = @_;
+
+  $self->db_query("INSERT INTO auth.master_rights (position, name, description) VALUES (?, ?, ?)", bind => $_) for
+    [ 2550, 'purchase_letter_edit',   'Edit purchase letters'        ],
+    [ 2650, 'purchase_letter_report', 'Show purchase letters report' ];
+
+  my $groups = $main::auth->read_groups();
+
+  foreach my $group (values %{$groups}) {
+    $group->{rights}->{purchase_letter_edit} = $group->{rights}->{purchase_order_edit};
+    $group->{rights}->{purchase_letter_report} = $group->{rights}->{purchase_order_edit};
+    $main::auth->save_group($group);
+  }
+
+  return 1;
+} # end run
+
+1;
diff --git a/sql/Pg-upgrade2/letter_vendorletter.sql b/sql/Pg-upgrade2/letter_vendorletter.sql
new file mode 100644 (file)
index 0000000..f673f55
--- /dev/null
@@ -0,0 +1,10 @@
+-- @tag: letter_vendorletter
+-- @description: Briefe jetzt auch für Lieferanten
+-- @depends: release_3_4_1
+-- @encoding: utf-8
+
+ALTER TABLE letter ALTER COLUMN customer_id DROP NOT NULL;
+ALTER TABLE letter ADD COLUMN vendor_id INTEGER REFERENCES vendor(id);
+
+ALTER TABLE letter_draft ALTER COLUMN customer_id DROP NOT NULL;
+ALTER TABLE letter_draft ADD COLUMN vendor_id INTEGER REFERENCES vendor(id);
index 9657f0c..1f985f1 100644 (file)
@@ -2,7 +2,7 @@
 % config: tag-style=$( )$
 $( USE KiviLatex )$
 $( USE P )$
-$( SET customer = letter.customer )$
+$( SET customer = letter.customer_vendor )$
 \input{inheaders.tex}
 $( KiviLatex.required_packages_for_html )$
 
index 390efcc..c1fd03e 100644 (file)
@@ -10,6 +10,7 @@
   <input type="hidden" name="letter.id" value="[% letter.id | html %]">
   <input type="hidden" name="draft.id" value="[% draft.id | html %]">
   <input type="hidden" name="type" value="[% type | html %]">
+  [% L.hidden_tag('is_sales', SELF.is_sales) %]
 
   [%- INCLUDE 'common/flash.html' %]
 
@@ -31,6 +32,7 @@
  <td width=50%>
   <!-- upper left block -->
    <table width=90%>
+[%- IF SELF.is_sales %]
     <tr>
      <th align='right'>[% 'Customer' | $T8 %]:</th>
      <td>[% P.customer_vendor_picker('letter.customer_id', letter.customer_id, type='customer') %]
 [%- END %]
      </td>
     </tr>
+[%- ELSE %]
+    <tr>
+     <th align='right'>[% 'Vendor' | $T8 %]:</th>
+     <td>[% P.customer_vendor_picker('letter.vendor_id', letter.vendor_id, type='vendor') %]
+[%- IF letter.vendor_id %]
+      <input type="button" value="[% 'Details (one letter abbreviation)' | $T8 %]" onclick="show_vc_details('vendor')">
+[%- END %]
+     </td>
+    </tr>
+[%- END %]
     <tr>
      <th align='right'>[% 'Contact Person' | $T8 %]</th>
-     <td>[% L.select_tag('letter.cp_id', letter.customer_id ? letter.customer.contacts : [], value_key='cp_id', title_key='full_name', default=letter.cp_id) %]</td>
+     <td>[% L.select_tag('letter.cp_id', letter.customer_vendor_id ? letter.customer_vendor.contacts : [], value_key='cp_id', title_key='full_name', default=letter.cp_id) %]</td>
     </tr>
     <tr>
      <th align='right'>[% 'Your Reference' | $T8 %]:</th>
       var data = $('form').serializeArray();
       data.push({ name: 'action_update_contacts', value: 1 });
       $.post('controller.pl', data, kivi.eval_json_result);
+    });
+    $('#letter_vendor_id').change(function(){
+      var data = $('form').serializeArray();
+      data.push({ name: 'action_update_contacts', value: 1 });
+      $.post('controller.pl', data, kivi.eval_json_result);
     })
   })
 </script>
index f2455ec..9b9d490 100644 (file)
        <th class="listheading">&nbsp;</th>
        <th class="listheading">[% 'Date' | $T8 %]</th>
        <th class="listheading">[% 'Subject' | $T8 %]</th>
+[%- IF SELF.is_sales %]
        <th class="listheading">[% 'Customer' | $T8 %]</th>
+[%- ELSE %]
+       <th class="listheading">[% 'Vendor' | $T8 %]</th>
+[%- END %]
       </tr>
 
       [% FOREACH row = LETTER_DRAFTS %]
         <td>[% L.checkbox_tag("ids[+]", value=row.id) %]</td>
         <td>[% row.date.to_kivitendo | html %]</td>
         <td><a href="[% SELF.url_for(action='edit', 'draft.id'=row.id) %]">[% row.subject | html %]</a></td>
+[%- IF SELF.is_sales %]
         <td>[% row.customer.displayable_name | html %]</td>
+[%- ELSE %]
+        <td>[% row.vendor.displayable_name | html %]</td>
+[%- END %]
        </tr>
       [% END %]
      </table>
@@ -31,6 +39,7 @@
    <tr>
     <td>
      <input type="hidden" name="action" value="Letter/dispatch">
+     [% L.hidden_tag('is_sales', SELF.is_sales) %]
      <input type="submit" class="submit" name="action_skip_draft" value="[% 'Skip' | $T8 %]">
      <input type="submit" class="submit" name="action_delete_drafts" value="[% 'Delete drafts' | $T8 %]">
    </td>
index 90f46bb..0e8f0c8 100644 (file)
    <th align='right'>[% 'Letternumber' | $T8 %]</th>
    <td>[% L.input_tag('filter.letternumber:substr::ilike', filter.letternumber_substr__ilike, style='width:250px') %]</th>
   </tr>
+[%- IF SELF.is_sales %]
   <tr>
    <td align="right">[% 'Customer' | $T8 %]</td>
    <td>[% L.customer_vendor_picker('filter.customer_id', filter.customer_id, type='customer', style='width:250px') %]</td>
   </tr>
-
+[%- ELSE %]
+  <tr>
+   <td align="right">[% 'Vendor' | $T8 %]</td>
+   <td>[% L.customer_vendor_picker('filter.vendor_id', filter.vendor_id, type='vendor', style='width:250px') %]</td>
+  </tr>
+[%- END %]
   <tr>
    <td align="right">[% 'Contact' | $T8 %]</td>
    <td>[% L.input_tag('filter.contact.cp_name:substr::ilike', filter.contact.cp_name_substr__ilike, style='width:250px') %]</th>
@@ -45,6 +51,7 @@
   </tr>
  </table>
 
+ [% L.hidden_tag('is_sales', SELF.is_sales) %]
  [% L.hidden_tag('sort_by', FORM.sort_by) %]
  [% L.hidden_tag('sort_dir', FORM.sort_dir) %]
  [% L.hidden_tag('page', FORM.page) %]