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;
125 my (@columns, @item_columns);
127 if (ref($source) eq 'SL::DB::Order') {
128 @columns = qw(quonumber payment_id delivery_customer_id delivery_vendor_id);
129 @item_columns = qw(subtotal);
132 @columns = qw(donumber);
135 my %args = ( map({ ( $_ => $source->$_ ) } qw(customer_id taxincluded shippingpoint shipvia notes intnotes salesman_id cusordnumber ordnumber department_id
136 cp_id language_id taxzone_id shipto_id globalproject_id transaction_description currency_id delivery_term_id), @columns),
137 transdate => DateTime->today_local,
138 gldate => DateTime->today_local,
139 duedate => DateTime->today_local->add(days => $terms * 1),
144 employee_id => (SL::DB::Manager::Employee->current || SL::DB::Employee->new(id => $source->employee_id))->id,
147 if ($source->type =~ /_order$/) {
148 $args{deliverydate} = $source->reqdate;
149 $args{orddate} = $source->transdate;
151 $args{quodate} = $source->transdate;
154 my $invoice = $class->new(%args, %params);
157 my $source_item = $_;
158 SL::DB::InvoiceItem->new(map({ ( $_ => $source_item->$_ ) }
159 qw(parts_id description qty sellprice discount project_id serialnumber pricegroup_id ordnumber transdate cusordnumber unit
160 base_qty longdescription lastcost price_factor_id), @item_columns),
161 deliverydate => $source_item->reqdate,
162 fxsellprice => $source_item->sellprice,);
163 } @{ $source->items_sorted };
166 foreach my $item (@items) {
167 my $source_cvars = $source->items_sorted->[$i]->cvars_by_config;
168 my $target_cvars = $item->cvars_by_config;
169 pairwise { $a->value($b->value) } @{ $target_cvars }, @{ $source_cvars };
173 $invoice->invoiceitems(\@items);
179 my ($self, %params) = @_;
181 require SL::DB::Chart;
182 if (!$params{ar_id}) {
183 my $chart = SL::DB::Manager::Chart->get_all(query => [ SL::DB::Manager::Chart->link_filter('AR') ],
186 croak("No AR chart found and no parameter `ar_id' given") unless $chart;
187 $params{ar_id} = $chart->id;
191 my %data = $self->calculate_prices_and_taxes;
193 $self->_post_create_assemblyitem_entries($data{assembly_items});
196 $self->_post_add_acctrans($data{amounts_cogs});
197 $self->_post_add_acctrans($data{amounts});
198 $self->_post_add_acctrans($data{taxes});
200 $self->_post_add_acctrans({ $params{ar_id} => $self->amount * -1 });
202 $self->_post_update_allocated($data{allocated});
205 if ($self->db->in_transaction) {
207 } elsif (!$self->db->do_transaction($worker)) {
208 $::lxdebug->message(LXDebug->WARN(), "convert_to_invoice failed: " . join("\n", (split(/\n/, $self->db->error))[0..2]));
215 sub _post_add_acctrans {
216 my ($self, $entries) = @_;
218 my $default_tax_id = SL::DB::Manager::Tax->find_by(taxkey => 0)->id;
221 require SL::DB::AccTransaction;
222 require SL::DB::Chart;
223 while (my ($chart_id, $spec) = each %{ $entries }) {
224 $spec = { taxkey => 0, tax_id => $default_tax_id, amount => $spec } unless ref $spec;
225 $chart_link = SL::DB::Manager::Chart->find_by(id => $chart_id)->{'link'};
228 SL::DB::AccTransaction->new(trans_id => $self->id,
229 chart_id => $chart_id,
230 amount => $spec->{amount},
231 tax_id => $spec->{tax_id},
232 taxkey => $spec->{taxkey},
233 project_id => $self->globalproject_id,
234 transdate => $self->transdate,
235 chart_link => $chart_link)->save;
239 sub _post_create_assemblyitem_entries {
240 my ($self, $assembly_entries) = @_;
242 my $items = $self->invoiceitems;
246 foreach my $item (@{ $items }) {
247 next if $item->assemblyitem;
249 push @new_items, $item;
252 foreach my $assembly_item (@{ $assembly_entries->[$item_idx] || [ ] }) {
253 push @new_items, SL::DB::InvoiceItem->new(parts_id => $assembly_item->{part},
254 description => $assembly_item->{part}->description,
255 unit => $assembly_item->{part}->unit,
256 qty => $assembly_item->{qty},
257 allocated => $assembly_item->{allocated},
260 assemblyitem => 't');
264 $self->invoiceitems(\@new_items);
267 sub _post_update_allocated {
268 my ($self, $allocated) = @_;
270 while (my ($invoice_id, $diff) = each %{ $allocated }) {
271 SL::DB::Manager::InvoiceItem->update_all(set => { allocated => { sql => "allocated + $diff" } },
272 where => [ id => $invoice_id ]);
279 return 'ar_transaction' if !$self->invoice;
280 return 'credit_note' if $self->type eq 'credit_note' && $self->amount < 0 && !$self->storno;
281 return 'invoice_storno' if $self->type ne 'credit_note' && $self->amount < 0 && $self->storno;
282 return 'credit_note_storno' if $self->type eq 'credit_note' && $self->amount > 0 && $self->storno;
286 sub displayable_state {
289 return $self->closed ? $::locale->text('closed') : $::locale->text('open');
304 SL::DB::Invoice: Rose model for invoices (table "ar")
310 =item C<new_from $source>
312 Creates a new C<SL::DB::Invoice> instance and copies as much
313 information from C<$source> as possible. At the moment only sales
314 orders and sales quotations are supported as sources.
316 The conversion copies order items into invoice items. Dates are copied
317 as appropriate, e.g. the C<transdate> field from an order will be
318 copied into the invoice's C<orddate> field.
320 Amounts, prices and taxes are not
321 calculated. L<SL::DB::Helper::PriceTaxCalculator::calculate_prices_and_taxes>
322 can be used for this.
324 The object returned is not saved.
326 =item C<post %params>
328 Posts the invoice. Required parameters are:
334 The ID of the accounds receivable chart the invoices amounts are
335 posted to. If it is not set then the first chart configured for
336 accounts receivables is used.
340 This function implements several steps:
344 =item 1. It calculates all prices, amounts and taxes by calling
345 L<SL::DB::Helper::PriceTaxCalculator::calculate_prices_and_taxes>.
347 =item 2. A new and unique invoice number is created.
349 =item 3. All amounts for costs of goods sold are recorded in
352 =item 4. All amounts for parts, services and assemblies are recorded
353 in C<acc_trans> with their respective charts. This is determined by
354 the part's buchungsgruppen.
356 =item 5. The total amount is posted to the accounts receivable chart
357 and recorded in C<acc_trans>.
359 =item 6. Items in C<invoice> are updated according to their allocation
360 status (regarding for costs of goold sold). Will only be done if
361 kivitendo is not configured to use Einnahmenüberschussrechnungen.
363 =item 7. The invoice and its items are saved.
367 Returns C<$self> on success and C<undef> on failure. The whole process
368 is run inside a transaction. If it fails then nothing is saved to or
369 changed in the database. A new transaction is only started if none is
372 =item C<basic_info $field>
374 See L<SL::DB::Object::basic_info>.
380 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>