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 Rose::DB::Object::Helpers ();
13 use SL::DB::MetaSetup::Invoice;
14 use SL::DB::Manager::Invoice;
15 use SL::DB::Helper::FlattenToForm;
16 use SL::DB::Helper::LinkedRecords;
17 use SL::DB::Helper::PriceTaxCalculator;
18 use SL::DB::Helper::PriceUpdater;
19 use SL::DB::Helper::TransNumberGenerator;
21 __PACKAGE__->meta->add_relationship(
23 type => 'one to many',
24 class => 'SL::DB::InvoiceItem',
25 column_map => { id => 'trans_id' },
27 with_objects => [ 'part' ]
31 type => 'one to many',
32 class => 'SL::DB::Invoice',
33 column_map => { id => 'storno_id' },
35 sepa_export_items => {
36 type => 'one to many',
37 class => 'SL::DB::SepaExportItem',
38 column_map => { id => 'ar_id' },
39 manager_args => { with_objects => [ 'sepa_export' ] }
43 class => 'SL::DB::Shipto',
44 column_map => { id => 'trans_id' },
45 query_args => [ module => 'AR' ],
49 __PACKAGE__->meta->initialize;
51 __PACKAGE__->before_save('_before_save_set_invnumber');
55 sub _before_save_set_invnumber {
58 $self->create_trans_number if !$self->invnumber;
65 sub items { goto &invoiceitems; }
70 return [ sort {$a->id <=> $b->id } @{ $self->items } ];
74 # For compatibility with Order, DeliveryOrder
75 croak 'not an accessor' if @_ > 1;
79 # it is assumed, that ordnumbers are unique here.
80 sub first_order_by_ordnumber {
83 my $orders = SL::DB::Manager::Order->get_all(
85 ordnumber => $self->ordnumber,
90 return first { $_->is_type('sales_order') } @{ $orders };
93 sub abschlag_percentage {
95 my $order = $self->first_order_by_ordnumber or return;
96 my $order_amount = $order->netamount or return;
97 return $self->abschlag
98 ? $self->netamount / $order_amount
104 die 'not a setter method' if @_;
106 return ($self->amount || 0) - ($self->netamount || 0);
109 __PACKAGE__->meta->make_attr_helpers(taxamount => 'numeric(15,5)');
113 return $self->paid >= $self->amount;
116 sub _clone_orderitem_delivery_order_item_cvar {
119 my $cloned = Rose::DB::Object::Helpers::clone_and_reset($_);
120 $cloned->sub_module('invoice');
126 my ($class, $source, %params) = @_;
128 croak("Unsupported source object type '" . ref($source) . "'") unless ref($source) =~ m/^ SL::DB:: (?: Order | DeliveryOrder ) $/x;
129 croak("Cannot create invoices for purchase records") unless $source->customer_id;
131 require SL::DB::Employee;
133 my $terms = $source->can('payment_id') && $source->payment_id ? $source->payment_terms->terms_netto : 0;
134 my (@columns, @item_columns, $item_parent_id_column, $item_parent_column);
136 if (ref($source) eq 'SL::DB::Order') {
137 @columns = qw(quonumber payment_id delivery_customer_id delivery_vendor_id);
138 @item_columns = qw(subtotal);
140 $item_parent_id_column = 'trans_id';
141 $item_parent_column = 'order';
144 @columns = qw(donumber);
146 $item_parent_id_column = 'delivery_order_id';
147 $item_parent_column = 'delivery_order';
150 my %args = ( map({ ( $_ => $source->$_ ) } qw(customer_id taxincluded shippingpoint shipvia notes intnotes salesman_id cusordnumber ordnumber department_id
151 cp_id language_id taxzone_id shipto_id globalproject_id transaction_description currency_id delivery_term_id), @columns),
152 transdate => DateTime->today_local,
153 gldate => DateTime->today_local,
154 duedate => DateTime->today_local->add(days => $terms * 1),
159 employee_id => (SL::DB::Manager::Employee->current || SL::DB::Employee->new(id => $source->employee_id))->id,
162 if ($source->type =~ /_order$/) {
163 $args{deliverydate} = $source->reqdate;
164 $args{orddate} = $source->transdate;
166 $args{quodate} = $source->transdate;
169 my $invoice = $class->new(%args);
170 $invoice->assign_attributes(%{ $params{attributes} }) if $params{attributes};
171 my $items = delete($params{items}) || $source->items_sorted;
175 my $source_item = $_;
176 my $source_item_id = $_->$item_parent_id_column;
177 my @custom_variables = map { _clone_orderitem_delivery_order_item_cvar($_) } @{ $source_item->custom_variables };
179 $item_parents{$source_item_id} ||= $source_item->$item_parent_column;
180 my $item_parent = $item_parents{$source_item_id};
182 SL::DB::InvoiceItem->new(map({ ( $_ => $source_item->$_ ) }
183 qw(parts_id description qty sellprice discount project_id serialnumber pricegroup_id transdate cusordnumber unit
184 base_qty longdescription lastcost price_factor_id), @item_columns),
185 deliverydate => $source_item->reqdate,
186 fxsellprice => $source_item->sellprice,
187 custom_variables => \@custom_variables,
188 ordnumber => ref($item_parent) eq 'SL::DB::Order' ? $item_parent->ordnumber : $source_item->ordnumber,
189 donumber => ref($item_parent) eq 'SL::DB::DeliveryOrder' ? $item_parent->donumber : $source_item->can('donumber') ? $source_item->donumber : '',
194 @items = grep { $_->qty * 1 } @items if $params{skip_items_zero_qty};
196 $invoice->invoiceitems(\@items);
202 my ($self, %params) = @_;
204 require SL::DB::Chart;
205 if (!$params{ar_id}) {
206 my $chart = SL::DB::Manager::Chart->get_all(query => [ SL::DB::Manager::Chart->link_filter('AR') ],
209 croak("No AR chart found and no parameter `ar_id' given") unless $chart;
210 $params{ar_id} = $chart->id;
214 my %data = $self->calculate_prices_and_taxes;
216 $self->_post_create_assemblyitem_entries($data{assembly_items});
219 $self->_post_add_acctrans($data{amounts_cogs});
220 $self->_post_add_acctrans($data{amounts});
221 $self->_post_add_acctrans($data{taxes});
223 $self->_post_add_acctrans({ $params{ar_id} => $self->amount * -1 });
225 $self->_post_update_allocated($data{allocated});
228 if ($self->db->in_transaction) {
230 } elsif (!$self->db->do_transaction($worker)) {
231 $::lxdebug->message(LXDebug->WARN(), "convert_to_invoice failed: " . join("\n", (split(/\n/, $self->db->error))[0..2]));
238 sub _post_add_acctrans {
239 my ($self, $entries) = @_;
241 my $default_tax_id = SL::DB::Manager::Tax->find_by(taxkey => 0)->id;
244 require SL::DB::AccTransaction;
245 require SL::DB::Chart;
246 while (my ($chart_id, $spec) = each %{ $entries }) {
247 $spec = { taxkey => 0, tax_id => $default_tax_id, amount => $spec } unless ref $spec;
248 $chart_link = SL::DB::Manager::Chart->find_by(id => $chart_id)->{'link'};
251 SL::DB::AccTransaction->new(trans_id => $self->id,
252 chart_id => $chart_id,
253 amount => $spec->{amount},
254 tax_id => $spec->{tax_id},
255 taxkey => $spec->{taxkey},
256 project_id => $self->globalproject_id,
257 transdate => $self->transdate,
258 chart_link => $chart_link)->save;
262 sub _post_create_assemblyitem_entries {
263 my ($self, $assembly_entries) = @_;
265 my $items = $self->invoiceitems;
269 foreach my $item (@{ $items }) {
270 next if $item->assemblyitem;
272 push @new_items, $item;
275 foreach my $assembly_item (@{ $assembly_entries->[$item_idx] || [ ] }) {
276 push @new_items, SL::DB::InvoiceItem->new(parts_id => $assembly_item->{part},
277 description => $assembly_item->{part}->description,
278 unit => $assembly_item->{part}->unit,
279 qty => $assembly_item->{qty},
280 allocated => $assembly_item->{allocated},
283 assemblyitem => 't');
287 $self->invoiceitems(\@new_items);
290 sub _post_update_allocated {
291 my ($self, $allocated) = @_;
293 while (my ($invoice_id, $diff) = each %{ $allocated }) {
294 SL::DB::Manager::InvoiceItem->update_all(set => { allocated => { sql => "allocated + $diff" } },
295 where => [ id => $invoice_id ]);
302 return 'ar_transaction' if !$self->invoice;
303 return 'credit_note' if $self->type eq 'credit_note' && $self->amount < 0 && !$self->storno;
304 return 'invoice_storno' if $self->type ne 'credit_note' && $self->amount < 0 && $self->storno;
305 return 'credit_note_storno' if $self->type eq 'credit_note' && $self->amount > 0 && $self->storno;
309 sub displayable_state {
312 return $self->closed ? $::locale->text('closed') : $::locale->text('open');
327 SL::DB::Invoice: Rose model for invoices (table "ar")
333 =item C<new_from $source, %params>
335 Creates a new C<SL::DB::Invoice> instance and copies as much
336 information from C<$source> as possible. At the moment only sales
337 orders and sales quotations are supported as sources.
339 The conversion copies order items into invoice items. Dates are copied
340 as appropriate, e.g. the C<transdate> field from an order will be
341 copied into the invoice's C<orddate> field.
343 C<%params> can include the following options:
349 An optional array reference of RDBO instances for the items to use. If
350 missing then the method C<items_sorted> will be called on
351 C<$source>. This option can be used to override the sorting, to
352 exclude certain positions or to add additional ones.
354 =item C<skip_items_zero_qty>
356 If trueish then items with a quantity of 0 are skipped.
360 An optional hash reference. If it exists then it is passed to C<new>
361 allowing the caller to set certain attributes for the new delivery
366 Amounts, prices and taxes are not
367 calculated. L<SL::DB::Helper::PriceTaxCalculator::calculate_prices_and_taxes>
368 can be used for this.
370 The object returned is not saved.
372 =item C<post %params>
374 Posts the invoice. Required parameters are:
380 The ID of the accounds receivable chart the invoices amounts are
381 posted to. If it is not set then the first chart configured for
382 accounts receivables is used.
386 This function implements several steps:
390 =item 1. It calculates all prices, amounts and taxes by calling
391 L<SL::DB::Helper::PriceTaxCalculator::calculate_prices_and_taxes>.
393 =item 2. A new and unique invoice number is created.
395 =item 3. All amounts for costs of goods sold are recorded in
398 =item 4. All amounts for parts, services and assemblies are recorded
399 in C<acc_trans> with their respective charts. This is determined by
400 the part's buchungsgruppen.
402 =item 5. The total amount is posted to the accounts receivable chart
403 and recorded in C<acc_trans>.
405 =item 6. Items in C<invoice> are updated according to their allocation
406 status (regarding for costs of goold sold). Will only be done if
407 kivitendo is not configured to use Einnahmenüberschussrechnungen.
409 =item 7. The invoice and its items are saved.
413 Returns C<$self> on success and C<undef> on failure. The whole process
414 is run inside a transaction. If it fails then nothing is saved to or
415 changed in the database. A new transaction is only started if none is
418 =item C<basic_info $field>
420 See L<SL::DB::Object::basic_info>.
426 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>