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 class => 'SL::DB::Shipto',
42 column_map => { id => 'trans_id' },
43 query_args => [ module => 'AR' ],
47 __PACKAGE__->meta->initialize;
49 __PACKAGE__->before_save('_before_save_set_invnumber');
53 sub _before_save_set_invnumber {
56 $self->create_trans_number if !$self->invnumber;
63 sub items { goto &invoiceitems; }
68 return [ sort {$a->id <=> $b->id } @{ $self->items } ];
72 # For compatibility with Order, DeliveryOrder
73 croak 'not an accessor' if @_ > 1;
77 # it is assumed, that ordnumbers are unique here.
78 sub first_order_by_ordnumber {
81 my $orders = SL::DB::Manager::Order->get_all(
83 ordnumber => $self->ordnumber,
88 return first { $_->is_type('sales_order') } @{ $orders };
91 sub abschlag_percentage {
93 my $order = $self->first_order_by_ordnumber or return;
94 my $order_amount = $order->netamount or return;
95 return $self->abschlag
96 ? $self->netamount / $order_amount
102 die 'not a setter method' if @_;
104 return ($self->amount || 0) - ($self->netamount || 0);
107 __PACKAGE__->meta->make_attr_helpers(taxamount => 'numeric(15,5)');
111 return $self->paid >= $self->amount;
115 my ($class, $source, %params) = @_;
117 croak("Unsupported source object type '" . ref($source) . "'") unless ref($source) =~ m/^ SL::DB:: (?: Order | DeliveryOrder ) $/x;
118 croak("Cannot create invoices for purchase records") unless $source->customer_id;
120 require SL::DB::Employee;
122 my $terms = $source->can('payment_id') && $source->payment_id ? $source->payment_terms->terms_netto : 0;
123 my (@columns, @item_columns);
125 if (ref($source) eq 'SL::DB::Order') {
126 @columns = qw(quonumber payment_id delivery_customer_id delivery_vendor_id);
127 @item_columns = qw(subtotal);
130 @columns = qw(donumber);
133 my %args = ( map({ ( $_ => $source->$_ ) } qw(customer_id taxincluded shippingpoint shipvia notes intnotes salesman_id cusordnumber ordnumber department_id
134 cp_id language_id taxzone_id shipto_id globalproject_id transaction_description currency_id delivery_term_id), @columns),
135 transdate => DateTime->today_local,
136 gldate => DateTime->today_local,
137 duedate => DateTime->today_local->add(days => $terms * 1),
142 employee_id => (SL::DB::Manager::Employee->current || SL::DB::Employee->new(id => $source->employee_id))->id,
145 if ($source->type =~ /_order$/) {
146 $args{deliverydate} = $source->reqdate;
147 $args{orddate} = $source->transdate;
149 $args{quodate} = $source->transdate;
152 my $invoice = $class->new(%args, %params);
155 my $source_item = $_;
156 SL::DB::InvoiceItem->new(map({ ( $_ => $source_item->$_ ) }
157 qw(parts_id description qty sellprice discount project_id serialnumber pricegroup_id ordnumber transdate cusordnumber unit
158 base_qty longdescription lastcost price_factor_id), @item_columns),
159 deliverydate => $source_item->reqdate,
160 fxsellprice => $source_item->sellprice,);
161 } @{ $source->items_sorted };
163 $invoice->invoiceitems(\@items);
169 my ($self, %params) = @_;
171 require SL::DB::Chart;
172 if (!$params{ar_id}) {
173 my $chart = SL::DB::Manager::Chart->get_all(query => [ SL::DB::Manager::Chart->link_filter('AR') ],
176 croak("No AR chart found and no parameter `ar_id' given") unless $chart;
177 $params{ar_id} = $chart->id;
181 my %data = $self->calculate_prices_and_taxes;
183 $self->_post_create_assemblyitem_entries($data{assembly_items});
186 $self->_post_add_acctrans($data{amounts_cogs});
187 $self->_post_add_acctrans($data{amounts});
188 $self->_post_add_acctrans($data{taxes});
190 $self->_post_add_acctrans({ $params{ar_id} => $self->amount * -1 });
192 $self->_post_update_allocated($data{allocated});
195 if ($self->db->in_transaction) {
197 } elsif (!$self->db->do_transaction($worker)) {
198 $::lxdebug->message(LXDebug->WARN(), "convert_to_invoice failed: " . join("\n", (split(/\n/, $self->db->error))[0..2]));
205 sub _post_add_acctrans {
206 my ($self, $entries) = @_;
208 my $default_tax_id = SL::DB::Manager::Tax->find_by(taxkey => 0)->id;
211 require SL::DB::AccTransaction;
212 require SL::DB::Chart;
213 while (my ($chart_id, $spec) = each %{ $entries }) {
214 $spec = { taxkey => 0, tax_id => $default_tax_id, amount => $spec } unless ref $spec;
215 $chart_link = SL::DB::Manager::Chart->find_by(id => $chart_id)->{'link'};
218 SL::DB::AccTransaction->new(trans_id => $self->id,
219 chart_id => $chart_id,
220 amount => $spec->{amount},
221 tax_id => $spec->{tax_id},
222 taxkey => $spec->{taxkey},
223 project_id => $self->globalproject_id,
224 transdate => $self->transdate,
225 chart_link => $chart_link)->save;
229 sub _post_create_assemblyitem_entries {
230 my ($self, $assembly_entries) = @_;
232 my $items = $self->invoiceitems;
236 foreach my $item (@{ $items }) {
237 next if $item->assemblyitem;
239 push @new_items, $item;
242 foreach my $assembly_item (@{ $assembly_entries->[$item_idx] || [ ] }) {
243 push @new_items, SL::DB::InvoiceItem->new(parts_id => $assembly_item->{part},
244 description => $assembly_item->{part}->description,
245 unit => $assembly_item->{part}->unit,
246 qty => $assembly_item->{qty},
247 allocated => $assembly_item->{allocated},
250 assemblyitem => 't');
254 $self->invoiceitems(\@new_items);
257 sub _post_update_allocated {
258 my ($self, $allocated) = @_;
260 while (my ($invoice_id, $diff) = each %{ $allocated }) {
261 SL::DB::Manager::InvoiceItem->update_all(set => { allocated => { sql => "allocated + $diff" } },
262 where => [ id => $invoice_id ]);
269 return 'ar_transaction' if !$self->invoice;
270 return 'credit_note' if $self->type eq 'credit_note' && $self->amount < 0 && !$self->storno;
271 return 'invoice_storno' if $self->type ne 'credit_note' && $self->amount < 0 && $self->storno;
272 return 'credit_note_storno' if $self->type eq 'credit_note' && $self->amount > 0 && $self->storno;
276 sub displayable_state {
279 return $self->closed ? $::locale->text('closed') : $::locale->text('open');
294 SL::DB::Invoice: Rose model for invoices (table "ar")
300 =item C<new_from $source>
302 Creates a new C<SL::DB::Invoice> instance and copies as much
303 information from C<$source> as possible. At the moment only sales
304 orders and sales quotations are supported as sources.
306 The conversion copies order items into invoice items. Dates are copied
307 as appropriate, e.g. the C<transdate> field from an order will be
308 copied into the invoice's C<orddate> field.
310 Amounts, prices and taxes are not
311 calculated. L<SL::DB::Helper::PriceTaxCalculator::calculate_prices_and_taxes>
312 can be used for this.
314 The object returned is not saved.
316 =item C<post %params>
318 Posts the invoice. Required parameters are:
324 The ID of the accounds receivable chart the invoices amounts are
325 posted to. If it is not set then the first chart configured for
326 accounts receivables is used.
330 This function implements several steps:
334 =item 1. It calculates all prices, amounts and taxes by calling
335 L<SL::DB::Helper::PriceTaxCalculator::calculate_prices_and_taxes>.
337 =item 2. A new and unique invoice number is created.
339 =item 3. All amounts for costs of goods sold are recorded in
342 =item 4. All amounts for parts, services and assemblies are recorded
343 in C<acc_trans> with their respective charts. This is determined by
344 the part's buchungsgruppen.
346 =item 5. The total amount is posted to the accounts receivable chart
347 and recorded in C<acc_trans>.
349 =item 6. Items in C<invoice> are updated according to their allocation
350 status (regarding for costs of goold sold). Will only be done if
351 kivitendo is not configured to use Einnahmenüberschussrechnungen.
353 =item 7. The invoice and its items are saved.
357 Returns C<$self> on success and C<undef> on failure. The whole process
358 is run inside a transaction. If it fails then nothing is saved to or
359 changed in the database. A new transaction is only started if none is
362 =item C<basic_info $field>
364 See L<SL::DB::Object::basic_info>.
370 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>