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);
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);
141 @columns = qw(donumber);
144 my %args = ( map({ ( $_ => $source->$_ ) } qw(customer_id taxincluded shippingpoint shipvia notes intnotes salesman_id cusordnumber ordnumber department_id
145 cp_id language_id taxzone_id shipto_id globalproject_id transaction_description currency_id delivery_term_id), @columns),
146 transdate => DateTime->today_local,
147 gldate => DateTime->today_local,
148 duedate => DateTime->today_local->add(days => $terms * 1),
153 employee_id => (SL::DB::Manager::Employee->current || SL::DB::Employee->new(id => $source->employee_id))->id,
156 if ($source->type =~ /_order$/) {
157 $args{deliverydate} = $source->reqdate;
158 $args{orddate} = $source->transdate;
160 $args{quodate} = $source->transdate;
163 my $invoice = $class->new(%args, %{ $params{attributes} || {} });
166 my $source_item = $_;
167 my @custom_variables = map { _clone_orderitem_delivery_order_item_cvar($_) } @{ $source_item->custom_variables };
169 SL::DB::InvoiceItem->new(map({ ( $_ => $source_item->$_ ) }
170 qw(parts_id description qty sellprice discount project_id serialnumber pricegroup_id ordnumber transdate cusordnumber unit
171 base_qty longdescription lastcost price_factor_id), @item_columns),
172 deliverydate => $source_item->reqdate,
173 fxsellprice => $source_item->sellprice,
174 custom_variables => \@custom_variables,
177 } @{ $source->items_sorted };
179 $invoice->invoiceitems(\@items);
185 my ($self, %params) = @_;
187 require SL::DB::Chart;
188 if (!$params{ar_id}) {
189 my $chart = SL::DB::Manager::Chart->get_all(query => [ SL::DB::Manager::Chart->link_filter('AR') ],
192 croak("No AR chart found and no parameter `ar_id' given") unless $chart;
193 $params{ar_id} = $chart->id;
197 my %data = $self->calculate_prices_and_taxes;
199 $self->_post_create_assemblyitem_entries($data{assembly_items});
202 $self->_post_add_acctrans($data{amounts_cogs});
203 $self->_post_add_acctrans($data{amounts});
204 $self->_post_add_acctrans($data{taxes});
206 $self->_post_add_acctrans({ $params{ar_id} => $self->amount * -1 });
208 $self->_post_update_allocated($data{allocated});
211 if ($self->db->in_transaction) {
213 } elsif (!$self->db->do_transaction($worker)) {
214 $::lxdebug->message(LXDebug->WARN(), "convert_to_invoice failed: " . join("\n", (split(/\n/, $self->db->error))[0..2]));
221 sub _post_add_acctrans {
222 my ($self, $entries) = @_;
224 my $default_tax_id = SL::DB::Manager::Tax->find_by(taxkey => 0)->id;
227 require SL::DB::AccTransaction;
228 require SL::DB::Chart;
229 while (my ($chart_id, $spec) = each %{ $entries }) {
230 $spec = { taxkey => 0, tax_id => $default_tax_id, amount => $spec } unless ref $spec;
231 $chart_link = SL::DB::Manager::Chart->find_by(id => $chart_id)->{'link'};
234 SL::DB::AccTransaction->new(trans_id => $self->id,
235 chart_id => $chart_id,
236 amount => $spec->{amount},
237 tax_id => $spec->{tax_id},
238 taxkey => $spec->{taxkey},
239 project_id => $self->globalproject_id,
240 transdate => $self->transdate,
241 chart_link => $chart_link)->save;
245 sub _post_create_assemblyitem_entries {
246 my ($self, $assembly_entries) = @_;
248 my $items = $self->invoiceitems;
252 foreach my $item (@{ $items }) {
253 next if $item->assemblyitem;
255 push @new_items, $item;
258 foreach my $assembly_item (@{ $assembly_entries->[$item_idx] || [ ] }) {
259 push @new_items, SL::DB::InvoiceItem->new(parts_id => $assembly_item->{part},
260 description => $assembly_item->{part}->description,
261 unit => $assembly_item->{part}->unit,
262 qty => $assembly_item->{qty},
263 allocated => $assembly_item->{allocated},
266 assemblyitem => 't');
270 $self->invoiceitems(\@new_items);
273 sub _post_update_allocated {
274 my ($self, $allocated) = @_;
276 while (my ($invoice_id, $diff) = each %{ $allocated }) {
277 SL::DB::Manager::InvoiceItem->update_all(set => { allocated => { sql => "allocated + $diff" } },
278 where => [ id => $invoice_id ]);
285 return 'ar_transaction' if !$self->invoice;
286 return 'credit_note' if $self->type eq 'credit_note' && $self->amount < 0 && !$self->storno;
287 return 'invoice_storno' if $self->type ne 'credit_note' && $self->amount < 0 && $self->storno;
288 return 'credit_note_storno' if $self->type eq 'credit_note' && $self->amount > 0 && $self->storno;
292 sub displayable_state {
295 return $self->closed ? $::locale->text('closed') : $::locale->text('open');
310 SL::DB::Invoice: Rose model for invoices (table "ar")
316 =item C<new_from $source, %params>
318 Creates a new C<SL::DB::Invoice> instance and copies as much
319 information from C<$source> as possible. At the moment only sales
320 orders and sales quotations are supported as sources.
322 The conversion copies order items into invoice items. Dates are copied
323 as appropriate, e.g. the C<transdate> field from an order will be
324 copied into the invoice's C<orddate> field.
326 C<%params> can include the following options:
332 An optional hash reference. If it exists then it is passed to C<new>
333 allowing the caller to set certain attributes for the new delivery
338 Amounts, prices and taxes are not
339 calculated. L<SL::DB::Helper::PriceTaxCalculator::calculate_prices_and_taxes>
340 can be used for this.
342 The object returned is not saved.
344 =item C<post %params>
346 Posts the invoice. Required parameters are:
352 The ID of the accounds receivable chart the invoices amounts are
353 posted to. If it is not set then the first chart configured for
354 accounts receivables is used.
358 This function implements several steps:
362 =item 1. It calculates all prices, amounts and taxes by calling
363 L<SL::DB::Helper::PriceTaxCalculator::calculate_prices_and_taxes>.
365 =item 2. A new and unique invoice number is created.
367 =item 3. All amounts for costs of goods sold are recorded in
370 =item 4. All amounts for parts, services and assemblies are recorded
371 in C<acc_trans> with their respective charts. This is determined by
372 the part's buchungsgruppen.
374 =item 5. The total amount is posted to the accounts receivable chart
375 and recorded in C<acc_trans>.
377 =item 6. Items in C<invoice> are updated according to their allocation
378 status (regarding for costs of goold sold). Will only be done if
379 kivitendo is not configured to use Einnahmenüberschussrechnungen.
381 =item 7. The invoice and its items are saved.
385 Returns C<$self> on success and C<undef> on failure. The whole process
386 is run inside a transaction. If it fails then nothing is saved to or
387 changed in the database. A new transaction is only started if none is
390 =item C<basic_info $field>
392 See L<SL::DB::Object::basic_info>.
398 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>