]> wagnertech.de Git - mfinanz.git/blobdiff - SL/DB/PurchaseInvoice.pm
kivitendo 3.9.2-0.2
[mfinanz.git] / SL / DB / PurchaseInvoice.pm
index 3eb320ee3325f3eb52fcd470b70e355846072011..ca4bf5960d5f408d432f066df4985b60b1897883 100644 (file)
@@ -4,6 +4,7 @@ use strict;
 
 use Carp;
 use Data::Dumper;
+use List::Util qw(sum);
 
 use SL::DB::MetaSetup::PurchaseInvoice;
 use SL::DB::Manager::PurchaseInvoice;
@@ -11,9 +12,11 @@ use SL::DB::Helper::AttrHTML;
 use SL::DB::Helper::AttrSorted;
 use SL::DB::Helper::LinkedRecords;
 use SL::DB::Helper::Payment qw(:ALL);
+use SL::DB::Helper::RecordLink qw(RECORD_ID RECORD_TYPE_REF RECORD_ITEM_ID RECORD_ITEM_TYPE_REF);
 use SL::DB::Helper::SalesPurchaseInvoice;
+use SL::DB::Helper::ZUGFeRD qw(:IMPORT);
 use SL::Locale::String qw(t8);
-use Rose::DB::Object::Helpers qw(has_loaded_related forget_related);
+use Rose::DB::Object::Helpers qw(has_loaded_related forget_related as_tree strip);
 
 # The calculator hasn't been adjusted for purchase invoices yet.
 # use SL::DB::Helper::PriceTaxCalculator;
@@ -57,9 +60,30 @@ __PACKAGE__->meta->initialize;
 __PACKAGE__->attr_html('notes');
 __PACKAGE__->attr_sorted('items');
 
+__PACKAGE__->after_save('_after_save_link_records');
+
+# hooks
+
+sub _after_save_link_records {
+  my ($self) = @_;
+
+  my @allowed_record_sources = qw(SL::DB::Reclamation SL::DB::Order);
+  my @allowed_item_sources = qw(SL::DB::ReclamationItem SL::DB::OrderItem);
+
+  SL::DB::Helper::RecordLink::link_records(
+    $self,
+    \@allowed_record_sources,
+    \@allowed_item_sources,
+    close_source_quotations => 1,
+  );
+}
+
+# methods
+
 sub items { goto &invoiceitems; }
 sub add_items { goto &add_invoiceitems; }
