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 => [ 'parts' ]
 
  33 __PACKAGE__->meta->initialize;
 
  37 sub items        { goto &invoiceitems; }
 
  38 sub payment_term { goto &payment;      }
 
  40 # it is assumed, that ordnumbers are unique here.
 
  41 sub first_order_by_ordnumber {
 
  44   my $orders = SL::DB::Manager::Order->get_all(
 
  46       ordnumber => $self->ordnumber,
 
  51   return first { $_->is_type('sales_order') } @{ $orders };
 
  54 sub abschlag_percentage {
 
  56   my $order        = $self->first_order_by_ordnumber or return;
 
  57   my $order_amount = $order->netamount               or return;
 
  58   return $self->abschlag
 
  59     ? $self->netamount / $order_amount
 
  65   die 'not a setter method' if @_;
 
  67   return ($self->amount || 0) - ($self->netamount || 0);
 
  70 __PACKAGE__->meta->make_attr_helpers(taxamount => 'numeric(15,5)');
 
  74   return $self->paid >= $self->amount;
 
  78   my ($class, $source, %params) = @_;
 
  80   croak("Unsupported source object type '" . ref($source) . "'") unless ref($source) =~ m/^ SL::DB:: (?: Order | DeliveryOrder ) $/x;
 
  81   croak("Cannot create invoices for purchase records")           unless $source->customer_id;
 
  83   my $terms = $source->can('payment_id') && $source->payment_id ? $source->payment_term->terms_netto : 0;
 
  85   my %args = ( map({ ( $_ => $source->$_ ) } qw(customer_id taxincluded shippingpoint shipvia notes intnotes curr salesman_id cusordnumber ordnumber quonumber
 
  86                                                 department_id cp_id language_id payment_id delivery_customer_id delivery_vendor_id taxzone_id shipto_id
 
  87                                                 globalproject_id transaction_description)),
 
  88                transdate   => DateTime->today_local,
 
  89                gldate      => DateTime->today_local,
 
  90                duedate     => DateTime->today_local->add(days => $terms * 1),
 
  95                employee_id => (SL::DB::Manager::Employee->current || SL::DB::Employee->new(id => $source->employee_id))->id,
 
  98   if ($source->type =~ /_order$/) {
 
  99     $args{deliverydate} = $source->reqdate;
 
 100     $args{orddate}      = $source->transdate;
 
 102     $args{quodate}      = $source->transdate;
 
 105   my $invoice = $class->new(%args, %params);
 
 108     my $source_item = $_;
 
 109     SL::DB::InvoiceItem->new(map({ ( $_ => $source_item->$_ ) }
 
 110                                  qw(parts_id description qty sellprice discount project_id
 
 111                                     serialnumber pricegroup_id ordnumber transdate cusordnumber unit
 
 112                                     base_qty subtotal longdescription lastcost price_factor_id)),
 
 113                             deliverydate => $source_item->reqdate,
 
 114                             fxsellprice  => $source_item->sellprice,);
 
 115   } @{ $source->items };
 
 117   $invoice->invoiceitems(\@items);
 
 123   my ($self, %params) = @_;
 
 125   if (!$params{ar_id}) {
 
 126     my $chart = SL::DB::Manager::Chart->get_all(query   => [ SL::DB::Manager::Chart->link_filter('AR') ],
 
 129     croak("No AR chart found and no parameter `ar_id' given") unless $chart;
 
 130     $params{ar_id} = $chart->id;
 
 134     my %data = $self->calculate_prices_and_taxes;
 
 136     $self->_post_create_assemblyitem_entries($data{assembly_items});
 
 137     $self->create_trans_number;
 
 140     $self->_post_add_acctrans($data{amounts_cogs});
 
 141     $self->_post_add_acctrans($data{amounts});
 
 142     $self->_post_add_acctrans($data{taxes});
 
 144     $self->_post_add_acctrans({ $params{ar_id} => $self->amount * -1 });
 
 146     $self->_post_update_allocated($data{allocated});
 
 149   if ($self->db->in_transaction) {
 
 151   } elsif (!$self->db->do_transaction($worker)) {
 
 152     $::lxdebug->message(LXDebug->WARN(), "convert_to_invoice failed: " . join("\n", (split(/\n/, $self->db->error))[0..2]));
 
 159 sub _post_add_acctrans {
 
 160   my ($self, $entries) = @_;
 
 162   while (my ($chart_id, $spec) = each %{ $entries }) {
 
 163     $spec = { taxkey => 0, amount => $spec } unless ref $spec;
 
 164     SL::DB::AccTransaction->new(trans_id   => $self->id,
 
 165                                 chart_id   => $chart_id,
 
 166                                 amount     => $spec->{amount},
 
 167                                 taxkey     => $spec->{taxkey},
 
 168                                 project_id => $self->globalproject_id,
 
 169                                 transdate  => $self->transdate)->save;
 
 173 sub _post_create_assemblyitem_entries {
 
 174   my ($self, $assembly_entries) = @_;
 
 176   my $items = $self->invoiceitems;
 
 180   foreach my $item (@{ $items }) {
 
 181     next if $item->assemblyitem;
 
 183     push @new_items, $item;
 
 186     foreach my $assembly_item (@{ $assembly_entries->[$item_idx] || [ ] }) {
 
 187       push @new_items, SL::DB::InvoiceItem->new(parts_id     => $assembly_item->{part},
 
 188                                                 description  => $assembly_item->{part}->description,
 
 189                                                 unit         => $assembly_item->{part}->unit,
 
 190                                                 qty          => $assembly_item->{qty},
 
 191                                                 allocated    => $assembly_item->{allocated},
 
 194                                                 assemblyitem => 't');
 
 198   $self->invoiceitems(\@new_items);
 
 201 sub _post_update_allocated {
 
 202   my ($self, $allocated) = @_;
 
 204   while (my ($invoice_id, $diff) = each %{ $allocated }) {
 
 205     SL::DB::Manager::InvoiceItem->update_all(set   => { allocated => { sql => "allocated + $diff" } },
 
 206                                              where => [ id        => $invoice_id ]);
 
 213   return 'ar_transaction'     if !$self->invoice;
 
 214   return 'credit_note'        if $self->type eq 'credit_note' && $self->amount < 0 && !$self->storno;
 
 215   return 'invoice_storno'     if $self->type ne 'credit_note' && $self->amount < 0 &&  $self->storno;
 
 216   return 'credit_note_storno' if $self->type eq 'credit_note' && $self->amount > 0 &&  $self->storno;
 
 220 sub displayable_state {
 
 223   return $self->closed ? $::locale->text('closed') : $::locale->text('open');
 
 234 SL::DB::Invoice: Rose model for invoices (table "ar")
 
 240 =item C<new_from $source>
 
 242 Creates a new C<SL::DB::Invoice> instance and copies as much
 
 243 information from C<$source> as possible. At the moment only sales
 
 244 orders and sales quotations are supported as sources.
 
 246 The conversion copies order items into invoice items. Dates are copied
 
 247 as appropriate, e.g. the C<transdate> field from an order will be
 
 248 copied into the invoice's C<orddate> field.
 
 250 Amounts, prices and taxes are not
 
 251 calculated. L<SL::DB::Helper::PriceTaxCalculator::calculate_prices_and_taxes>
 
 252 can be used for this.
 
 254 The object returned is not saved.
 
 256 =item C<post %params>
 
 258 Posts the invoice. Required parameters are:
 
 264 The ID of the accounds receivable chart the invoices amounts are
 
 265 posted to. If it is not set then the first chart configured for
 
 266 accounts receivables is used.
 
 270 This function implements several steps:
 
 274 =item 1. It calculates all prices, amounts and taxes by calling
 
 275 L<SL::DB::Helper::PriceTaxCalculator::calculate_prices_and_taxes>.
 
 277 =item 2. A new and unique invoice number is created.
 
 279 =item 3. All amounts for costs of goods sold are recorded in
 
 282 =item 4. All amounts for parts, services and assemblies are recorded
 
 283 in C<acc_trans> with their respective charts. This is determined by
 
 284 the part's buchungsgruppen.
 
 286 =item 5. The total amount is posted to the accounts receivable chart
 
 287 and recorded in C<acc_trans>.
 
 289 =item 6. Items in C<invoice> are updated according to their allocation
 
 290 status (regarding for costs of goold sold). Will only be done if
 
 291 kivitendo is not configured to use Einnahmenüberschussrechnungen.
 
 293 =item 7. The invoice and its items are saved.
 
 297 Returns C<$self> on success and C<undef> on failure. The whole process
 
 298 is run inside a transaction. If it fails then nothing is saved to or
 
 299 changed in the database. A new transaction is only started if none is
 
 302 =item C<basic_info $field>
 
 304 See L<SL::DB::Object::basic_info>.
 
 310 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>