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;
18 use SL::DB::AccTransaction;
22 __PACKAGE__->meta->add_relationship(
24 type => 'one to many',
25 class => 'SL::DB::InvoiceItem',
26 column_map => { id => 'trans_id' },
28 with_objects => [ 'part' ]
32 type => 'one to many',
33 class => 'SL::DB::Invoice',
34 column_map => { id => 'storno_id' },
36 sepa_export_items => {
37 type => 'one to many',
38 class => 'SL::DB::SepaExportItem',
39 column_map => { id => 'ar_id' },
40 manager_args => { with_objects => [ 'sepa_export' ] }
44 __PACKAGE__->meta->initialize;
48 sub items { goto &invoiceitems; }
53 my @sorted = sort {$a->id <=> $b->id } @{ $self->items };
54 return wantarray ? @sorted : \@sorted;
58 # For compatibility with Order, DeliveryOrder
59 croak 'not an accessor' if @_ > 1;
63 # it is assumed, that ordnumbers are unique here.
64 sub first_order_by_ordnumber {
67 my $orders = SL::DB::Manager::Order->get_all(
69 ordnumber => $self->ordnumber,
74 return first { $_->is_type('sales_order') } @{ $orders };
77 sub abschlag_percentage {
79 my $order = $self->first_order_by_ordnumber or return;
80 my $order_amount = $order->netamount or return;
81 return $self->abschlag
82 ? $self->netamount / $order_amount
88 die 'not a setter method' if @_;
90 return ($self->amount || 0) - ($self->netamount || 0);
93 __PACKAGE__->meta->make_attr_helpers(taxamount => 'numeric(15,5)');
97 return $self->paid >= $self->amount;
101 my ($class, $source, %params) = @_;
103 croak("Unsupported source object type '" . ref($source) . "'") unless ref($source) =~ m/^ SL::DB:: (?: Order | DeliveryOrder ) $/x;
104 croak("Cannot create invoices for purchase records") unless $source->customer_id;
106 my $terms = $source->can('payment_id') && $source->payment_id ? $source->payment_terms->terms_netto : 0;
108 my %args = ( map({ ( $_ => $source->$_ ) } qw(customer_id taxincluded shippingpoint shipvia notes intnotes salesman_id cusordnumber ordnumber quonumber
109 department_id cp_id language_id payment_id delivery_customer_id delivery_vendor_id taxzone_id shipto_id
110 globalproject_id transaction_description currency_id)),
111 transdate => DateTime->today_local,
112 gldate => DateTime->today_local,
113 duedate => DateTime->today_local->add(days => $terms * 1),
118 employee_id => (SL::DB::Manager::Employee->current || SL::DB::Employee->new(id => $source->employee_id))->id,
121 if ($source->type =~ /_order$/) {
122 $args{deliverydate} = $source->reqdate;
123 $args{orddate} = $source->transdate;
125 $args{quodate} = $source->transdate;
128 my $invoice = $class->new(%args, %params);
131 my $source_item = $_;
132 SL::DB::InvoiceItem->new(map({ ( $_ => $source_item->$_ ) }
133 qw(parts_id description qty sellprice discount project_id
134 serialnumber pricegroup_id ordnumber transdate cusordnumber unit
135 base_qty subtotal longdescription lastcost price_factor_id)),
136 deliverydate => $source_item->reqdate,
137 fxsellprice => $source_item->sellprice,);
138 } @{ $source->items_sorted };
140 $invoice->invoiceitems(\@items);
146 my ($self, %params) = @_;
148 if (!$params{ar_id}) {
149 my $chart = SL::DB::Manager::Chart->get_all(query => [ SL::DB::Manager::Chart->link_filter('AR') ],
152 croak("No AR chart found and no parameter `ar_id' given") unless $chart;
153 $params{ar_id} = $chart->id;
157 my %data = $self->calculate_prices_and_taxes;
159 $self->_post_create_assemblyitem_entries($data{assembly_items});
160 $self->create_trans_number;
163 $self->_post_add_acctrans($data{amounts_cogs});
164 $self->_post_add_acctrans($data{amounts});
165 $self->_post_add_acctrans($data{taxes});
167 $self->_post_add_acctrans({ $params{ar_id} => $self->amount * -1 });
169 $self->_post_update_allocated($data{allocated});
172 if ($self->db->in_transaction) {
174 } elsif (!$self->db->do_transaction($worker)) {
175 $::lxdebug->message(LXDebug->WARN(), "convert_to_invoice failed: " . join("\n", (split(/\n/, $self->db->error))[0..2]));
182 sub _post_add_acctrans {
183 my ($self, $entries) = @_;
185 my $default_tax_id = SL::DB::Manager::Tax->find_by(taxkey => 0)->id;
188 while (my ($chart_id, $spec) = each %{ $entries }) {
189 $spec = { taxkey => 0, tax_id => $default_tax_id, amount => $spec } unless ref $spec;
190 $chart_link = SL::DB::Manager::Chart->find_by(id => $chart_id)->{'link'};
193 SL::DB::AccTransaction->new(trans_id => $self->id,
194 chart_id => $chart_id,
195 amount => $spec->{amount},
196 tax_id => $spec->{tax_id},
197 taxkey => $spec->{taxkey},
198 project_id => $self->globalproject_id,
199 transdate => $self->transdate,
200 chart_link => $chart_link)->save;
204 sub _post_create_assemblyitem_entries {
205 my ($self, $assembly_entries) = @_;
207 my $items = $self->invoiceitems;
211 foreach my $item (@{ $items }) {
212 next if $item->assemblyitem;
214 push @new_items, $item;
217 foreach my $assembly_item (@{ $assembly_entries->[$item_idx] || [ ] }) {
218 push @new_items, SL::DB::InvoiceItem->new(parts_id => $assembly_item->{part},
219 description => $assembly_item->{part}->description,
220 unit => $assembly_item->{part}->unit,
221 qty => $assembly_item->{qty},
222 allocated => $assembly_item->{allocated},
225 assemblyitem => 't');
229 $self->invoiceitems(\@new_items);
232 sub _post_update_allocated {
233 my ($self, $allocated) = @_;
235 while (my ($invoice_id, $diff) = each %{ $allocated }) {
236 SL::DB::Manager::InvoiceItem->update_all(set => { allocated => { sql => "allocated + $diff" } },
237 where => [ id => $invoice_id ]);
244 return 'ar_transaction' if !$self->invoice;
245 return 'credit_note' if $self->type eq 'credit_note' && $self->amount < 0 && !$self->storno;
246 return 'invoice_storno' if $self->type ne 'credit_note' && $self->amount < 0 && $self->storno;
247 return 'credit_note_storno' if $self->type eq 'credit_note' && $self->amount > 0 && $self->storno;
251 sub displayable_state {
254 return $self->closed ? $::locale->text('closed') : $::locale->text('open');
269 SL::DB::Invoice: Rose model for invoices (table "ar")
275 =item C<new_from $source>
277 Creates a new C<SL::DB::Invoice> instance and copies as much
278 information from C<$source> as possible. At the moment only sales
279 orders and sales quotations are supported as sources.
281 The conversion copies order items into invoice items. Dates are copied
282 as appropriate, e.g. the C<transdate> field from an order will be
283 copied into the invoice's C<orddate> field.
285 Amounts, prices and taxes are not
286 calculated. L<SL::DB::Helper::PriceTaxCalculator::calculate_prices_and_taxes>
287 can be used for this.
289 The object returned is not saved.
291 =item C<post %params>
293 Posts the invoice. Required parameters are:
299 The ID of the accounds receivable chart the invoices amounts are
300 posted to. If it is not set then the first chart configured for
301 accounts receivables is used.
305 This function implements several steps:
309 =item 1. It calculates all prices, amounts and taxes by calling
310 L<SL::DB::Helper::PriceTaxCalculator::calculate_prices_and_taxes>.
312 =item 2. A new and unique invoice number is created.
314 =item 3. All amounts for costs of goods sold are recorded in
317 =item 4. All amounts for parts, services and assemblies are recorded
318 in C<acc_trans> with their respective charts. This is determined by
319 the part's buchungsgruppen.
321 =item 5. The total amount is posted to the accounts receivable chart
322 and recorded in C<acc_trans>.
324 =item 6. Items in C<invoice> are updated according to their allocation
325 status (regarding for costs of goold sold). Will only be done if
326 kivitendo is not configured to use Einnahmenüberschussrechnungen.
328 =item 7. The invoice and its items are saved.
332 Returns C<$self> on success and C<undef> on failure. The whole process
333 is run inside a transaction. If it fails then nothing is saved to or
334 changed in the database. A new transaction is only started if none is
337 =item C<basic_info $field>
339 See L<SL::DB::Object::basic_info>.
345 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>