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 class => 'SL::DB::PaymentTerm',
34 column_map => { payment_id => 'id' },
38 class => 'SL::DB::Contact',
39 column_map => { cp_id => 'cp_id' },
43 class => 'SL::DB::Shipto',
44 column_map => { shipto_id => 'shipto_id' },
48 class => 'SL::DB::Department',
49 column_map => { department_id => 'id' },
53 class => 'SL::DB::Language',
54 column_map => { language_id => 'id' },
58 class => 'SL::DB::Employee',
59 column_map => { employee_id => 'id' },
63 __PACKAGE__->meta->initialize;
67 sub items { goto &invoiceitems; }
69 # it is assumed, that ordnumbers are unique here.
70 sub first_order_by_ordnumber {
73 my $orders = SL::DB::Manager::Order->get_all(
75 ordnumber => $self->ordnumber,
80 return first { $_->is_type('sales_order') } @{ $orders };
83 sub abschlag_percentage {
85 my $order = $self->first_order_by_ordnumber or return;
86 my $order_amount = $order->netamount or return;
87 return $self->abschlag
88 ? $self->netamount / $order_amount
94 die 'not a setter method' if @_;
96 return $self->amount - $self->netamount;
99 __PACKAGE__->meta->make_attr_helpers(taxamount => 'numeric(15,5)');
103 return $self->paid >= $self->amount;
107 my ($class, $source, %params) = @_;
109 croak("Unsupported source object type '" . ref($source) . "'") unless ref($source) =~ m/^ SL::DB:: (?: Order | DeliveryOrder ) $/x;
110 croak("Cannot create invoices for purchase records") unless $source->customer_id;
112 my $terms = $source->can('payment_id') && $source->payment_id ? $source->payment_term->terms_netto : 0;
114 my %args = ( map({ ( $_ => $source->$_ ) } qw(customer_id taxincluded shippingpoint shipvia notes intnotes curr salesman_id cusordnumber ordnumber quonumber
115 department_id cp_id language_id payment_id delivery_customer_id delivery_vendor_id taxzone_id shipto_id
116 globalproject_id transaction_description)),
117 transdate => DateTime->today_local,
118 gldate => DateTime->today_local,
119 duedate => DateTime->today_local->add(days => $terms * 1),
124 employee_id => (SL::DB::Manager::Employee->current || SL::DB::Employee->new(id => $source->employee_id))->id,
127 if ($source->type =~ /_order$/) {
128 $args{deliverydate} = $source->reqdate;
129 $args{orddate} = $source->transdate;
131 $args{quodate} = $source->transdate;
134 my $invoice = $class->new(%args, %params);
137 my $source_item = $_;
138 SL::DB::InvoiceItem->new(map({ ( $_ => $source_item->$_ ) }
139 qw(parts_id description qty sellprice discount project_id
140 serialnumber pricegroup_id ordnumber transdate cusordnumber unit
141 base_qty subtotal longdescription lastcost price_factor_id)),
142 deliverydate => $source_item->reqdate,
143 fxsellprice => $source_item->sellprice,);
144 } @{ $source->items };
146 $invoice->invoiceitems(\@items);
152 my ($self, %params) = @_;
154 if (!$params{ar_id}) {
155 my $chart = SL::DB::Manager::Chart->get_all(query => [ SL::DB::Manager::Chart->link_filter('AR') ],
158 croak("No AR chart found and no parameter `ar_id' given") unless $chart;
159 $params{ar_id} = $chart->id;
163 my %data = $self->calculate_prices_and_taxes;
165 $self->_post_create_assemblyitem_entries($data{assembly_items});
166 $self->create_trans_number;
169 $self->_post_add_acctrans($data{amounts_cogs});
170 $self->_post_add_acctrans($data{amounts});
171 $self->_post_add_acctrans($data{taxes});
173 $self->_post_add_acctrans({ $params{ar_id} => $self->amount * -1 });
175 $self->_post_update_allocated($data{allocated});
178 if ($self->db->in_transaction) {
180 } elsif (!$self->db->do_transaction($worker)) {
181 $::lxdebug->message(0, "convert_to_invoice failed: " . join("\n", (split(/\n/, $self->db->error))[0..2]));
188 sub _post_add_acctrans {
189 my ($self, $entries) = @_;
191 while (my ($chart_id, $spec) = each %{ $entries }) {
192 $spec = { taxkey => 0, amount => $spec } unless ref $spec;
193 SL::DB::AccTransaction->new(trans_id => $self->id,
194 chart_id => $chart_id,
195 amount => $spec->{amount},
196 taxkey => $spec->{taxkey},
197 project_id => $self->globalproject_id,
198 transdate => $self->transdate)->save;
202 sub _post_create_assemblyitem_entries {
203 my ($self, $assembly_entries) = @_;
205 my $items = $self->invoiceitems;
209 foreach my $item (@{ $items }) {
210 next if $item->assemblyitem;
212 push @new_items, $item;
215 foreach my $assembly_item (@{ $assembly_entries->[$item_idx] || [ ] }) {
216 push @new_items, SL::DB::InvoiceItem->new(parts_id => $assembly_item->{part},
217 description => $assembly_item->{part}->description,
218 unit => $assembly_item->{part}->unit,
219 qty => $assembly_item->{qty},
220 allocated => $assembly_item->{allocated},
223 assemblyitem => 't');
227 $self->invoiceitems(\@new_items);
230 sub _post_update_allocated {
231 my ($self, $allocated) = @_;
233 while (my ($invoice_id, $diff) = each %{ $allocated }) {
234 SL::DB::Manager::InvoiceItem->update_all(set => { allocated => { sql => "allocated + $diff" } },
235 where => [ id => $invoice_id ]);
247 SL::DB::Invoice: Rose model for invoices (table "ar")
253 =item C<new_from $source>
255 Creates a new C<SL::DB::Invoice> instance and copies as much
256 information from C<$source> as possible. At the moment only sales
257 orders and sales quotations are supported as sources.
259 The conversion copies order items into invoice items. Dates are copied
260 as appropriate, e.g. the C<transdate> field from an order will be
261 copied into the invoice's C<orddate> field.
263 Amounts, prices and taxes are not
264 calculated. L<SL::DB::Helper::PriceTaxCalculator::calculate_prices_and_taxes>
265 can be used for this.
267 The object returned is not saved.
269 =item C<post %params>
271 Posts the invoice. Required parameters are:
277 The ID of the accounds receivable chart the invoices amounts are
278 posted to. If it is not set then the first chart configured for
279 accounts receivables is used.
283 This function implements several steps:
287 =item 1. It calculates all prices, amounts and taxes by calling
288 L<SL::DB::Helper::PriceTaxCalculator::calculate_prices_and_taxes>.
290 =item 2. A new and unique invoice number is created.
292 =item 3. All amounts for costs of goods sold are recorded in
295 =item 4. All amounts for parts, services and assemblies are recorded
296 in C<acc_trans> with their respective charts. This is determined by
297 the part's buchungsgruppen.
299 =item 5. The total amount is posted to the accounts receivable chart
300 and recorded in C<acc_trans>.
302 =item 6. Items in C<invoice> are updated according to their allocation
303 status (regarding for costs of goold sold). Will only be done if
304 Lx-Office is not configured to use Einnahmenüberschussrechnungen
305 (see config/lx_office.conf, section "system", variable "eur").
307 =item 7. The invoice and its items are saved.
311 Returns C<$self> on success and C<undef> on failure. The whole process
312 is run inside a transaction. If it fails then nothing is saved to or
313 changed in the database. A new transaction is only started if none is
316 =item C<basic_info $field>
318 See L<SL::DB::Object::basic_info>.
324 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>