use Carp;
use Data::Dumper;
+use List::Util qw(sum);
use SL::DB::MetaSetup::PurchaseInvoice;
use SL::DB::Manager::PurchaseInvoice;
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;
__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
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;
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) = @_;
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) = @_;
# 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;
$self->add_transactions( $acc );
push( @$acc_trans, $acc );
return $acc_trans;
-};
+}
sub add_ap_amount_row {
my ($self, %params ) = @_;
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(
);
$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) = @_;
return $self->tax_point || $self->deliverydate || $self->transdate;
}
+sub netamount_base_currency {
+ my ($self) = @_;
+
+ return $self->netamount; # already matches base currency
+}
+
1;
=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>.