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' ]
33 __PACKAGE__->meta->initialize;
37 sub items { goto &invoiceitems; }
40 # For compatibility with Order, DeliveryOrder
41 croak 'not an accessor' if @_ > 1;
45 # it is assumed, that ordnumbers are unique here.
46 sub first_order_by_ordnumber {
49 my $orders = SL::DB::Manager::Order->get_all(
51 ordnumber => $self->ordnumber,
56 return first { $_->is_type('sales_order') } @{ $orders };
59 sub abschlag_percentage {
61 my $order = $self->first_order_by_ordnumber or return;
62 my $order_amount = $order->netamount or return;
63 return $self->abschlag
64 ? $self->netamount / $order_amount
70 die 'not a setter method' if @_;
72 return ($self->amount || 0) - ($self->netamount || 0);
75 __PACKAGE__->meta->make_attr_helpers(taxamount => 'numeric(15,5)');
79 return $self->paid >= $self->amount;
83 my ($class, $source, %params) = @_;
85 croak("Unsupported source object type '" . ref($source) . "'") unless ref($source) =~ m/^ SL::DB:: (?: Order | DeliveryOrder ) $/x;
86 croak("Cannot create invoices for purchase records") unless $source->customer_id;
88 my $terms = $source->can('payment_id') && $source->payment_id ? $source->payment_term->terms_netto : 0;
90 my %args = ( map({ ( $_ => $source->$_ ) } qw(customer_id taxincluded shippingpoint shipvia notes intnotes curr salesman_id cusordnumber ordnumber quonumber
91 department_id cp_id language_id payment_id delivery_customer_id delivery_vendor_id taxzone_id shipto_id
92 globalproject_id transaction_description)),
93 transdate => DateTime->today_local,
94 gldate => DateTime->today_local,
95 duedate => DateTime->today_local->add(days => $terms * 1),
100 employee_id => (SL::DB::Manager::Employee->current || SL::DB::Employee->new(id => $source->employee_id))->id,
103 if ($source->type =~ /_order$/) {
104 $args{deliverydate} = $source->reqdate;
105 $args{orddate} = $source->transdate;
107 $args{quodate} = $source->transdate;
110 my $invoice = $class->new(%args, %params);
113 my $source_item = $_;
114 SL::DB::InvoiceItem->new(map({ ( $_ => $source_item->$_ ) }
115 qw(parts_id description qty sellprice discount project_id
116 serialnumber pricegroup_id ordnumber transdate cusordnumber unit
117 base_qty subtotal longdescription lastcost price_factor_id)),
118 deliverydate => $source_item->reqdate,
119 fxsellprice => $source_item->sellprice,);
120 } @{ $source->items };
122 $invoice->invoiceitems(\@items);
128 my ($self, %params) = @_;
130 if (!$params{ar_id}) {
131 my $chart = SL::DB::Manager::Chart->get_all(query => [ SL::DB::Manager::Chart->link_filter('AR') ],
134 croak("No AR chart found and no parameter `ar_id' given") unless $chart;
135 $params{ar_id} = $chart->id;
139 my %data = $self->calculate_prices_and_taxes;
141 $self->_post_create_assemblyitem_entries($data{assembly_items});
142 $self->create_trans_number;
145 $self->_post_add_acctrans($data{amounts_cogs});
146 $self->_post_add_acctrans($data{amounts});
147 $self->_post_add_acctrans($data{taxes});
149 $self->_post_add_acctrans({ $params{ar_id} => $self->amount * -1 });
151 $self->_post_update_allocated($data{allocated});
154 if ($self->db->in_transaction) {
156 } elsif (!$self->db->do_transaction($worker)) {
157 $::lxdebug->message(LXDebug->WARN(), "convert_to_invoice failed: " . join("\n", (split(/\n/, $self->db->error))[0..2]));
164 sub _post_add_acctrans {
165 my ($self, $entries) = @_;
167 while (my ($chart_id, $spec) = each %{ $entries }) {
168 $spec = { taxkey => 0, amount => $spec } unless ref $spec;
169 SL::DB::AccTransaction->new(trans_id => $self->id,
170 chart_id => $chart_id,
171 amount => $spec->{amount},
172 taxkey => $spec->{taxkey},
173 project_id => $self->globalproject_id,
174 transdate => $self->transdate)->save;
178 sub _post_create_assemblyitem_entries {
179 my ($self, $assembly_entries) = @_;
181 my $items = $self->invoiceitems;
185 foreach my $item (@{ $items }) {
186 next if $item->assemblyitem;
188 push @new_items, $item;
191 foreach my $assembly_item (@{ $assembly_entries->[$item_idx] || [ ] }) {
192 push @new_items, SL::DB::InvoiceItem->new(parts_id => $assembly_item->{part},
193 description => $assembly_item->{part}->description,
194 unit => $assembly_item->{part}->unit,
195 qty => $assembly_item->{qty},
196 allocated => $assembly_item->{allocated},
199 assemblyitem => 't');
203 $self->invoiceitems(\@new_items);
206 sub _post_update_allocated {
207 my ($self, $allocated) = @_;
209 while (my ($invoice_id, $diff) = each %{ $allocated }) {
210 SL::DB::Manager::InvoiceItem->update_all(set => { allocated => { sql => "allocated + $diff" } },
211 where => [ id => $invoice_id ]);
218 return 'ar_transaction' if !$self->invoice;
219 return 'credit_note' if $self->type eq 'credit_note' && $self->amount < 0 && !$self->storno;
220 return 'invoice_storno' if $self->type ne 'credit_note' && $self->amount < 0 && $self->storno;
221 return 'credit_note_storno' if $self->type eq 'credit_note' && $self->amount > 0 && $self->storno;
225 sub displayable_state {
228 return $self->closed ? $::locale->text('closed') : $::locale->text('open');
239 SL::DB::Invoice: Rose model for invoices (table "ar")
245 =item C<new_from $source>
247 Creates a new C<SL::DB::Invoice> instance and copies as much
248 information from C<$source> as possible. At the moment only sales
249 orders and sales quotations are supported as sources.
251 The conversion copies order items into invoice items. Dates are copied
252 as appropriate, e.g. the C<transdate> field from an order will be
253 copied into the invoice's C<orddate> field.
255 Amounts, prices and taxes are not
256 calculated. L<SL::DB::Helper::PriceTaxCalculator::calculate_prices_and_taxes>
257 can be used for this.
259 The object returned is not saved.
261 =item C<post %params>
263 Posts the invoice. Required parameters are:
269 The ID of the accounds receivable chart the invoices amounts are
270 posted to. If it is not set then the first chart configured for
271 accounts receivables is used.
275 This function implements several steps:
279 =item 1. It calculates all prices, amounts and taxes by calling
280 L<SL::DB::Helper::PriceTaxCalculator::calculate_prices_and_taxes>.
282 =item 2. A new and unique invoice number is created.
284 =item 3. All amounts for costs of goods sold are recorded in
287 =item 4. All amounts for parts, services and assemblies are recorded
288 in C<acc_trans> with their respective charts. This is determined by
289 the part's buchungsgruppen.
291 =item 5. The total amount is posted to the accounts receivable chart
292 and recorded in C<acc_trans>.
294 =item 6. Items in C<invoice> are updated according to their allocation
295 status (regarding for costs of goold sold). Will only be done if
296 kivitendo is not configured to use Einnahmenüberschussrechnungen.
298 =item 7. The invoice and its items are saved.
302 Returns C<$self> on success and C<undef> on failure. The whole process
303 is run inside a transaction. If it fails then nothing is saved to or
304 changed in the database. A new transaction is only started if none is
307 =item C<basic_info $field>
309 See L<SL::DB::Object::basic_info>.
315 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>