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 => [ 'parts' ]
33 __PACKAGE__->meta->initialize;
37 sub items { goto &invoiceitems; }
38 sub payment_term { goto &payment; }
41 # For compatibility with Order, DeliveryOrder
42 croak 'not an accessor' if @_ > 1;
46 # it is assumed, that ordnumbers are unique here.
47 sub first_order_by_ordnumber {
50 my $orders = SL::DB::Manager::Order->get_all(
52 ordnumber => $self->ordnumber,
57 return first { $_->is_type('sales_order') } @{ $orders };
60 sub abschlag_percentage {
62 my $order = $self->first_order_by_ordnumber or return;
63 my $order_amount = $order->netamount or return;
64 return $self->abschlag
65 ? $self->netamount / $order_amount
71 die 'not a setter method' if @_;
73 return ($self->amount || 0) - ($self->netamount || 0);
76 __PACKAGE__->meta->make_attr_helpers(taxamount => 'numeric(15,5)');
80 return $self->paid >= $self->amount;
84 my ($class, $source, %params) = @_;
86 croak("Unsupported source object type '" . ref($source) . "'") unless ref($source) =~ m/^ SL::DB:: (?: Order | DeliveryOrder ) $/x;
87 croak("Cannot create invoices for purchase records") unless $source->customer_id;
89 my $terms = $source->can('payment_id') && $source->payment_id ? $source->payment_term->terms_netto : 0;
91 my %args = ( map({ ( $_ => $source->$_ ) } qw(customer_id taxincluded shippingpoint shipvia notes intnotes curr salesman_id cusordnumber ordnumber quonumber
92 department_id cp_id language_id payment_id delivery_customer_id delivery_vendor_id taxzone_id shipto_id
93 globalproject_id transaction_description)),
94 transdate => DateTime->today_local,
95 gldate => DateTime->today_local,
96 duedate => DateTime->today_local->add(days => $terms * 1),
101 employee_id => (SL::DB::Manager::Employee->current || SL::DB::Employee->new(id => $source->employee_id))->id,
104 if ($source->type =~ /_order$/) {
105 $args{deliverydate} = $source->reqdate;
106 $args{orddate} = $source->transdate;
108 $args{quodate} = $source->transdate;
111 my $invoice = $class->new(%args, %params);
114 my $source_item = $_;
115 SL::DB::InvoiceItem->new(map({ ( $_ => $source_item->$_ ) }
116 qw(parts_id description qty sellprice discount project_id
117 serialnumber pricegroup_id ordnumber transdate cusordnumber unit
118 base_qty subtotal longdescription lastcost price_factor_id)),
119 deliverydate => $source_item->reqdate,
120 fxsellprice => $source_item->sellprice,);
121 } @{ $source->items };
123 $invoice->invoiceitems(\@items);
129 my ($self, %params) = @_;
131 if (!$params{ar_id}) {
132 my $chart = SL::DB::Manager::Chart->get_all(query => [ SL::DB::Manager::Chart->link_filter('AR') ],
135 croak("No AR chart found and no parameter `ar_id' given") unless $chart;
136 $params{ar_id} = $chart->id;
140 my %data = $self->calculate_prices_and_taxes;
142 $self->_post_create_assemblyitem_entries($data{assembly_items});
143 $self->create_trans_number;
146 $self->_post_add_acctrans($data{amounts_cogs});
147 $self->_post_add_acctrans($data{amounts});
148 $self->_post_add_acctrans($data{taxes});
150 $self->_post_add_acctrans({ $params{ar_id} => $self->amount * -1 });
152 $self->_post_update_allocated($data{allocated});
155 if ($self->db->in_transaction) {
157 } elsif (!$self->db->do_transaction($worker)) {
158 $::lxdebug->message(LXDebug->WARN(), "convert_to_invoice failed: " . join("\n", (split(/\n/, $self->db->error))[0..2]));
165 sub _post_add_acctrans {
166 my ($self, $entries) = @_;
168 while (my ($chart_id, $spec) = each %{ $entries }) {
169 $spec = { taxkey => 0, amount => $spec } unless ref $spec;
170 SL::DB::AccTransaction->new(trans_id => $self->id,
171 chart_id => $chart_id,
172 amount => $spec->{amount},
173 taxkey => $spec->{taxkey},
174 project_id => $self->globalproject_id,
175 transdate => $self->transdate)->save;
179 sub _post_create_assemblyitem_entries {
180 my ($self, $assembly_entries) = @_;
182 my $items = $self->invoiceitems;
186 foreach my $item (@{ $items }) {
187 next if $item->assemblyitem;
189 push @new_items, $item;
192 foreach my $assembly_item (@{ $assembly_entries->[$item_idx] || [ ] }) {
193 push @new_items, SL::DB::InvoiceItem->new(parts_id => $assembly_item->{part},
194 description => $assembly_item->{part}->description,
195 unit => $assembly_item->{part}->unit,
196 qty => $assembly_item->{qty},
197 allocated => $assembly_item->{allocated},
200 assemblyitem => 't');
204 $self->invoiceitems(\@new_items);
207 sub _post_update_allocated {
208 my ($self, $allocated) = @_;
210 while (my ($invoice_id, $diff) = each %{ $allocated }) {
211 SL::DB::Manager::InvoiceItem->update_all(set => { allocated => { sql => "allocated + $diff" } },
212 where => [ id => $invoice_id ]);
219 return 'ar_transaction' if !$self->invoice;
220 return 'credit_note' if $self->type eq 'credit_note' && $self->amount < 0 && !$self->storno;
221 return 'invoice_storno' if $self->type ne 'credit_note' && $self->amount < 0 && $self->storno;
222 return 'credit_note_storno' if $self->type eq 'credit_note' && $self->amount > 0 && $self->storno;
226 sub displayable_state {
229 return $self->closed ? $::locale->text('closed') : $::locale->text('open');
240 SL::DB::Invoice: Rose model for invoices (table "ar")
246 =item C<new_from $source>
248 Creates a new C<SL::DB::Invoice> instance and copies as much
249 information from C<$source> as possible. At the moment only sales
250 orders and sales quotations are supported as sources.
252 The conversion copies order items into invoice items. Dates are copied
253 as appropriate, e.g. the C<transdate> field from an order will be
254 copied into the invoice's C<orddate> field.
256 Amounts, prices and taxes are not
257 calculated. L<SL::DB::Helper::PriceTaxCalculator::calculate_prices_and_taxes>
258 can be used for this.
260 The object returned is not saved.
262 =item C<post %params>
264 Posts the invoice. Required parameters are:
270 The ID of the accounds receivable chart the invoices amounts are
271 posted to. If it is not set then the first chart configured for
272 accounts receivables is used.
276 This function implements several steps:
280 =item 1. It calculates all prices, amounts and taxes by calling
281 L<SL::DB::Helper::PriceTaxCalculator::calculate_prices_and_taxes>.
283 =item 2. A new and unique invoice number is created.
285 =item 3. All amounts for costs of goods sold are recorded in
288 =item 4. All amounts for parts, services and assemblies are recorded
289 in C<acc_trans> with their respective charts. This is determined by
290 the part's buchungsgruppen.
292 =item 5. The total amount is posted to the accounts receivable chart
293 and recorded in C<acc_trans>.
295 =item 6. Items in C<invoice> are updated according to their allocation
296 status (regarding for costs of goold sold). Will only be done if
297 kivitendo is not configured to use Einnahmenüberschussrechnungen.
299 =item 7. The invoice and its items are saved.
303 Returns C<$self> on success and C<undef> on failure. The whole process
304 is run inside a transaction. If it fails then nothing is saved to or
305 changed in the database. A new transaction is only started if none is
308 =item C<basic_info $field>
310 See L<SL::DB::Object::basic_info>.
316 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>