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;
19 __PACKAGE__->meta->add_relationship(
21 type => 'one to many',
22 class => 'SL::DB::InvoiceItem',
23 column_map => { id => 'trans_id' },
25 with_objects => [ 'part' ]
29 type => 'one to many',
30 class => 'SL::DB::Invoice',
31 column_map => { id => 'storno_id' },
33 sepa_export_items => {
34 type => 'one to many',
35 class => 'SL::DB::SepaExportItem',
36 column_map => { id => 'ar_id' },
37 manager_args => { with_objects => [ 'sepa_export' ] }
41 __PACKAGE__->meta->initialize;
43 __PACKAGE__->before_save('_before_save_set_invnumber');
47 sub _before_save_set_invnumber {
50 $self->create_trans_number if !$self->invnumber;
57 sub items { goto &invoiceitems; }
62 return [ sort {$a->id <=> $b->id } @{ $self->items } ];
66 # For compatibility with Order, DeliveryOrder
67 croak 'not an accessor' if @_ > 1;
71 # it is assumed, that ordnumbers are unique here.
72 sub first_order_by_ordnumber {
75 my $orders = SL::DB::Manager::Order->get_all(
77 ordnumber => $self->ordnumber,
82 return first { $_->is_type('sales_order') } @{ $orders };
85 sub abschlag_percentage {
87 my $order = $self->first_order_by_ordnumber or return;
88 my $order_amount = $order->netamount or return;
89 return $self->abschlag
90 ? $self->netamount / $order_amount
96 die 'not a setter method' if @_;
98 return ($self->amount || 0) - ($self->netamount || 0);
101 __PACKAGE__->meta->make_attr_helpers(taxamount => 'numeric(15,5)');
105 return $self->paid >= $self->amount;
109 my ($class, $source, %params) = @_;
111 croak("Unsupported source object type '" . ref($source) . "'") unless ref($source) =~ m/^ SL::DB:: (?: Order | DeliveryOrder ) $/x;
112 croak("Cannot create invoices for purchase records") unless $source->customer_id;
114 require SL::DB::Employee;
116 my $terms = $source->can('payment_id') && $source->payment_id ? $source->payment_terms->terms_netto : 0;
118 my %args = ( map({ ( $_ => $source->$_ ) } qw(customer_id taxincluded shippingpoint shipvia notes intnotes salesman_id cusordnumber ordnumber quonumber
119 department_id cp_id language_id payment_id delivery_customer_id delivery_vendor_id taxzone_id shipto_id
120 globalproject_id transaction_description currency_id delivery_term_id)),
121 transdate => DateTime->today_local,
122 gldate => DateTime->today_local,
123 duedate => DateTime->today_local->add(days => $terms * 1),
128 employee_id => (SL::DB::Manager::Employee->current || SL::DB::Employee->new(id => $source->employee_id))->id,
131 if ($source->type =~ /_order$/) {
132 $args{deliverydate} = $source->reqdate;
133 $args{orddate} = $source->transdate;
135 $args{quodate} = $source->transdate;
138 my $invoice = $class->new(%args, %params);
141 my $source_item = $_;
142 SL::DB::InvoiceItem->new(map({ ( $_ => $source_item->$_ ) }
143 qw(parts_id description qty sellprice discount project_id
144 serialnumber pricegroup_id ordnumber transdate cusordnumber unit
145 base_qty subtotal longdescription lastcost price_factor_id)),
146 deliverydate => $source_item->reqdate,
147 fxsellprice => $source_item->sellprice,);
148 } @{ $source->items_sorted };
150 $invoice->invoiceitems(\@items);
156 my ($self, %params) = @_;
158 require SL::DB::Chart;
159 if (!$params{ar_id}) {
160 my $chart = SL::DB::Manager::Chart->get_all(query => [ SL::DB::Manager::Chart->link_filter('AR') ],
163 croak("No AR chart found and no parameter `ar_id' given") unless $chart;
164 $params{ar_id} = $chart->id;
168 my %data = $self->calculate_prices_and_taxes;
170 $self->_post_create_assemblyitem_entries($data{assembly_items});
173 $self->_post_add_acctrans($data{amounts_cogs});
174 $self->_post_add_acctrans($data{amounts});
175 $self->_post_add_acctrans($data{taxes});
177 $self->_post_add_acctrans({ $params{ar_id} => $self->amount * -1 });
179 $self->_post_update_allocated($data{allocated});
182 if ($self->db->in_transaction) {
184 } elsif (!$self->db->do_transaction($worker)) {
185 $::lxdebug->message(LXDebug->WARN(), "convert_to_invoice failed: " . join("\n", (split(/\n/, $self->db->error))[0..2]));
192 sub _post_add_acctrans {
193 my ($self, $entries) = @_;
195 my $default_tax_id = SL::DB::Manager::Tax->find_by(taxkey => 0)->id;
198 require SL::DB::AccTransaction;
199 require SL::DB::Chart;
200 while (my ($chart_id, $spec) = each %{ $entries }) {
201 $spec = { taxkey => 0, tax_id => $default_tax_id, amount => $spec } unless ref $spec;
202 $chart_link = SL::DB::Manager::Chart->find_by(id => $chart_id)->{'link'};
205 SL::DB::AccTransaction->new(trans_id => $self->id,
206 chart_id => $chart_id,
207 amount => $spec->{amount},
208 tax_id => $spec->{tax_id},
209 taxkey => $spec->{taxkey},
210 project_id => $self->globalproject_id,
211 transdate => $self->transdate,
212 chart_link => $chart_link)->save;
216 sub _post_create_assemblyitem_entries {
217 my ($self, $assembly_entries) = @_;
219 my $items = $self->invoiceitems;
223 foreach my $item (@{ $items }) {
224 next if $item->assemblyitem;
226 push @new_items, $item;
229 foreach my $assembly_item (@{ $assembly_entries->[$item_idx] || [ ] }) {
230 push @new_items, SL::DB::InvoiceItem->new(parts_id => $assembly_item->{part},
231 description => $assembly_item->{part}->description,
232 unit => $assembly_item->{part}->unit,
233 qty => $assembly_item->{qty},
234 allocated => $assembly_item->{allocated},
237 assemblyitem => 't');
241 $self->invoiceitems(\@new_items);
244 sub _post_update_allocated {
245 my ($self, $allocated) = @_;
247 while (my ($invoice_id, $diff) = each %{ $allocated }) {
248 SL::DB::Manager::InvoiceItem->update_all(set => { allocated => { sql => "allocated + $diff" } },
249 where => [ id => $invoice_id ]);
256 return 'ar_transaction' if !$self->invoice;
257 return 'credit_note' if $self->type eq 'credit_note' && $self->amount < 0 && !$self->storno;
258 return 'invoice_storno' if $self->type ne 'credit_note' && $self->amount < 0 && $self->storno;
259 return 'credit_note_storno' if $self->type eq 'credit_note' && $self->amount > 0 && $self->storno;
263 sub displayable_state {
266 return $self->closed ? $::locale->text('closed') : $::locale->text('open');
281 SL::DB::Invoice: Rose model for invoices (table "ar")
287 =item C<new_from $source>
289 Creates a new C<SL::DB::Invoice> instance and copies as much
290 information from C<$source> as possible. At the moment only sales
291 orders and sales quotations are supported as sources.
293 The conversion copies order items into invoice items. Dates are copied
294 as appropriate, e.g. the C<transdate> field from an order will be
295 copied into the invoice's C<orddate> field.
297 Amounts, prices and taxes are not
298 calculated. L<SL::DB::Helper::PriceTaxCalculator::calculate_prices_and_taxes>
299 can be used for this.
301 The object returned is not saved.
303 =item C<post %params>
305 Posts the invoice. Required parameters are:
311 The ID of the accounds receivable chart the invoices amounts are
312 posted to. If it is not set then the first chart configured for
313 accounts receivables is used.
317 This function implements several steps:
321 =item 1. It calculates all prices, amounts and taxes by calling
322 L<SL::DB::Helper::PriceTaxCalculator::calculate_prices_and_taxes>.
324 =item 2. A new and unique invoice number is created.
326 =item 3. All amounts for costs of goods sold are recorded in
329 =item 4. All amounts for parts, services and assemblies are recorded
330 in C<acc_trans> with their respective charts. This is determined by
331 the part's buchungsgruppen.
333 =item 5. The total amount is posted to the accounts receivable chart
334 and recorded in C<acc_trans>.
336 =item 6. Items in C<invoice> are updated according to their allocation
337 status (regarding for costs of goold sold). Will only be done if
338 kivitendo is not configured to use Einnahmenüberschussrechnungen.
340 =item 7. The invoice and its items are saved.
344 Returns C<$self> on success and C<undef> on failure. The whole process
345 is run inside a transaction. If it fails then nothing is saved to or
346 changed in the database. A new transaction is only started if none is
349 =item C<basic_info $field>
351 See L<SL::DB::Object::basic_info>.
357 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>