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 __PACKAGE__->meta->initialize;
45 __PACKAGE__->before_save('_before_save_set_invnumber');
49 sub _before_save_set_invnumber {
52 $self->create_trans_number if !$self->invnumber;
59 sub items { goto &invoiceitems; }
64 return [ sort {$a->id <=> $b->id } @{ $self->items } ];
68 # For compatibility with Order, DeliveryOrder
69 croak 'not an accessor' if @_ > 1;
73 # it is assumed, that ordnumbers are unique here.
74 sub first_order_by_ordnumber {
77 my $orders = SL::DB::Manager::Order->get_all(
79 ordnumber => $self->ordnumber,
84 return first { $_->is_type('sales_order') } @{ $orders };
87 sub abschlag_percentage {
89 my $order = $self->first_order_by_ordnumber or return;
90 my $order_amount = $order->netamount or return;
91 return $self->abschlag
92 ? $self->netamount / $order_amount
98 die 'not a setter method' if @_;
100 return ($self->amount || 0) - ($self->netamount || 0);
103 __PACKAGE__->meta->make_attr_helpers(taxamount => 'numeric(15,5)');
107 return $self->paid >= $self->amount;
111 my ($class, $source, %params) = @_;
113 croak("Unsupported source object type '" . ref($source) . "'") unless ref($source) =~ m/^ SL::DB:: (?: Order | DeliveryOrder ) $/x;
114 croak("Cannot create invoices for purchase records") unless $source->customer_id;
116 require SL::DB::Employee;
118 my $terms = $source->can('payment_id') && $source->payment_id ? $source->payment_terms->terms_netto : 0;
120 my %args = ( map({ ( $_ => $source->$_ ) } qw(customer_id taxincluded shippingpoint shipvia notes intnotes salesman_id cusordnumber ordnumber quonumber
121 department_id cp_id language_id payment_id delivery_customer_id delivery_vendor_id taxzone_id shipto_id
122 globalproject_id transaction_description currency_id delivery_term_id)),
123 transdate => DateTime->today_local,
124 gldate => DateTime->today_local,
125 duedate => DateTime->today_local->add(days => $terms * 1),
130 employee_id => (SL::DB::Manager::Employee->current || SL::DB::Employee->new(id => $source->employee_id))->id,
133 if ($source->type =~ /_order$/) {
134 $args{deliverydate} = $source->reqdate;
135 $args{orddate} = $source->transdate;
137 $args{quodate} = $source->transdate;
140 my $invoice = $class->new(%args, %params);
143 my $source_item = $_;
144 SL::DB::InvoiceItem->new(map({ ( $_ => $source_item->$_ ) }
145 qw(parts_id description qty sellprice discount project_id
146 serialnumber pricegroup_id ordnumber transdate cusordnumber unit
147 base_qty subtotal longdescription lastcost price_factor_id)),
148 deliverydate => $source_item->reqdate,
149 fxsellprice => $source_item->sellprice,);
150 } @{ $source->items_sorted };
153 foreach my $item (@items) {
154 my $source_cvars = $source->items_sorted->[$i]->cvars_by_config;
155 my $target_cvars = $item->cvars_by_config;
156 pairwise { $a->value($b->value) } @{ $target_cvars }, @{ $source_cvars };
160 $invoice->invoiceitems(\@items);
166 my ($self, %params) = @_;
168 require SL::DB::Chart;
169 if (!$params{ar_id}) {
170 my $chart = SL::DB::Manager::Chart->get_all(query => [ SL::DB::Manager::Chart->link_filter('AR') ],
173 croak("No AR chart found and no parameter `ar_id' given") unless $chart;
174 $params{ar_id} = $chart->id;
178 my %data = $self->calculate_prices_and_taxes;
180 $self->_post_create_assemblyitem_entries($data{assembly_items});
183 $self->_post_add_acctrans($data{amounts_cogs});
184 $self->_post_add_acctrans($data{amounts});
185 $self->_post_add_acctrans($data{taxes});
187 $self->_post_add_acctrans({ $params{ar_id} => $self->amount * -1 });
189 $self->_post_update_allocated($data{allocated});
192 if ($self->db->in_transaction) {
194 } elsif (!$self->db->do_transaction($worker)) {
195 $::lxdebug->message(LXDebug->WARN(), "convert_to_invoice failed: " . join("\n", (split(/\n/, $self->db->error))[0..2]));
202 sub _post_add_acctrans {
203 my ($self, $entries) = @_;
205 my $default_tax_id = SL::DB::Manager::Tax->find_by(taxkey => 0)->id;
208 require SL::DB::AccTransaction;
209 require SL::DB::Chart;
210 while (my ($chart_id, $spec) = each %{ $entries }) {
211 $spec = { taxkey => 0, tax_id => $default_tax_id, amount => $spec } unless ref $spec;
212 $chart_link = SL::DB::Manager::Chart->find_by(id => $chart_id)->{'link'};
215 SL::DB::AccTransaction->new(trans_id => $self->id,
216 chart_id => $chart_id,
217 amount => $spec->{amount},
218 tax_id => $spec->{tax_id},
219 taxkey => $spec->{taxkey},
220 project_id => $self->globalproject_id,
221 transdate => $self->transdate,
222 chart_link => $chart_link)->save;
226 sub _post_create_assemblyitem_entries {
227 my ($self, $assembly_entries) = @_;
229 my $items = $self->invoiceitems;
233 foreach my $item (@{ $items }) {
234 next if $item->assemblyitem;
236 push @new_items, $item;
239 foreach my $assembly_item (@{ $assembly_entries->[$item_idx] || [ ] }) {
240 push @new_items, SL::DB::InvoiceItem->new(parts_id => $assembly_item->{part},
241 description => $assembly_item->{part}->description,
242 unit => $assembly_item->{part}->unit,
243 qty => $assembly_item->{qty},
244 allocated => $assembly_item->{allocated},
247 assemblyitem => 't');
251 $self->invoiceitems(\@new_items);
254 sub _post_update_allocated {
255 my ($self, $allocated) = @_;
257 while (my ($invoice_id, $diff) = each %{ $allocated }) {
258 SL::DB::Manager::InvoiceItem->update_all(set => { allocated => { sql => "allocated + $diff" } },
259 where => [ id => $invoice_id ]);
266 return 'ar_transaction' if !$self->invoice;
267 return 'credit_note' if $self->type eq 'credit_note' && $self->amount < 0 && !$self->storno;
268 return 'invoice_storno' if $self->type ne 'credit_note' && $self->amount < 0 && $self->storno;
269 return 'credit_note_storno' if $self->type eq 'credit_note' && $self->amount > 0 && $self->storno;
273 sub displayable_state {
276 return $self->closed ? $::locale->text('closed') : $::locale->text('open');
291 SL::DB::Invoice: Rose model for invoices (table "ar")
297 =item C<new_from $source>
299 Creates a new C<SL::DB::Invoice> instance and copies as much
300 information from C<$source> as possible. At the moment only sales
301 orders and sales quotations are supported as sources.
303 The conversion copies order items into invoice items. Dates are copied
304 as appropriate, e.g. the C<transdate> field from an order will be
305 copied into the invoice's C<orddate> field.
307 Amounts, prices and taxes are not
308 calculated. L<SL::DB::Helper::PriceTaxCalculator::calculate_prices_and_taxes>
309 can be used for this.
311 The object returned is not saved.
313 =item C<post %params>
315 Posts the invoice. Required parameters are:
321 The ID of the accounds receivable chart the invoices amounts are
322 posted to. If it is not set then the first chart configured for
323 accounts receivables is used.
327 This function implements several steps:
331 =item 1. It calculates all prices, amounts and taxes by calling
332 L<SL::DB::Helper::PriceTaxCalculator::calculate_prices_and_taxes>.
334 =item 2. A new and unique invoice number is created.
336 =item 3. All amounts for costs of goods sold are recorded in
339 =item 4. All amounts for parts, services and assemblies are recorded
340 in C<acc_trans> with their respective charts. This is determined by
341 the part's buchungsgruppen.
343 =item 5. The total amount is posted to the accounts receivable chart
344 and recorded in C<acc_trans>.
346 =item 6. Items in C<invoice> are updated according to their allocation
347 status (regarding for costs of goold sold). Will only be done if
348 kivitendo is not configured to use Einnahmenüberschussrechnungen.
350 =item 7. The invoice and its items are saved.
354 Returns C<$self> on success and C<undef> on failure. The whole process
355 is run inside a transaction. If it fails then nothing is saved to or
356 changed in the database. A new transaction is only started if none is
359 =item C<basic_info $field>
361 See L<SL::DB::Object::basic_info>.
367 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>