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);
10 use List::MoreUtils qw(pairwise);
12 use SL::DB::MetaSetup::Invoice;
13 use SL::DB::Manager::Invoice;
14 use SL::DB::Helper::FlattenToForm;
15 use SL::DB::Helper::LinkedRecords;
16 use SL::DB::Helper::PriceTaxCalculator;
17 use SL::DB::Helper::PriceUpdater;
18 use SL::DB::Helper::TransNumberGenerator;
19 use SL::DB::CustomVariable;
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;
117 my ($class, $source, %params) = @_;
119 croak("Unsupported source object type '" . ref($source) . "'") unless ref($source) =~ m/^ SL::DB:: (?: Order | DeliveryOrder ) $/x;
120 croak("Cannot create invoices for purchase records") unless $source->customer_id;
122 require SL::DB::Employee;
124 my $terms = $source->can('payment_id') && $source->payment_id ? $source->payment_terms->terms_netto : 0;
126 my %args = ( map({ ( $_ => $source->$_ ) } qw(customer_id taxincluded shippingpoint shipvia notes intnotes salesman_id cusordnumber ordnumber quonumber
127 department_id cp_id language_id payment_id delivery_customer_id delivery_vendor_id taxzone_id shipto_id
128 globalproject_id transaction_description currency_id delivery_term_id)),
129 transdate => DateTime->today_local,
130 gldate => DateTime->today_local,
131 duedate => DateTime->today_local->add(days => $terms * 1),
136 employee_id => (SL::DB::Manager::Employee->current || SL::DB::Employee->new(id => $source->employee_id))->id,
139 if ($source->type =~ /_order$/) {
140 $args{deliverydate} = $source->reqdate;
141 $args{orddate} = $source->transdate;
143 $args{quodate} = $source->transdate;
146 my $invoice = $class->new(%args, %params);
149 my $source_item = $_;
150 SL::DB::InvoiceItem->new(map({ ( $_ => $source_item->$_ ) }
151 qw(parts_id description qty sellprice discount project_id
152 serialnumber pricegroup_id ordnumber transdate cusordnumber unit
153 base_qty subtotal longdescription lastcost price_factor_id)),
154 deliverydate => $source_item->reqdate,
155 fxsellprice => $source_item->sellprice,);
156 } @{ $source->items_sorted };
159 foreach my $item (@items) {
160 my $source_cvars = $source->items_sorted->[$i]->cvars_by_config;
161 my $target_cvars = $item->cvars_by_config;
162 pairwise { $a->value($b->value) } @{ $target_cvars }, @{ $source_cvars };
166 $invoice->invoiceitems(\@items);
172 my ($self, %params) = @_;
174 require SL::DB::Chart;
175 if (!$params{ar_id}) {
176 my $chart = SL::DB::Manager::Chart->get_all(query => [ SL::DB::Manager::Chart->link_filter('AR') ],
179 croak("No AR chart found and no parameter `ar_id' given") unless $chart;
180 $params{ar_id} = $chart->id;
184 my %data = $self->calculate_prices_and_taxes;
186 $self->_post_create_assemblyitem_entries($data{assembly_items});
189 $self->_post_add_acctrans($data{amounts_cogs});
190 $self->_post_add_acctrans($data{amounts});
191 $self->_post_add_acctrans($data{taxes});
193 $self->_post_add_acctrans({ $params{ar_id} => $self->amount * -1 });
195 $self->_post_update_allocated($data{allocated});
198 if ($self->db->in_transaction) {
200 } elsif (!$self->db->do_transaction($worker)) {
201 $::lxdebug->message(LXDebug->WARN(), "convert_to_invoice failed: " . join("\n", (split(/\n/, $self->db->error))[0..2]));
208 sub _post_add_acctrans {
209 my ($self, $entries) = @_;
211 my $default_tax_id = SL::DB::Manager::Tax->find_by(taxkey => 0)->id;
214 require SL::DB::AccTransaction;
215 require SL::DB::Chart;
216 while (my ($chart_id, $spec) = each %{ $entries }) {
217 $spec = { taxkey => 0, tax_id => $default_tax_id, amount => $spec } unless ref $spec;
218 $chart_link = SL::DB::Manager::Chart->find_by(id => $chart_id)->{'link'};
221 SL::DB::AccTransaction->new(trans_id => $self->id,
222 chart_id => $chart_id,
223 amount => $spec->{amount},
224 tax_id => $spec->{tax_id},
225 taxkey => $spec->{taxkey},
226 project_id => $self->globalproject_id,
227 transdate => $self->transdate,
228 chart_link => $chart_link)->save;
232 sub _post_create_assemblyitem_entries {
233 my ($self, $assembly_entries) = @_;
235 my $items = $self->invoiceitems;
239 foreach my $item (@{ $items }) {
240 next if $item->assemblyitem;
242 push @new_items, $item;
245 foreach my $assembly_item (@{ $assembly_entries->[$item_idx] || [ ] }) {
246 push @new_items, SL::DB::InvoiceItem->new(parts_id => $assembly_item->{part},
247 description => $assembly_item->{part}->description,
248 unit => $assembly_item->{part}->unit,
249 qty => $assembly_item->{qty},
250 allocated => $assembly_item->{allocated},
253 assemblyitem => 't');
257 $self->invoiceitems(\@new_items);
260 sub _post_update_allocated {
261 my ($self, $allocated) = @_;
263 while (my ($invoice_id, $diff) = each %{ $allocated }) {
264 SL::DB::Manager::InvoiceItem->update_all(set => { allocated => { sql => "allocated + $diff" } },
265 where => [ id => $invoice_id ]);
272 return 'ar_transaction' if !$self->invoice;
273 return 'credit_note' if $self->type eq 'credit_note' && $self->amount < 0 && !$self->storno;
274 return 'invoice_storno' if $self->type ne 'credit_note' && $self->amount < 0 && $self->storno;
275 return 'credit_note_storno' if $self->type eq 'credit_note' && $self->amount > 0 && $self->storno;
279 sub displayable_state {
282 return $self->closed ? $::locale->text('closed') : $::locale->text('open');
297 SL::DB::Invoice: Rose model for invoices (table "ar")
303 =item C<new_from $source>
305 Creates a new C<SL::DB::Invoice> instance and copies as much
306 information from C<$source> as possible. At the moment only sales
307 orders and sales quotations are supported as sources.
309 The conversion copies order items into invoice items. Dates are copied
310 as appropriate, e.g. the C<transdate> field from an order will be
311 copied into the invoice's C<orddate> field.
313 Amounts, prices and taxes are not
314 calculated. L<SL::DB::Helper::PriceTaxCalculator::calculate_prices_and_taxes>
315 can be used for this.
317 The object returned is not saved.
319 =item C<post %params>
321 Posts the invoice. Required parameters are:
327 The ID of the accounds receivable chart the invoices amounts are
328 posted to. If it is not set then the first chart configured for
329 accounts receivables is used.
333 This function implements several steps:
337 =item 1. It calculates all prices, amounts and taxes by calling
338 L<SL::DB::Helper::PriceTaxCalculator::calculate_prices_and_taxes>.
340 =item 2. A new and unique invoice number is created.
342 =item 3. All amounts for costs of goods sold are recorded in
345 =item 4. All amounts for parts, services and assemblies are recorded
346 in C<acc_trans> with their respective charts. This is determined by
347 the part's buchungsgruppen.
349 =item 5. The total amount is posted to the accounts receivable chart
350 and recorded in C<acc_trans>.
352 =item 6. Items in C<invoice> are updated according to their allocation
353 status (regarding for costs of goold sold). Will only be done if
354 kivitendo is not configured to use Einnahmenüberschussrechnungen.
356 =item 7. The invoice and its items are saved.
360 Returns C<$self> on success and C<undef> on failure. The whole process
361 is run inside a transaction. If it fails then nothing is saved to or
362 changed in the database. A new transaction is only started if none is
365 =item C<basic_info $field>
367 See L<SL::DB::Object::basic_info>.
373 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>