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;
 
  18 use SL::DB::AccTransaction;
 
  22 __PACKAGE__->meta->add_relationship(
 
  24     type         => 'one to many',
 
  25     class        => 'SL::DB::InvoiceItem',
 
  26     column_map   => { id => 'trans_id' },
 
  28       with_objects => [ 'part' ]
 
  33 __PACKAGE__->meta->initialize;
 
  37 sub items { goto &invoiceitems; }
 
  40   # For compatibility with Order, DeliveryOrder
 
  41   croak 'not an accessor' if @_ > 1;
 
  45 # it is assumed, that ordnumbers are unique here.
 
  46 sub first_order_by_ordnumber {
 
  49   my $orders = SL::DB::Manager::Order->get_all(
 
  51       ordnumber => $self->ordnumber,
 
  56   return first { $_->is_type('sales_order') } @{ $orders };
 
  59 sub abschlag_percentage {
 
  61   my $order        = $self->first_order_by_ordnumber or return;
 
  62   my $order_amount = $order->netamount               or return;
 
  63   return $self->abschlag
 
  64     ? $self->netamount / $order_amount
 
  70   die 'not a setter method' if @_;
 
  72   return ($self->amount || 0) - ($self->netamount || 0);
 
  75 __PACKAGE__->meta->make_attr_helpers(taxamount => 'numeric(15,5)');
 
  79   return $self->paid >= $self->amount;
 
  83   my ($class, $source, %params) = @_;
 
  85   croak("Unsupported source object type '" . ref($source) . "'") unless ref($source) =~ m/^ SL::DB:: (?: Order | DeliveryOrder ) $/x;
 
  86   croak("Cannot create invoices for purchase records")           unless $source->customer_id;
 
  88   my $terms = $source->can('payment_id') && $source->payment_id ? $source->payment_term->terms_netto : 0;
 
  90   my %args = ( map({ ( $_ => $source->$_ ) } qw(customer_id taxincluded shippingpoint shipvia notes intnotes curr salesman_id cusordnumber ordnumber quonumber
 
  91                                                 department_id cp_id language_id payment_id delivery_customer_id delivery_vendor_id taxzone_id shipto_id
 
  92                                                 globalproject_id transaction_description)),
 
  93                transdate   => DateTime->today_local,
 
  94                gldate      => DateTime->today_local,
 
  95                duedate     => DateTime->today_local->add(days => $terms * 1),
 
 100                employee_id => (SL::DB::Manager::Employee->current || SL::DB::Employee->new(id => $source->employee_id))->id,
 
 103   if ($source->type =~ /_order$/) {
 
 104     $args{deliverydate} = $source->reqdate;
 
 105     $args{orddate}      = $source->transdate;
 
 107     $args{quodate}      = $source->transdate;
 
 110   my $invoice = $class->new(%args, %params);
 
 113     my $source_item = $_;
 
 114     SL::DB::InvoiceItem->new(map({ ( $_ => $source_item->$_ ) }
 
 115                                  qw(parts_id description qty sellprice discount project_id
 
 116                                     serialnumber pricegroup_id ordnumber transdate cusordnumber unit
 
 117                                     base_qty subtotal longdescription lastcost price_factor_id)),
 
 118                             deliverydate => $source_item->reqdate,
 
 119                             fxsellprice  => $source_item->sellprice,);
 
 120   } @{ $source->items };
 
 122   $invoice->invoiceitems(\@items);
 
 128   my ($self, %params) = @_;
 
 130   if (!$params{ar_id}) {
 
 131     my $chart = SL::DB::Manager::Chart->get_all(query   => [ SL::DB::Manager::Chart->link_filter('AR') ],
 
 134     croak("No AR chart found and no parameter `ar_id' given") unless $chart;
 
 135     $params{ar_id} = $chart->id;
 
 139     my %data = $self->calculate_prices_and_taxes;
 
 141     $self->_post_create_assemblyitem_entries($data{assembly_items});
 
 142     $self->create_trans_number;
 
 145     $self->_post_add_acctrans($data{amounts_cogs});
 
 146     $self->_post_add_acctrans($data{amounts});
 
 147     $self->_post_add_acctrans($data{taxes});
 
 149     $self->_post_add_acctrans({ $params{ar_id} => $self->amount * -1 });
 
 151     $self->_post_update_allocated($data{allocated});
 
 154   if ($self->db->in_transaction) {
 
 156   } elsif (!$self->db->do_transaction($worker)) {
 
 157     $::lxdebug->message(LXDebug->WARN(), "convert_to_invoice failed: " . join("\n", (split(/\n/, $self->db->error))[0..2]));
 
 164 sub _post_add_acctrans {
 
 165   my ($self, $entries) = @_;
 
 167   while (my ($chart_id, $spec) = each %{ $entries }) {
 
 168     $spec = { taxkey => 0, amount => $spec } unless ref $spec;
 
 169     SL::DB::AccTransaction->new(trans_id   => $self->id,
 
 170                                 chart_id   => $chart_id,
 
 171                                 amount     => $spec->{amount},
 
 172                                 taxkey     => $spec->{taxkey},
 
 173                                 project_id => $self->globalproject_id,
 
 174                                 transdate  => $self->transdate)->save;
 
 178 sub _post_create_assemblyitem_entries {
 
 179   my ($self, $assembly_entries) = @_;
 
 181   my $items = $self->invoiceitems;
 
 185   foreach my $item (@{ $items }) {
 
 186     next if $item->assemblyitem;
 
 188     push @new_items, $item;
 
 191     foreach my $assembly_item (@{ $assembly_entries->[$item_idx] || [ ] }) {
 
 192       push @new_items, SL::DB::InvoiceItem->new(parts_id     => $assembly_item->{part},
 
 193                                                 description  => $assembly_item->{part}->description,
 
 194                                                 unit         => $assembly_item->{part}->unit,
 
 195                                                 qty          => $assembly_item->{qty},
 
 196                                                 allocated    => $assembly_item->{allocated},
 
 199                                                 assemblyitem => 't');
 
 203   $self->invoiceitems(\@new_items);
 
 206 sub _post_update_allocated {
 
 207   my ($self, $allocated) = @_;
 
 209   while (my ($invoice_id, $diff) = each %{ $allocated }) {
 
 210     SL::DB::Manager::InvoiceItem->update_all(set   => { allocated => { sql => "allocated + $diff" } },
 
 211                                              where => [ id        => $invoice_id ]);
 
 218   return 'ar_transaction'     if !$self->invoice;
 
 219   return 'credit_note'        if $self->type eq 'credit_note' && $self->amount < 0 && !$self->storno;
 
 220   return 'invoice_storno'     if $self->type ne 'credit_note' && $self->amount < 0 &&  $self->storno;
 
 221   return 'credit_note_storno' if $self->type eq 'credit_note' && $self->amount > 0 &&  $self->storno;
 
 225 sub displayable_state {
 
 228   return $self->closed ? $::locale->text('closed') : $::locale->text('open');
 
 239 SL::DB::Invoice: Rose model for invoices (table "ar")
 
 245 =item C<new_from $source>
 
 247 Creates a new C<SL::DB::Invoice> instance and copies as much
 
 248 information from C<$source> as possible. At the moment only sales
 
 249 orders and sales quotations are supported as sources.
 
 251 The conversion copies order items into invoice items. Dates are copied
 
 252 as appropriate, e.g. the C<transdate> field from an order will be
 
 253 copied into the invoice's C<orddate> field.
 
 255 Amounts, prices and taxes are not
 
 256 calculated. L<SL::DB::Helper::PriceTaxCalculator::calculate_prices_and_taxes>
 
 257 can be used for this.
 
 259 The object returned is not saved.
 
 261 =item C<post %params>
 
 263 Posts the invoice. Required parameters are:
 
 269 The ID of the accounds receivable chart the invoices amounts are
 
 270 posted to. If it is not set then the first chart configured for
 
 271 accounts receivables is used.
 
 275 This function implements several steps:
 
 279 =item 1. It calculates all prices, amounts and taxes by calling
 
 280 L<SL::DB::Helper::PriceTaxCalculator::calculate_prices_and_taxes>.
 
 282 =item 2. A new and unique invoice number is created.
 
 284 =item 3. All amounts for costs of goods sold are recorded in
 
 287 =item 4. All amounts for parts, services and assemblies are recorded
 
 288 in C<acc_trans> with their respective charts. This is determined by
 
 289 the part's buchungsgruppen.
 
 291 =item 5. The total amount is posted to the accounts receivable chart
 
 292 and recorded in C<acc_trans>.
 
 294 =item 6. Items in C<invoice> are updated according to their allocation
 
 295 status (regarding for costs of goold sold). Will only be done if
 
 296 kivitendo is not configured to use Einnahmenüberschussrechnungen.
 
 298 =item 7. The invoice and its items are saved.
 
 302 Returns C<$self> on success and C<undef> on failure. The whole process
 
 303 is run inside a transaction. If it fails then nothing is saved to or
 
 304 changed in the database. A new transaction is only started if none is
 
 307 =item C<basic_info $field>
 
 309 See L<SL::DB::Object::basic_info>.
 
 315 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>