1 # This file has been auto-generated only because it didn't exist.
 
   2 # Feel free to modify it at will; it will not be overwritten automatically.
 
   4 package SL::DB::Invoice;
 
   9 use List::Util qw(first);
 
  11 use SL::DB::MetaSetup::Invoice;
 
  12 use SL::DB::Manager::Invoice;
 
  13 use SL::DB::Helper::FlattenToForm;
 
  14 use SL::DB::Helper::LinkedRecords;
 
  15 use SL::DB::Helper::PriceTaxCalculator;
 
  16 use SL::DB::Helper::PriceUpdater;
 
  17 use SL::DB::Helper::TransNumberGenerator;
 
  19 __PACKAGE__->meta->add_relationship(
 
  21     type         => 'one to many',
 
  22     class        => 'SL::DB::InvoiceItem',
 
  23     column_map   => { id => 'trans_id' },
 
  25       with_objects => [ 'part' ]
 
  29     type          => 'one to many',
 
  30     class         => 'SL::DB::Invoice',
 
  31     column_map    => { id => 'storno_id' },
 
  33   sepa_export_items => {
 
  34     type            => 'one to many',
 
  35     class           => 'SL::DB::SepaExportItem',
 
  36     column_map      => { id => 'ar_id' },
 
  37     manager_args    => { with_objects => [ 'sepa_export' ] }
 
  41 __PACKAGE__->meta->initialize;
 
  43 __PACKAGE__->before_save('_before_save_set_invnumber');
 
  47 sub _before_save_set_invnumber {
 
  50   $self->create_trans_number if !$self->invnumber;
 
  57 sub items { goto &invoiceitems; }
 
  62   return [ sort {$a->id <=> $b->id } @{ $self->items } ];
 
  66   # For compatibility with Order, DeliveryOrder
 
  67   croak 'not an accessor' if @_ > 1;
 
  71 # it is assumed, that ordnumbers are unique here.
 
  72 sub first_order_by_ordnumber {
 
  75   my $orders = SL::DB::Manager::Order->get_all(
 
  77       ordnumber => $self->ordnumber,
 
  82   return first { $_->is_type('sales_order') } @{ $orders };
 
  85 sub abschlag_percentage {
 
  87   my $order        = $self->first_order_by_ordnumber or return;
 
  88   my $order_amount = $order->netamount               or return;
 
  89   return $self->abschlag
 
  90     ? $self->netamount / $order_amount
 
  96   die 'not a setter method' if @_;
 
  98   return ($self->amount || 0) - ($self->netamount || 0);
 
 101 __PACKAGE__->meta->make_attr_helpers(taxamount => 'numeric(15,5)');
 
 105   return $self->paid >= $self->amount;
 
 109   my ($class, $source, %params) = @_;
 
 111   croak("Unsupported source object type '" . ref($source) . "'") unless ref($source) =~ m/^ SL::DB:: (?: Order | DeliveryOrder ) $/x;
 
 112   croak("Cannot create invoices for purchase records")           unless $source->customer_id;
 
 114   require SL::DB::Employee;
 
 116   my $terms = $source->can('payment_id') && $source->payment_id ? $source->payment_terms->terms_netto : 0;
 
 118   my %args = ( map({ ( $_ => $source->$_ ) } qw(customer_id taxincluded shippingpoint shipvia notes intnotes salesman_id cusordnumber ordnumber quonumber
 
 119                                                 department_id cp_id language_id payment_id delivery_customer_id delivery_vendor_id taxzone_id shipto_id
 
 120                                                 globalproject_id transaction_description currency_id delivery_term_id)),
 
 121                transdate   => DateTime->today_local,
 
 122                gldate      => DateTime->today_local,
 
 123                duedate     => DateTime->today_local->add(days => $terms * 1),
 
 128                employee_id => (SL::DB::Manager::Employee->current || SL::DB::Employee->new(id => $source->employee_id))->id,
 
 131   if ($source->type =~ /_order$/) {
 
 132     $args{deliverydate} = $source->reqdate;
 
 133     $args{orddate}      = $source->transdate;
 
 135     $args{quodate}      = $source->transdate;
 
 138   my $invoice = $class->new(%args, %params);
 
 141     my $source_item = $_;
 
 142     SL::DB::InvoiceItem->new(map({ ( $_ => $source_item->$_ ) }
 
 143                                  qw(parts_id description qty sellprice discount project_id
 
 144                                     serialnumber pricegroup_id ordnumber transdate cusordnumber unit
 
 145                                     base_qty subtotal longdescription lastcost price_factor_id)),
 
 146                             deliverydate => $source_item->reqdate,
 
 147                             fxsellprice  => $source_item->sellprice,);
 
 148   } @{ $source->items_sorted };
 
 150   $invoice->invoiceitems(\@items);
 
 156   my ($self, %params) = @_;
 
 158   require SL::DB::Chart;
 
 159   if (!$params{ar_id}) {
 
 160     my $chart = SL::DB::Manager::Chart->get_all(query   => [ SL::DB::Manager::Chart->link_filter('AR') ],
 
 163     croak("No AR chart found and no parameter `ar_id' given") unless $chart;
 
 164     $params{ar_id} = $chart->id;
 
 168     my %data = $self->calculate_prices_and_taxes;
 
 170     $self->_post_create_assemblyitem_entries($data{assembly_items});
 
 173     $self->_post_add_acctrans($data{amounts_cogs});
 
 174     $self->_post_add_acctrans($data{amounts});
 
 175     $self->_post_add_acctrans($data{taxes});
 
 177     $self->_post_add_acctrans({ $params{ar_id} => $self->amount * -1 });
 
 179     $self->_post_update_allocated($data{allocated});
 
 182   if ($self->db->in_transaction) {
 
 184   } elsif (!$self->db->do_transaction($worker)) {
 
 185     $::lxdebug->message(LXDebug->WARN(), "convert_to_invoice failed: " . join("\n", (split(/\n/, $self->db->error))[0..2]));
 
 192 sub _post_add_acctrans {
 
 193   my ($self, $entries) = @_;
 
 195   my $default_tax_id = SL::DB::Manager::Tax->find_by(taxkey => 0)->id;
 
 198   require SL::DB::AccTransaction;
 
 199   require SL::DB::Chart;
 
 200   while (my ($chart_id, $spec) = each %{ $entries }) {
 
 201     $spec = { taxkey => 0, tax_id => $default_tax_id, amount => $spec } unless ref $spec;
 
 202     $chart_link = SL::DB::Manager::Chart->find_by(id => $chart_id)->{'link'};
 
 205     SL::DB::AccTransaction->new(trans_id   => $self->id,
 
 206                                 chart_id   => $chart_id,
 
 207                                 amount     => $spec->{amount},
 
 208                                 tax_id     => $spec->{tax_id},
 
 209                                 taxkey     => $spec->{taxkey},
 
 210                                 project_id => $self->globalproject_id,
 
 211                                 transdate  => $self->transdate,
 
 212                                 chart_link => $chart_link)->save;
 
 216 sub _post_create_assemblyitem_entries {
 
 217   my ($self, $assembly_entries) = @_;
 
 219   my $items = $self->invoiceitems;
 
 223   foreach my $item (@{ $items }) {
 
 224     next if $item->assemblyitem;
 
 226     push @new_items, $item;
 
 229     foreach my $assembly_item (@{ $assembly_entries->[$item_idx] || [ ] }) {
 
 230       push @new_items, SL::DB::InvoiceItem->new(parts_id     => $assembly_item->{part},
 
 231                                                 description  => $assembly_item->{part}->description,
 
 232                                                 unit         => $assembly_item->{part}->unit,
 
 233                                                 qty          => $assembly_item->{qty},
 
 234                                                 allocated    => $assembly_item->{allocated},
 
 237                                                 assemblyitem => 't');
 
 241   $self->invoiceitems(\@new_items);
 
 244 sub _post_update_allocated {
 
 245   my ($self, $allocated) = @_;
 
 247   while (my ($invoice_id, $diff) = each %{ $allocated }) {
 
 248     SL::DB::Manager::InvoiceItem->update_all(set   => { allocated => { sql => "allocated + $diff" } },
 
 249                                              where => [ id        => $invoice_id ]);
 
 256   return 'ar_transaction'     if !$self->invoice;
 
 257   return 'credit_note'        if $self->type eq 'credit_note' && $self->amount < 0 && !$self->storno;
 
 258   return 'invoice_storno'     if $self->type ne 'credit_note' && $self->amount < 0 &&  $self->storno;
 
 259   return 'credit_note_storno' if $self->type eq 'credit_note' && $self->amount > 0 &&  $self->storno;
 
 263 sub displayable_state {
 
 266   return $self->closed ? $::locale->text('closed') : $::locale->text('open');
 
 281 SL::DB::Invoice: Rose model for invoices (table "ar")
 
 287 =item C<new_from $source>
 
 289 Creates a new C<SL::DB::Invoice> instance and copies as much
 
 290 information from C<$source> as possible. At the moment only sales
 
 291 orders and sales quotations are supported as sources.
 
 293 The conversion copies order items into invoice items. Dates are copied
 
 294 as appropriate, e.g. the C<transdate> field from an order will be
 
 295 copied into the invoice's C<orddate> field.
 
 297 Amounts, prices and taxes are not
 
 298 calculated. L<SL::DB::Helper::PriceTaxCalculator::calculate_prices_and_taxes>
 
 299 can be used for this.
 
 301 The object returned is not saved.
 
 303 =item C<post %params>
 
 305 Posts the invoice. Required parameters are:
 
 311 The ID of the accounds receivable chart the invoices amounts are
 
 312 posted to. If it is not set then the first chart configured for
 
 313 accounts receivables is used.
 
 317 This function implements several steps:
 
 321 =item 1. It calculates all prices, amounts and taxes by calling
 
 322 L<SL::DB::Helper::PriceTaxCalculator::calculate_prices_and_taxes>.
 
 324 =item 2. A new and unique invoice number is created.
 
 326 =item 3. All amounts for costs of goods sold are recorded in
 
 329 =item 4. All amounts for parts, services and assemblies are recorded
 
 330 in C<acc_trans> with their respective charts. This is determined by
 
 331 the part's buchungsgruppen.
 
 333 =item 5. The total amount is posted to the accounts receivable chart
 
 334 and recorded in C<acc_trans>.
 
 336 =item 6. Items in C<invoice> are updated according to their allocation
 
 337 status (regarding for costs of goold sold). Will only be done if
 
 338 kivitendo is not configured to use Einnahmenüberschussrechnungen.
 
 340 =item 7. The invoice and its items are saved.
 
 344 Returns C<$self> on success and C<undef> on failure. The whole process
 
 345 is run inside a transaction. If it fails then nothing is saved to or
 
 346 changed in the database. A new transaction is only started if none is
 
 349 =item C<basic_info $field>
 
 351 See L<SL::DB::Object::basic_info>.
 
 357 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>