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' },
38 __PACKAGE__->meta->initialize;
42 sub items { goto &invoiceitems; }
45 # For compatibility with Order, DeliveryOrder
46 croak 'not an accessor' if @_ > 1;
50 # it is assumed, that ordnumbers are unique here.
51 sub first_order_by_ordnumber {
54 my $orders = SL::DB::Manager::Order->get_all(
56 ordnumber => $self->ordnumber,
61 return first { $_->is_type('sales_order') } @{ $orders };
64 sub abschlag_percentage {
66 my $order = $self->first_order_by_ordnumber or return;
67 my $order_amount = $order->netamount or return;
68 return $self->abschlag
69 ? $self->netamount / $order_amount
75 die 'not a setter method' if @_;
77 return ($self->amount || 0) - ($self->netamount || 0);
80 __PACKAGE__->meta->make_attr_helpers(taxamount => 'numeric(15,5)');
84 return $self->paid >= $self->amount;
88 my ($class, $source, %params) = @_;
90 croak("Unsupported source object type '" . ref($source) . "'") unless ref($source) =~ m/^ SL::DB:: (?: Order | DeliveryOrder ) $/x;
91 croak("Cannot create invoices for purchase records") unless $source->customer_id;
93 my $terms = $source->can('payment_id') && $source->payment_id ? $source->payment_term->terms_netto : 0;
95 my %args = ( map({ ( $_ => $source->$_ ) } qw(customer_id taxincluded shippingpoint shipvia notes intnotes curr salesman_id cusordnumber ordnumber quonumber
96 department_id cp_id language_id payment_id delivery_customer_id delivery_vendor_id taxzone_id shipto_id
97 globalproject_id transaction_description)),
98 transdate => DateTime->today_local,
99 gldate => DateTime->today_local,
100 duedate => DateTime->today_local->add(days => $terms * 1),
105 employee_id => (SL::DB::Manager::Employee->current || SL::DB::Employee->new(id => $source->employee_id))->id,
108 if ($source->type =~ /_order$/) {
109 $args{deliverydate} = $source->reqdate;
110 $args{orddate} = $source->transdate;
112 $args{quodate} = $source->transdate;
115 my $invoice = $class->new(%args, %params);
118 my $source_item = $_;
119 SL::DB::InvoiceItem->new(map({ ( $_ => $source_item->$_ ) }
120 qw(parts_id description qty sellprice discount project_id
121 serialnumber pricegroup_id ordnumber transdate cusordnumber unit
122 base_qty subtotal longdescription lastcost price_factor_id)),
123 deliverydate => $source_item->reqdate,
124 fxsellprice => $source_item->sellprice,);
125 } @{ $source->items };
127 $invoice->invoiceitems(\@items);
133 my ($self, %params) = @_;
135 if (!$params{ar_id}) {
136 my $chart = SL::DB::Manager::Chart->get_all(query => [ SL::DB::Manager::Chart->link_filter('AR') ],
139 croak("No AR chart found and no parameter `ar_id' given") unless $chart;
140 $params{ar_id} = $chart->id;
144 my %data = $self->calculate_prices_and_taxes;
146 $self->_post_create_assemblyitem_entries($data{assembly_items});
147 $self->create_trans_number;
150 $self->_post_add_acctrans($data{amounts_cogs});
151 $self->_post_add_acctrans($data{amounts});
152 $self->_post_add_acctrans($data{taxes});
154 $self->_post_add_acctrans({ $params{ar_id} => $self->amount * -1 });
156 $self->_post_update_allocated($data{allocated});
159 if ($self->db->in_transaction) {
161 } elsif (!$self->db->do_transaction($worker)) {
162 $::lxdebug->message(LXDebug->WARN(), "convert_to_invoice failed: " . join("\n", (split(/\n/, $self->db->error))[0..2]));
169 sub _post_add_acctrans {
170 my ($self, $entries) = @_;
172 while (my ($chart_id, $spec) = each %{ $entries }) {
173 $spec = { taxkey => 0, amount => $spec } unless ref $spec;
174 SL::DB::AccTransaction->new(trans_id => $self->id,
175 chart_id => $chart_id,
176 amount => $spec->{amount},
177 taxkey => $spec->{taxkey},
178 project_id => $self->globalproject_id,
179 transdate => $self->transdate)->save;
183 sub _post_create_assemblyitem_entries {
184 my ($self, $assembly_entries) = @_;
186 my $items = $self->invoiceitems;
190 foreach my $item (@{ $items }) {
191 next if $item->assemblyitem;
193 push @new_items, $item;
196 foreach my $assembly_item (@{ $assembly_entries->[$item_idx] || [ ] }) {
197 push @new_items, SL::DB::InvoiceItem->new(parts_id => $assembly_item->{part},
198 description => $assembly_item->{part}->description,
199 unit => $assembly_item->{part}->unit,
200 qty => $assembly_item->{qty},
201 allocated => $assembly_item->{allocated},
204 assemblyitem => 't');
208 $self->invoiceitems(\@new_items);
211 sub _post_update_allocated {
212 my ($self, $allocated) = @_;
214 while (my ($invoice_id, $diff) = each %{ $allocated }) {
215 SL::DB::Manager::InvoiceItem->update_all(set => { allocated => { sql => "allocated + $diff" } },
216 where => [ id => $invoice_id ]);
223 return 'ar_transaction' if !$self->invoice;
224 return 'credit_note' if $self->type eq 'credit_note' && $self->amount < 0 && !$self->storno;
225 return 'invoice_storno' if $self->type ne 'credit_note' && $self->amount < 0 && $self->storno;
226 return 'credit_note_storno' if $self->type eq 'credit_note' && $self->amount > 0 && $self->storno;
230 sub displayable_state {
233 return $self->closed ? $::locale->text('closed') : $::locale->text('open');
244 SL::DB::Invoice: Rose model for invoices (table "ar")
250 =item C<new_from $source>
252 Creates a new C<SL::DB::Invoice> instance and copies as much
253 information from C<$source> as possible. At the moment only sales
254 orders and sales quotations are supported as sources.
256 The conversion copies order items into invoice items. Dates are copied
257 as appropriate, e.g. the C<transdate> field from an order will be
258 copied into the invoice's C<orddate> field.
260 Amounts, prices and taxes are not
261 calculated. L<SL::DB::Helper::PriceTaxCalculator::calculate_prices_and_taxes>
262 can be used for this.
264 The object returned is not saved.
266 =item C<post %params>
268 Posts the invoice. Required parameters are:
274 The ID of the accounds receivable chart the invoices amounts are
275 posted to. If it is not set then the first chart configured for
276 accounts receivables is used.
280 This function implements several steps:
284 =item 1. It calculates all prices, amounts and taxes by calling
285 L<SL::DB::Helper::PriceTaxCalculator::calculate_prices_and_taxes>.
287 =item 2. A new and unique invoice number is created.
289 =item 3. All amounts for costs of goods sold are recorded in
292 =item 4. All amounts for parts, services and assemblies are recorded
293 in C<acc_trans> with their respective charts. This is determined by
294 the part's buchungsgruppen.
296 =item 5. The total amount is posted to the accounts receivable chart
297 and recorded in C<acc_trans>.
299 =item 6. Items in C<invoice> are updated according to their allocation
300 status (regarding for costs of goold sold). Will only be done if
301 kivitendo is not configured to use Einnahmenüberschussrechnungen.
303 =item 7. The invoice and its items are saved.
307 Returns C<$self> on success and C<undef> on failure. The whole process
308 is run inside a transaction. If it fails then nothing is saved to or
309 changed in the database. A new transaction is only started if none is
312 =item C<basic_info $field>
314 See L<SL::DB::Object::basic_info>.
320 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>