-sub record_number { goto &invnumber; };
+sub record_number { goto &invnumber; }
+sub record_type { goto &invoice_type; }
 
 sub is_sales {
   # For compatibility with Order, DeliveryOrder
@@ -87,7 +111,7 @@ sub abbreviation {
   return t8('Invoice (one letter abbreviation)'). '(' . t8('Storno (one letter abbreviation)') . ')' if $self->storno;
   return t8('Invoice (one letter abbreviation)');
 
-};
+}
 
 sub oneline_summary {
   my $self = shift;
@@ -109,9 +133,15 @@ sub link {
 sub invoice_type {
   my ($self) = @_;
 
-  return 'ap_transaction' if !$self->invoice;
+  return 'purchase_credit_note'  if  $self->amount < 0;
+  return 'ap_transaction'        if !$self->invoice;
   return 'purchase_invoice';
 }
+sub is_credit_note {
+  my ($self) = @_;
+
+  return $self->invoice_type eq 'purchase_credit_note' ? 1 : undef;
+}
 
 sub displayable_type {
   my ($self) = @_;
@@ -122,7 +152,26 @@ sub displayable_type {
 
 sub displayable_name {
   join ' ', grep $_, map $_[0]->$_, qw(displayable_type record_number);
-};
+}
+
+sub convert_to_reclamation {
+  my ($self, %params) = @_;
+  $params{destination_type} = $self->is_sales ? 'sales_reclamation'
+                                              : 'purchase_reclamation';
+
+  require SL::DB::Reclamation;
+  my $reclamation = SL::DB::Reclamation->new_from($self, %params);
+
+  return $reclamation;
+}
+
+sub create_from_zugferd_data {
+  my ($class, $data) = @_;
+
+  my $ap_invoice = $class->new();
+
+  $ap_invoice->import_zugferd_data($data);
+}
 
 sub create_ap_row {
   my ($self, %params) = @_;
@@ -132,6 +181,8 @@ sub create_ap_row {
   # only allow this method for ap invoices (Kreditorenbuchung)
   die if $self->invoice and not $self->vendor_id;
 
+  return 0 unless scalar @{$self->transactions} > 0;
+
   my $acc_trans = [];
   my $chart = $params{chart} || SL::DB::Manager::Chart->find_by(id => $::instance_conf->get_ap_chart_id);
   die "illegal chart in create_ap_row" unless $chart;
@@ -153,7 +204,7 @@ sub create_ap_row {
   $self->add_transactions( $acc );
   push( @$acc_trans, $acc );
   return $acc_trans;
-};
+}
 
 sub add_ap_amount_row {
   my ($self, %params ) = @_;
@@ -174,8 +225,9 @@ sub add_ap_amount_row {
 
   if ( $tax and $tax->rate != 0 ) {
     ($netamount, $taxamount) = Form->calculate_tax($params{amount}, $tax->rate, $self->taxincluded, $roundplaces);
-  };
-  next unless $netamount; # netamount mustn't be zero
+  }
+
+  return unless $netamount; # netamount mustn't be zero
 
   my $sign = $self->vendor_id ? -1 : 1;
   my $acc = SL::DB::AccTransaction->new(
@@ -205,9 +257,80 @@ sub add_ap_amount_row {
      );
      $self->add_transactions( $acc );
      push( @$acc_trans, $acc );
-  };
+  }
   return $acc_trans;
-};
+}
+
+sub validate_acc_trans {
+  my ($self, %params) = @_;
+  # should be able to check unsaved invoice objects with several acc_trans lines
+
+  die "validate_acc_trans can't check invoice object with empty transactions" unless $self->transactions;
+
+  my @transactions = @{$self->transactions};
+  # die "invoice has no acc_transactions" unless scalar @transactions > 0;
+
+  return 0 unless scalar @transactions > 0;
+  return 0 unless $self->has_loaded_related('transactions');
+
+  $::lxdebug->message(LXDebug->DEBUG1(), sprintf("starting validatation of purchase invoice %s with trans_id %s and taxincluded %s\n", $self->invnumber // '', $self->id // '', $self->taxincluded // ''));
+  foreach my $acc ( @transactions ) {
+    $::lxdebug->message(LXDebug->DEBUG1(), sprintf("chart: %s  amount: %s   tax_id: %s  link: %s\n", $acc->chart->accno, $acc->amount, $acc->tax_id, $acc->chart->link));
+  }
+
+  my $acc_trans_sum = sum map { $_->amount } @transactions;
+
+  unless ( $::form->round_amount($acc_trans_sum, 10) == 0 ) {
+    my $string = "sum of acc_transactions isn't 0: $acc_trans_sum\n";
+
+    foreach my $trans ( @transactions ) {
+      $string .= sprintf("  %s %s %s\n", $trans->chart->accno, $trans->taxkey, $trans->amount);
+    }
+    $::lxdebug->message(LXDebug->DEBUG1(), $string);
+    return 0;
+  }
+
+  # only use the first AP entry, so it also works for paid invoices
+  my @ap_transactions = map { $_->amount } grep { $_->chart_link eq 'AP' } @transactions;
+  my $ap_sum = $ap_transactions[0];
+  # my $ap_sum = sum map { $_->amount } grep { $_->chart_link eq 'AP' } @transactions;
+
+  my $sign = $self->vendor_id ? 1 : -1;
+
+  unless ( $::form->round_amount($ap_sum * $sign, 2) == $::form->round_amount($self->amount, 2) ) {
+
+    $::lxdebug->message(LXDebug->DEBUG1(), sprintf("debug: (ap_sum) %s = %s (amount)\n",  $::form->round_amount($ap_sum * $sign, 2) , $::form->round_amount($self->amount, 2) ) );
+    foreach my $trans ( @transactions ) {
+      $::lxdebug->message(LXDebug->DEBUG1(), sprintf("  %s %s %s %s\n", $trans->chart->accno, $trans->taxkey, $trans->amount, $trans->chart->link));
+    }
+
+    die sprintf("sum of ap (%s) isn't equal to invoice amount (%s)", $::form->round_amount($ap_sum * $sign, 2), $::form->round_amount($self->amount, 2));
+  }
+
+  return 1;
+}
+
+sub recalculate_amounts {
+  my ($self, %params) = @_;
+  # calculate and set amount and netamount from acc_trans objects
+
+  croak ("Can only recalculate amounts for ap transactions") if $self->invoice;
+
+  return undef unless $self->has_loaded_related('transactions');
+
+  my ($netamount, $taxamount);
+
+  my @transactions = @{$self->transactions};
+
+  foreach my $acc ( @transactions ) {
+    $netamount += $acc->amount if $acc->chart->link =~ /AP_amount/;
+    $taxamount += $acc->amount if $acc->chart->link =~ /AP_tax/;
+  }
+
+  my $sign = $self->vendor_id ? -1 : 1;
+  $self->amount   (($netamount + $taxamount) * $sign);
+  $self->netamount(($netamount)              * $sign);
+}
 
 sub mark_as_paid {
   my ($self) = @_;
@@ -221,6 +344,12 @@ sub effective_tax_point {
   return $self->tax_point || $self->deliverydate || $self->transdate;
 }
 
+sub netamount_base_currency {
+  my ($self) = @_;
+
+  return $self->netamount; # already matches base currency
+}
+
 1;
 
 
@@ -238,6 +367,17 @@ SL::DB::PurchaseInvoice: Rose model for purchase invoices (table "ap")
 
 =over 4
 
+=item C<create_ap_row>
+
+=item C<add_ap_amount_row>
+
+=item C<validate_acc_trans>
+
+=item C<recalculate_amounts>
+
+These functions are similar to the ones in the C<SL::DB::Invoice> module. See
+there for more information.
+
 =item C<mark_as_paid>
 
 Marks the invoice as paid by setting its C<paid> member to the value of C<amount>.