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::Helper::CustomVariables (
19 sub_module => 'orderitems',
22 parts_id => 'SL::DB::Part',
25 use SL::DB::AccTransaction;
29 __PACKAGE__->meta->add_relationship(
31 type => 'one to many',
32 class => 'SL::DB::InvoiceItem',
33 column_map => { id => 'trans_id' },
35 with_objects => [ 'part' ]
40 class => 'SL::DB::PaymentTerm',
41 column_map => { payment_id => 'id' },
45 class => 'SL::DB::Contact',
46 column_map => { cp_id => 'cp_id' },
50 class => 'SL::DB::Shipto',
51 column_map => { shipto_id => 'shipto_id' },
55 class => 'SL::DB::Department',
56 column_map => { department_id => 'id' },
60 class => 'SL::DB::Language',
61 column_map => { language_id => 'id' },
65 class => 'SL::DB::Employee',
66 column_map => { employee_id => 'id' },
70 __PACKAGE__->meta->initialize;
74 sub items { goto &invoiceitems; }
76 # it is assumed, that ordnumbers are unique here.
77 sub first_order_by_ordnumber {
80 my $orders = SL::DB::Manager::Order->get_all(
82 ordnumber => $self->ordnumber,
87 return first { $_->is_type('sales_order') } @{ $orders };
90 sub abschlag_percentage {
92 my $order = $self->first_order_by_ordnumber or return;
93 my $order_amount = $order->netamount or return;
94 return $self->abschlag
95 ? $self->netamount / $order_amount
101 die 'not a setter method' if @_;
103 return $self->amount - $self->netamount;
106 __PACKAGE__->meta->make_attr_helpers(taxamount => 'numeric(15,5)');
110 return $self->paid >= $self->amount;
114 my ($class, $source, %params) = @_;
116 croak("Unsupported source object type '" . ref($source) . "'") unless ref($source) =~ m/^ SL::DB:: (?: Order | DeliveryOrder ) $/x;
117 croak("Cannot create invoices for purchase records") unless $source->customer_id;
119 my $terms = $source->can('payment_id') && $source->payment_id ? $source->payment_term->terms_netto : 0;
121 my %args = ( map({ ( $_ => $source->$_ ) } qw(customer_id taxincluded shippingpoint shipvia notes intnotes curr salesman_id cusordnumber ordnumber quonumber
122 department_id cp_id language_id payment_id delivery_customer_id delivery_vendor_id taxzone_id shipto_id
123 globalproject_id transaction_description)),
124 transdate => DateTime->today_local,
125 gldate => DateTime->today_local,
126 duedate => DateTime->today_local->add(days => $terms * 1),
131 employee_id => (SL::DB::Manager::Employee->current || SL::DB::Employee->new(id => $source->employee_id))->id,
134 if ($source->type =~ /_order$/) {
135 $args{deliverydate} = $source->reqdate;
136 $args{orddate} = $source->transdate;
138 $args{quodate} = $source->transdate;
141 my $invoice = $class->new(%args, %params);
144 my $source_item = $_;
145 SL::DB::InvoiceItem->new(map({ ( $_ => $source_item->$_ ) }
146 qw(parts_id description qty sellprice discount project_id
147 serialnumber pricegroup_id ordnumber transdate cusordnumber unit
148 base_qty subtotal longdescription lastcost price_factor_id)),
149 deliverydate => $source_item->reqdate,
150 fxsellprice => $source_item->sellprice,);
151 } @{ $source->items };
153 $invoice->invoiceitems(\@items);
159 my ($self, %params) = @_;
161 if (!$params{ar_id}) {
162 my $chart = SL::DB::Manager::Chart->get_all(query => [ SL::DB::Manager::Chart->link_filter('AR') ],
165 croak("No AR chart found and no parameter `ar_id' given") unless $chart;
166 $params{ar_id} = $chart->id;
170 my %data = $self->calculate_prices_and_taxes;
172 $self->_post_create_assemblyitem_entries($data{assembly_items});
173 $self->create_trans_number;
176 $self->_post_add_acctrans($data{amounts_cogs});
177 $self->_post_add_acctrans($data{amounts});
178 $self->_post_add_acctrans($data{taxes});
180 $self->_post_add_acctrans({ $params{ar_id} => $self->amount * -1 });
182 $self->_post_update_allocated($data{allocated});
185 if ($self->db->in_transaction) {
187 } elsif (!$self->db->do_transaction($worker)) {
188 $::lxdebug->message(LXDebug->WARN(), "convert_to_invoice failed: " . join("\n", (split(/\n/, $self->db->error))[0..2]));
195 sub _post_add_acctrans {
196 my ($self, $entries) = @_;
198 while (my ($chart_id, $spec) = each %{ $entries }) {
199 $spec = { taxkey => 0, amount => $spec } unless ref $spec;
200 SL::DB::AccTransaction->new(trans_id => $self->id,
201 chart_id => $chart_id,
202 amount => $spec->{amount},
203 taxkey => $spec->{taxkey},
204 project_id => $self->globalproject_id,
205 transdate => $self->transdate)->save;
209 sub _post_create_assemblyitem_entries {
210 my ($self, $assembly_entries) = @_;
212 my $items = $self->invoiceitems;
216 foreach my $item (@{ $items }) {
217 next if $item->assemblyitem;
219 push @new_items, $item;
222 foreach my $assembly_item (@{ $assembly_entries->[$item_idx] || [ ] }) {
223 push @new_items, SL::DB::InvoiceItem->new(parts_id => $assembly_item->{part},
224 description => $assembly_item->{part}->description,
225 unit => $assembly_item->{part}->unit,
226 qty => $assembly_item->{qty},
227 allocated => $assembly_item->{allocated},
230 assemblyitem => 't');
234 $self->invoiceitems(\@new_items);
237 sub _post_update_allocated {
238 my ($self, $allocated) = @_;
240 while (my ($invoice_id, $diff) = each %{ $allocated }) {
241 SL::DB::Manager::InvoiceItem->update_all(set => { allocated => { sql => "allocated + $diff" } },
242 where => [ id => $invoice_id ]);
254 SL::DB::Invoice: Rose model for invoices (table "ar")
260 =item C<new_from $source>
262 Creates a new C<SL::DB::Invoice> instance and copies as much
263 information from C<$source> as possible. At the moment only sales
264 orders and sales quotations are supported as sources.
266 The conversion copies order items into invoice items. Dates are copied
267 as appropriate, e.g. the C<transdate> field from an order will be
268 copied into the invoice's C<orddate> field.
270 Amounts, prices and taxes are not
271 calculated. L<SL::DB::Helper::PriceTaxCalculator::calculate_prices_and_taxes>
272 can be used for this.
274 The object returned is not saved.
276 =item C<post %params>
278 Posts the invoice. Required parameters are:
284 The ID of the accounds receivable chart the invoices amounts are
285 posted to. If it is not set then the first chart configured for
286 accounts receivables is used.
290 This function implements several steps:
294 =item 1. It calculates all prices, amounts and taxes by calling
295 L<SL::DB::Helper::PriceTaxCalculator::calculate_prices_and_taxes>.
297 =item 2. A new and unique invoice number is created.
299 =item 3. All amounts for costs of goods sold are recorded in
302 =item 4. All amounts for parts, services and assemblies are recorded
303 in C<acc_trans> with their respective charts. This is determined by
304 the part's buchungsgruppen.
306 =item 5. The total amount is posted to the accounts receivable chart
307 and recorded in C<acc_trans>.
309 =item 6. Items in C<invoice> are updated according to their allocation
310 status (regarding for costs of goold sold). Will only be done if
311 Lx-Office is not configured to use Einnahmenüberschussrechnungen
312 (see config/lx_office.conf, section "system", variable "eur").
314 =item 7. The invoice and its items are saved.
318 Returns C<$self> on success and C<undef> on failure. The whole process
319 is run inside a transaction. If it fails then nothing is saved to or
320 changed in the database. A new transaction is only started if none is
323 =item C<basic_info $field>
325 See L<SL::DB::Object::basic_info>.
331 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>