Kundenstatistik: erster commit ohne Webtemplates
[kivitendo-erp.git] / SL / Controller / CustomerVendor.pm
index f16ce6f..373f713 100644 (file)
@@ -3,10 +3,15 @@ package SL::Controller::CustomerVendor;
 use strict;
 use parent qw(SL::Controller::Base);
 
+use List::MoreUtils qw(any);
+
 use SL::JSON;
 use SL::DBUtils;
 use SL::Helper::Flash;
 use SL::Locale::String;
+use SL::Controller::Helper::GetModels;
+use SL::Controller::Helper::ReportGenerator;
+use SL::Controller::Helper::ParseFilter;
 
 use SL::DB::Customer;
 use SL::DB::Vendor;
@@ -22,6 +27,13 @@ use SL::DB::FollowUp;
 use SL::DB::FollowUpLink;
 use SL::DB::History;
 use SL::DB::Currency;
+use SL::DB::Invoice;
+
+use Data::Dumper;
+
+use Rose::Object::MakeMethods::Generic (
+  'scalar --get_set_init' => [ qw(customer_models vendor_models) ],
+);
 
 # safety
 __PACKAGE__->run_before(
@@ -50,11 +62,35 @@ __PACKAGE__->run_before(
   '_load_customer_vendor',
   only => [
     'edit',
+    'show',
     'update',
     'ajaj_get_shipto',
     'ajaj_get_contact',
   ]
 );
+
+# make sure this comes after _load_customer_vendor
+__PACKAGE__->run_before(
+  '_check_customer_vendor_all_edit',
+  only => [
+    'edit',
+    'show',
+    'update',
+    'delete',
+    'save',
+    'save_and_ap_transaction',
+    'save_and_ar_transaction',
+    'save_and_close',
+    'save_and_invoice',
+    'save_and_order',
+    'save_and_quotation',
+    'save_and_rfq',
+    'delete',
+    'delete_contact',
+    'delete_shipto',
+  ]
+);
+
 __PACKAGE__->run_before(
   '_create_customer_vendor',
   only => [
@@ -70,6 +106,7 @@ sub action_add {
 
   $self->_pre_render();
   $self->{cv}->assign_attributes(hourly_rate => $::instance_conf->get_customer_hourly_rate) if $self->{cv}->is_customer;
+
   $self->render(
     'customer_vendor/form',
     title => ($self->is_vendor() ? $::locale->text('Add Vendor') : $::locale->text('Add Customer')),
@@ -88,12 +125,40 @@ sub action_edit {
   );
 }
 
+sub action_show {
+  my ($self) = @_;
+
+  if ($::request->type eq 'json') {
+    my $cv_hash;
+    if (!$self->{cv}) {
+      # TODO error
+    } else {
+      $cv_hash          = $self->{cv}->as_tree;
+      $cv_hash->{cvars} = $self->{cv}->cvar_as_hashref;
+    }
+
+    $self->render(\ SL::JSON::to_json($cv_hash), { layout => 0, type => 'json', process => 0 });
+  }
+}
+
 sub _save {
   my ($self) = @_;
 
+  my @errors = $self->{cv}->validate;
+  if (@errors) {
+    flash('error', @errors);
+    $self->_pre_render();
+    $self->render(
+      'customer_vendor/form',
+      title => ($self->is_vendor() ? t8('Edit Vendor') : t8('Edit Customer')),
+      %{$self->{template_args}}
+    );
+    $::dispatcher->end_request;
+  }
+
   my $db = $self->{cv}->db;
 
-  $db->do_transaction(sub {
+  $db->with_transaction(sub {
     my $cvs_by_nr;
     if ( $self->is_vendor() ) {
       if ( $self->{cv}->vendornumber ) {
@@ -118,7 +183,7 @@ sub _save {
 
     $self->{contact}->cp_cv_id($self->{cv}->id);
     if( $self->{contact}->cp_name ne '' || $self->{contact}->cp_givenname ne '' ) {
-      $self->{contact}->save();
+      $self->{contact}->save(cascade => 1);
     }
 
     if( $self->{note}->subject ne '' && $self->{note}->body ne '' ) {
@@ -140,8 +205,8 @@ sub _save {
     }
 
     $self->{shipto}->trans_id($self->{cv}->id);
-    if( $self->{shipto}->shiptoname ne '' ) {
-      $self->{shipto}->save();
+    if(any { $self->{shipto}->$_ ne '' } qw(shiptoname shiptodepartment_1 shiptodepartment_2 shiptostreet shiptozipcode shiptocity shiptocountry shiptogln shiptocontact shiptophone shiptofax shiptoemail)) {
+      $self->{shipto}->save(cascade => 1);
     }
 
     my $snumbers = $self->is_vendor() ? 'vendornumber_'. $self->{cv}->vendornumber : 'customernumber_'. $self->{cv}->customernumber;
@@ -164,6 +229,8 @@ sub _save {
         $note->delete(cascade => 'delete');
       }
     }
+
+    1;
   }) || die($db->error);
 
 }
@@ -171,34 +238,23 @@ sub _save {
 sub action_save {
   my ($self) = @_;
 
-  if (!$self->{cv}->name) {
-    flash('error', t8('Customer missing!'));
-    $self->_pre_render();
-    $self->render(
-      'customer_vendor/form',
-      title => ($self->is_vendor() ? t8('Edit Vendor') : t8('Edit Customer')),
-      %{$self->{template_args}}
-    );
-  } else {
-
-    $self->_save();
-
-    my @redirect_params = (
-      action => 'edit',
-      id     => $self->{cv}->id,
-      db     => ($self->is_vendor() ? 'vendor' : 'customer'),
-    );
+  $self->_save();
 
-    if ( $self->{contact}->cp_id ) {
-      push(@redirect_params, contact_id => $self->{contact}->cp_id);
-    }
+  my @redirect_params = (
+    action => 'edit',
+    id     => $self->{cv}->id,
+    db     => ($self->is_vendor() ? 'vendor' : 'customer'),
+  );
 
-    if ( $self->{shipto}->shipto_id ) {
-      push(@redirect_params, shipto_id => $self->{shipto}->shipto_id);
-    }
+  if ( $self->{contact}->cp_id ) {
+    push(@redirect_params, contact_id => $self->{contact}->cp_id);
+  }
 
-    $self->redirect_to(@redirect_params);
+  if ( $self->{shipto}->shipto_id ) {
+    push(@redirect_params, shipto_id => $self->{shipto}->shipto_id);
   }
+
+  $self->redirect_to(@redirect_params);
 }
 
 sub action_save_and_close {
@@ -213,23 +269,29 @@ sub action_save_and_close {
 sub _transaction {
   my ($self, $script) = @_;
 
-  $::auth->assert('general_ledger         | invoice_edit         | vendor_invoice_edit | ' .
+  $::auth->assert('gl_transactions | ap_transactions | ar_transactions'.
+                    '| invoice_edit         | vendor_invoice_edit | ' .
                  ' request_quotation_edit | sales_quotation_edit | sales_order_edit    | purchase_order_edit');
 
   $self->_save();
 
-  my $callback = $::form->escape($::form->{callback}, 1);
   my $name = $::form->escape($self->{cv}->name, 1);
   my $db = $self->is_vendor() ? 'vendor' : 'customer';
+  my $action = 'add';
+
+  if ($::instance_conf->get_feature_experimental && 'oe.pl' eq $script) {
+    $script = 'controller.pl';
+    $action = 'Order/' . $action;
+  }
 
   my $url = $self->url_for(
     controller => $script,
-    action     => 'add',
+    action     => $action,
     vc         => $db,
     $db .'_id' => $self->{cv}->id,
     $db        => $name,
     type       => $::form->{type},
-    callback   => $callback,
+    callback   => $::form->{callback},
   );
 
   print $::form->redirect_header($url);
@@ -238,7 +300,7 @@ sub _transaction {
 sub action_save_and_ar_transaction {
   my ($self) = @_;
 
-  $main::auth->assert('general_ledger');
+  $main::auth->assert('ar_transactions');
 
   $self->_transaction('ar.pl');
 }
@@ -246,7 +308,7 @@ sub action_save_and_ar_transaction {
 sub action_save_and_ap_transaction {
   my ($self) = @_;
 
-  $main::auth->assert('general_ledger');
+  $main::auth->assert('ap_transactions');
 
   $self->_transaction('ap.pl');
 }
@@ -304,7 +366,7 @@ sub action_delete {
     $self->action_edit();
   } else {
 
-    $db->do_transaction(sub {
+    $db->with_transaction(sub {
       $self->{cv}->delete(cascade => 1);
 
       my $snumbers = $self->is_vendor() ? 'vendornumber_'. $self->{cv}->vendornumber : 'customernumber_'. $self->{cv}->customernumber;
@@ -332,7 +394,7 @@ sub action_delete_contact {
     SL::Helper::Flash::flash('error', $::locale->text('No contact selected to delete'));
   } else {
 
-    $db->do_transaction(sub {
+    $db->with_transaction(sub {
       if ( $self->{contact}->used ) {
         $self->{contact}->detach();
         $self->{contact}->save();
@@ -341,9 +403,11 @@ sub action_delete_contact {
         $self->{contact}->delete(cascade => 1);
         SL::Helper::Flash::flash('info', $::locale->text('Contact deleted.'));
       }
+
+      1;
     }) || die($db->error);
 
-    $self->{contact} = SL::DB::Contact->new();
+    $self->{contact} = $self->_new_contact_object;
   }
 
   $self->action_edit();
@@ -358,7 +422,7 @@ sub action_delete_shipto {
     SL::Helper::Flash::flash('error', $::locale->text('No shipto selected to delete'));
   } else {
 
-    $db->do_transaction(sub {
+    $db->with_transaction(sub {
       if ( $self->{shipto}->used ) {
         $self->{shipto}->detach();
         $self->{shipto}->save(cascade => 1);
@@ -367,6 +431,8 @@ sub action_delete_shipto {
         $self->{shipto}->delete(cascade => 1);
         SL::Helper::Flash::flash('info', $::locale->text('Shipto deleted.'));
       }
+
+      1;
     }) || die($db->error);
 
     $self->{shipto} = SL::DB::Shipto->new();
@@ -405,7 +471,6 @@ sub action_search_contact {
   print $::form->redirect_header($url);
 }
 
-
 sub action_get_delivery {
   my ($self) = @_;
 
@@ -470,7 +535,8 @@ sub action_get_delivery {
        ON p.id = i.parts_id
 
      LEFT JOIN oe
-       ON (oe.ordnumber = ${arap}.ordnumber AND NOT ${arap}.ordnumber = '')
+       ON (oe.ordnumber = ${arap}.ordnumber AND NOT ${arap}.ordnumber = ''
+           AND ". ($arap eq 'ar' ? 'oe.customer_id IS NOT NULL' : 'oe.vendor_id IS NOT NULL') ." )
 
      ${where}
      ORDER BY ${arap}.transdate DESC LIMIT 15";
@@ -483,16 +549,19 @@ sub action_get_delivery {
 sub action_ajaj_get_shipto {
   my ($self) = @_;
 
-  my $data = {
+  my $data = {};
+  $data->{shipto} = {
     map(
       {
         my $name = 'shipto'. $_;
         $name => $self->{shipto}->$name;
       }
-      qw(_id name department_1 department_2 street zipcode city country contact phone fax email)
+      qw(_id name department_1 department_2 street zipcode city gln country contact phone fax email)
     )
   };
 
+  $data->{shipto_cvars} = $self->_prepare_cvar_configs_for_ajaj($self->{shipto}->cvars_by_config);
+
   $self->render(\SL::JSON::to_json($data), { type => 'json', process => 0 });
 }
 
@@ -519,59 +588,69 @@ sub action_ajaj_get_contact {
     )
   };
 
-  $data->{contact_cvars} = {
-    map(
-      {
-        if ( $_->config->type eq 'number' ) {
-          $_->config->name => $::form->format_amount(\%::myconfig, $_->value, -2);
-        } else {
-          $_->config->name => $_->value;
-        }
-      }
-      grep(
-        { $_->is_valid; }
-        @{$self->{contact}->cvars_by_config}
-      )
-    )
-  };
+  $data->{contact_cvars} = $self->_prepare_cvar_configs_for_ajaj($self->{contact}->cvars_by_config);
 
   $self->render(\SL::JSON::to_json($data), { type => 'json', process => 0 });
 }
 
-sub action_ajaj_customer_autocomplete {
+sub action_ajaj_autocomplete {
   my ($self, %params) = @_;
 
-  my $limit = $::form->{limit} || 20;
-  my $type  = $::form->{type}  || {};
-  my $query = { ilike => '%'. $::form->{term} .'%' };
-
-  my @filter;
-  push(
-    @filter,
-    $::form->{column} ? ($::form->{column} => $query) : (or => [ customernumber => $query, name => $query ])
-  );
+  my ($model, $manager, $number, $matches);
+
+  # first see if this is customer or vendor picking
+  if ($::form->{type} eq 'customer') {
+     $model   = $self->customer_models;
+     $manager = 'SL::DB::Manager::Customer';
+     $number  = 'customernumber';
+  } elsif ($::form->{type} eq 'vendor')  {
+     $model   = $self->vendor_models;
+     $manager = 'SL::DB::Manager::Vendor';
+     $number  = 'vendornumber';
+  } else {
+     die "unknown type $::form->{type}";
+  }
 
-  push @filter, (or => [ obsolete => undef, obsolete => 0 ]) if !$::form->{obsolete};
+  # if someone types something, and hits enter, assume he entered the full name.
+  # if something matches, treat that as sole match
+  # unfortunately get_models can't do more than one per package atm, so we d it
+  # the oldfashioned way.
+  if ($::form->{prefer_exact}) {
+    my $exact_matches;
+    if (1 == scalar @{ $exact_matches = $manager->get_all(
+      query => [
+        obsolete => 0,
+        (salesman_id => SL::DB::Manager::Employee->current->id) x !$::auth->assert('customer_vendor_all_edit', 1),
+        or => [
+          name    => { ilike => $::form->{filter}{'all:substr:multi::ilike'} },
+          $number => { ilike => $::form->{filter}{'all:substr:multi::ilike'} },
+        ]
+      ],
+      limit => 2,
+    ) }) {
+      $matches = $exact_matches;
+    }
+  }
 
-  my $customers = SL::DB::Manager::Customer->get_all(query => [ @filter ], limit => $limit);
-  my $value_col = $::form->{column} || 'name';
+  $matches //= $model->get;
+
+  my @hashes = map {
+   +{
+     value       => $_->displayable_name,
+     label       => $_->displayable_name,
+     id          => $_->id,
+     $number     => $_->$number,
+     name        => $_->name,
+     type        => $::form->{type},
+     cvars       => { map { ($_->config->name => { value => $_->value_as_text, is_valid => $_->is_valid }) } @{ $_->cvars_by_config } },
+    }
+  } @{ $matches };
 
-  my $data = [
-    map(
-      {
-        {
-          value => $_->can($value_col)->($_),
-          label => $_->displayable_name,
-          id    => $_->id,
-          customernumber => $_->customernumber,
-          name  => $_->name,
-        }
-      }
-      @{$customers}
-    )
-  ];
+  $self->render(\ SL::JSON::to_json(\@hashes), { layout => 0, type => 'json', process => 0 });
+}
 
-  $self->render(\SL::JSON::to_json($data), { layout => 0, type => 'json' });
+sub action_test_page {
+  $_[0]->render('customer_vendor/test_page');
 }
 
 sub is_vendor {
@@ -590,7 +669,7 @@ sub is_orphaned {
   }
 
   my $arap      = $self->is_vendor ? 'ap' : 'ar';
-  my $num_args  = 2;
+  my $num_args  = 3;
 
   my $cv = $self->is_vendor ? 'vendor' : 'customer';
 
@@ -605,6 +684,13 @@ sub is_orphaned {
     SELECT a.id
     FROM oe a
     JOIN '. $cv .' ct ON (a.'. $cv .'_id = ct.id)
+    WHERE ct.id = ?
+
+    UNION
+
+    SELECT a.id
+    FROM delivery_orders a
+    JOIN '. $cv .' ct ON (a.'. $cv .'_id = ct.id)
     WHERE ct.id = ?';
 
 
@@ -620,6 +706,17 @@ sub is_orphaned {
   return $self->{_is_orphaned} = !$dummy;
 }
 
+sub _copy_form_to_cvars {
+  my ($self, %params) = @_;
+
+  foreach my $cvar (@{ $params{target}->cvars_by_config }) {
+    my $value = $params{source}->{$cvar->config->name};
+    $value    = $::form->parse_amount(\%::myconfig, $value) if $cvar->config->type eq 'number';
+
+    $cvar->value($value);
+  }
+}
+
 sub _instantiate_args {
   my ($self) = @_;
 
@@ -632,11 +729,7 @@ sub _instantiate_args {
       $self->{cv} = SL::DB::Customer->new(id => $::form->{cv}->{id})->load();
     }
   } else {
-    if ( $self->is_vendor() ) {
-      $self->{cv} = SL::DB::Vendor->new();
-    } else {
-      $self->{cv} = SL::DB::Customer->new();
-    }
+    $self->{cv} = $self->_new_customer_vendor_object;
   }
   $self->{cv}->assign_attributes(%{$::form->{cv}});
 
@@ -646,16 +739,6 @@ sub _instantiate_args {
 
   $self->{cv}->hourly_rate($::instance_conf->get_customer_hourly_rate) if $self->is_customer && !$self->{cv}->hourly_rate;
 
-  foreach my $cvar (@{$self->{cv}->cvars_by_config()}) {
-    my $value = $::form->{cv_cvars}->{$cvar->config->name};
-
-    if ( $cvar->config->type eq 'number' ) {
-      $value = $::form->parse_amount(\%::myconfig, $value);
-    }
-
-    $cvar->value($value);
-  }
-
   if ( $::form->{note}->{id} ) {
     $self->{note} = SL::DB::Note->new(id => $::form->{note}->{id})->load();
     $self->{note_followup} = $self->{note}->follow_up;
@@ -688,19 +771,13 @@ sub _instantiate_args {
   if ( $::form->{contact}->{cp_id} ) {
     $self->{contact} = SL::DB::Contact->new(cp_id => $::form->{contact}->{cp_id})->load();
   } else {
-    $self->{contact} = SL::DB::Contact->new();
+    $self->{contact} = $self->_new_contact_object;
   }
   $self->{contact}->assign_attributes(%{$::form->{contact}});
 
-  foreach my $cvar (@{$self->{contact}->cvars_by_config()}) {
-    my $value = $::form->{contact_cvars}->{$cvar->config->name};
-
-    if ( $cvar->config->type eq 'number' ) {
-      $value = $::form->parse_amount(\%::myconfig, $value);
-    }
-
-    $cvar->value($value);
-  }
+  $self->_copy_form_to_cvars(target => $self->{cv},      source => $::form->{cv_cvars});
+  $self->_copy_form_to_cvars(target => $self->{contact}, source => $::form->{contact_cvars});
+  $self->_copy_form_to_cvars(target => $self->{shipto},  source => $::form->{shipto_cvars});
 }
 
 sub _load_customer_vendor {
@@ -739,18 +816,24 @@ sub _load_customer_vendor {
       die($::locale->text('Error'));
     }
   } else {
-    $self->{contact} = SL::DB::Contact->new();
+    $self->{contact} = $self->_new_contact_object;
   }
 }
 
+sub _check_customer_vendor_all_edit {
+  my ($self) = @_;
+
+  unless ($::auth->assert('customer_vendor_all_edit', 1)) {
+    die($::locale->text("You don't have the rights to edit this customer.") . "\n")
+      if $self->{cv}->is_customer and
+         SL::DB::Manager::Employee->current->id != $self->{cv}->salesman_id;
+  };
+};
+
 sub _create_customer_vendor {
   my ($self) = @_;
 
-  if ( $self->is_vendor() ) {
-    $self->{cv} = SL::DB::Vendor->new();
-  } else {
-    $self->{cv} = SL::DB::Customer->new();
-  }
+  $self->{cv} = $self->_new_customer_vendor_object;
   $self->{cv}->currency_id($::instance_conf->get_currency_id());
 
   $self->{note} = SL::DB::Note->new();
@@ -759,7 +842,7 @@ sub _create_customer_vendor {
 
   $self->{shipto} = SL::DB::Shipto->new();
 
-  $self->{contact} = SL::DB::Contact->new();
+  $self->{contact} = $self->_new_contact_object;
 }
 
 sub _pre_render {
@@ -805,7 +888,7 @@ sub _pre_render {
 
   $self->{all_languages} = SL::DB::Manager::Language->get_all();
 
-  $self->{all_taxzones} = SL::DB::Manager::TaxZone->get_all();
+  $self->{all_taxzones} = SL::DB::Manager::TaxZone->get_all_sorted();
 
   if ( $::instance_conf->get_vertreter() ) {
     $query =
@@ -828,11 +911,14 @@ sub _pre_render {
     $self->{all_salesmen} = SL::DB::Manager::Employee->get_all(query => [ or => [ id => $self->{cv}->salesman_id,  deleted => 0 ] ]);
   }
 
-  $self->{all_payment_terms} = SL::DB::Manager::PaymentTerm->get_all();
+  $self->{all_payment_terms} = SL::DB::Manager::PaymentTerm->get_all_sorted(where => [ or => [ id       => $self->{cv}->payment_id,
+                                                                                               obsolete => 0 ] ]);
 
   $self->{all_delivery_terms} = SL::DB::Manager::DeliveryTerm->get_all();
 
-  $self->{all_pricegroups} = SL::DB::Manager::Pricegroup->get_all();
+  if ($self->{cv}->is_customer) {
+    $self->{all_pricegroups} = SL::DB::Manager::Pricegroup->get_all_sorted(query => [ or => [ id => $self->{cv}->pricegroup_id, obsolete => 0 ] ]);
+  }
 
   $query =
     'SELECT DISTINCT(cp_abteilung) AS department
@@ -859,11 +945,124 @@ sub _pre_render {
     ],
     with_objects => ['follow_up'],
   );
-
+  
+  $self->{open_items} = SL::DB::Manager::Invoice->get_all_count(
+    query => [
+      customer_id => $self->{cv}->id,
+      paid => {lt_sql => 'amount'},      
+    ],
+  );
+  
   $self->{template_args} ||= {};
 
-  $::request->{layout}->add_javascripts('autocomplete_customer.js');
   $::request->{layout}->add_javascripts('kivi.CustomerVendor.js');
+  $::request->{layout}->add_javascripts('kivi.File.js');
+
+  $self->_setup_form_action_bar;
+}
+
+sub _setup_form_action_bar {
+  my ($self) = @_;
+
+  for my $bar ($::request->layout->get('actionbar')) {
+    $bar->add(
+      combobox => [
+        action => [
+          t8('Save'),
+          submit    => [ '#form', { action => "CustomerVendor/save" } ],
+          checks    => [ 'check_taxzone_and_ustid' ],
+          accesskey => 'enter',
+        ],
+        action => [
+          t8('Save and Close'),
+          submit => [ '#form', { action => "CustomerVendor/save_and_close" } ],
+          checks => [ 'check_taxzone_and_ustid' ],
+        ],
+      ], # end of combobox "Save"
+
+      combobox => [
+        action => [ t8('Workflow') ],
+        (action => [
+          t8('Save and AP Transaction'),
+          submit => [ '#form', { action => "CustomerVendor/save_and_ap_transaction" } ],
+          checks => [ 'check_taxzone_and_ustid' ],
+        ]) x !!$self->is_vendor,
+        (action => [
+          t8('Save and AR Transaction'),
+          submit => [ '#form', { action => "CustomerVendor/save_and_ar_transaction" } ],
+          checks => [ 'check_taxzone_and_ustid' ],
+        ]) x !$self->is_vendor,
+        action => [
+          t8('Save and Invoice'),
+          submit => [ '#form', { action => "CustomerVendor/save_and_invoice" } ],
+          checks => [ 'check_taxzone_and_ustid' ],
+        ],
+        action => [
+          t8('Save and Order'),
+          submit => [ '#form', { action => "CustomerVendor/save_and_order" } ],
+          checks => [ 'check_taxzone_and_ustid' ],
+        ],
+        (action => [
+          t8('Save and RFQ'),
+          submit => [ '#form', { action => "CustomerVendor/save_and_rfq" } ],
+          checks => [ 'check_taxzone_and_ustid' ],
+        ]) x !!$self->is_vendor,
+        (action => [
+          t8('Save and Quotation'),
+          submit => [ '#form', { action => "CustomerVendor/save_and_quotation" } ],
+          checks => [ 'check_taxzone_and_ustid' ],
+        ]) x !$self->is_vendor,
+      ], # end of combobox "Workflow"
+
+      action => [
+        t8('Delete'),
+        submit   => [ '#form', { action => "CustomerVendor/delete" } ],
+        confirm  => t8('Do you really want to delete this object?'),
+        disabled => !$self->{cv}->id    ? t8('This object has not been saved yet.')
+                  : !$self->is_orphaned ? t8('This object has already been used.')
+                  :                       undef,
+      ],
+
+      'separator',
+
+      action => [
+        t8('History'),
+        call     => [ 'kivi.CustomerVendor.showHistoryWindow', $self->{cv}->id ],
+        disabled => !$self->{cv}->id ? t8('This object has not been saved yet.') : undef,
+      ],
+    );
+  }
+}
+
+sub _prepare_cvar_configs_for_ajaj {
+  my ($self, $cvars) = @_;
+
+  return {
+    map {
+      my $cvar   = $_;
+      my $result = { type => $cvar->config->type };
+
+      if ($cvar->config->type eq 'number') {
+        $result->{value} = $::form->format_amount(\%::myconfig, $cvar->value, -2);
+
+      } elsif ($result->{type} eq 'date') {
+        $result->{value} = $cvar->value ? $cvar->value->to_kivitendo : undef;
+
+      } elsif ($result->{type} =~ m{customer|vendor|part}) {
+        my $object       = $cvar->value;
+        my $method       = $result->{type} eq 'part' ? 'description' : 'name';
+
+        $result->{id}    = int($cvar->number_value) || undef;
+        $result->{value} = $object ? $object->$method // '' : '';
+
+      } else {
+        $result->{value} = $cvar->value;
+      }
+
+      ( $cvar->config->name => $result )
+
+    } grep { $_->is_valid } @{ $cvars }
+  };
 }
 
 sub normalize_name {
@@ -880,4 +1079,67 @@ sub normalize_name {
   $self->{cv}->name($name);
 }
 
+sub home_address_for_google_maps {
+  my ($self)  = @_;
+
+  my $address = $::instance_conf->get_address // '';
+  $address    =~ s{^\s+|\s+$|\r+}{}g;
+  $address    =~ s{\n+}{,}g;
+  $address    =~ s{\s+}{ }g;
+
+  return $address;
+}
+
+sub init_customer_models {
+  my ($self) = @_;
+
+  SL::Controller::Helper::GetModels->new(
+    controller   => $self,
+    model        => 'Customer',
+    sorted => {
+      _default  => {
+        by => 'customernumber',
+        dir  => 1,
+      },
+      customernumber => t8('Customer Number'),
+    },
+    query => [
+     ( salesman_id => SL::DB::Manager::Employee->current->id) x !$::auth->assert('customer_vendor_all_edit', 1),
+    ],
+  );
+}
+
+sub init_vendor_models {
+  my ($self) = @_;
+
+  SL::Controller::Helper::GetModels->new(
+    controller => $self,
+    model      => 'Vendor',
+    sorted => {
+      _default  => {
+        by => 'vendornumber',
+        dir  => 1,
+      },
+      vendornumber => t8('Vendor Number'),
+    },
+  );
+}
+
+sub _new_customer_vendor_object {
+  my ($self) = @_;
+
+  my $class  = 'SL::DB::' . ($self->is_vendor ? 'Vendor' : 'Customer');
+  return $class->new(
+    contacts         => [],
+    shipto           => [],
+    custom_variables => [],
+  );
+}
+
+sub _new_contact_object {
+  my ($self) = @_;
+
+  return SL::DB::Contact->new(custom_variables => []);
+}
+
 1;