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::LinkedRecords;
14 use SL::DB::Helper::PriceTaxCalculator;
15 use SL::DB::Helper::TransNumberGenerator;
16 use SL::DB::AccTransaction;
20 __PACKAGE__->meta->add_relationship(
22 type => 'one to many',
23 class => 'SL::DB::InvoiceItem',
24 column_map => { id => 'trans_id' },
26 with_objects => [ 'part' ]
31 class => 'SL::DB::PaymentTerm',
32 column_map => { payment_id => 'id' },
36 __PACKAGE__->meta->initialize;
40 sub items { goto &invoiceitems; }
42 # it is assumed, that ordnumbers are unique here.
43 sub first_order_by_ordnumber {
46 my $orders = SL::DB::Manager::Order->get_all(
48 ordnumber => $self->ordnumber,
53 return first { $_->is_type('sales_order') } @{ $orders };
56 sub abschlag_percentage {
58 my $order = $self->first_order_by_ordnumber or return;
59 my $order_amount = $order->netamount or return;
60 return $self->abschlag
61 ? $self->netamount / $order_amount
67 die 'not a setter method' if @_;
69 return $self->amount - $self->netamount;
72 __PACKAGE__->meta->make_attr_helpers(taxamount => 'numeric(15,5)');
76 return $self->paid >= $self->amount;
80 my ($class, $source, %params) = @_;
82 croak("Unsupported source object type '" . ref($source) . "'") unless ref($source) =~ m/^ SL::DB:: (?: Order | DeliveryOrder ) $/x;
83 croak("Cannot create invoices for purchase records") unless $source->customer_id;
85 my $terms = $source->can('payment_id') && $source->payment_id ? $source->payment_term->terms_netto : 0;
87 my %args = ( map({ ( $_ => $source->$_ ) } qw(customer_id taxincluded shippingpoint shipvia notes intnotes curr salesman_id cusordnumber ordnumber quonumber
88 department_id cp_id language_id payment_id delivery_customer_id delivery_vendor_id taxzone_id shipto_id
89 globalproject_id transaction_description)),
90 transdate => DateTime->today_local,
91 gldate => DateTime->today_local,
92 duedate => DateTime->today_local->add(days => $terms * 1),
97 employee_id => (SL::DB::Manager::Employee->current || SL::DB::Employee->new(id => $source->employee_id))->id,
100 if ($source->type =~ /_order$/) {
101 $args{deliverydate} = $source->reqdate;
102 $args{orddate} = $source->transdate;
104 $args{quodate} = $source->transdate;
107 my $invoice = $class->new(%args, %params);
110 my $source_item = $_;
111 SL::DB::InvoiceItem->new(map({ ( $_ => $source_item->$_ ) }
112 qw(parts_id description qty sellprice discount project_id
113 serialnumber pricegroup_id ordnumber transdate cusordnumber unit
114 base_qty subtotal longdescription lastcost price_factor_id)),
115 deliverydate => $source_item->reqdate,
116 fxsellprice => $source_item->sellprice,);
117 } @{ $source->items };
119 $invoice->invoiceitems(\@items);
125 my ($self, %params) = @_;
127 if (!$params{ar_id}) {
128 my $chart = SL::DB::Manager::Chart->get_all(query => [ SL::DB::Manager::Chart->link_filter('AR') ],
131 croak("No AR chart found and no parameter `ar_id' given") unless $chart;
132 $params{ar_id} = $chart->id;
136 my %data = $self->calculate_prices_and_taxes;
138 $self->_post_create_assemblyitem_entries($data{assembly_items});
139 $self->create_trans_number;
142 $self->_post_add_acctrans($data{amounts_cogs});
143 $self->_post_add_acctrans($data{amounts});
144 $self->_post_add_acctrans($data{taxes});
146 $self->_post_add_acctrans({ $params{ar_id} => $self->amount * -1 });
148 $self->_post_update_allocated($data{allocated});
151 if ($self->db->in_transaction) {
153 } elsif (!$self->db->do_transaction($worker)) {
154 $::lxdebug->message(0, "convert_to_invoice failed: " . join("\n", (split(/\n/, $self->db->error))[0..2]));
161 sub _post_add_acctrans {
162 my ($self, $entries) = @_;
164 while (my ($chart_id, $spec) = each %{ $entries }) {
165 $spec = { taxkey => 0, amount => $spec } unless ref $spec;
166 SL::DB::AccTransaction->new(trans_id => $self->id,
167 chart_id => $chart_id,
168 amount => $spec->{amount},
169 taxkey => $spec->{taxkey},
170 project_id => $self->globalproject_id,
171 transdate => $self->transdate)->save;
175 sub _post_create_assemblyitem_entries {
176 my ($self, $assembly_entries) = @_;
178 my $items = $self->invoiceitems;
182 foreach my $item (@{ $items }) {
183 next if $item->assemblyitem;
185 push @new_items, $item;
188 foreach my $assembly_item (@{ $assembly_entries->[$item_idx] || [ ] }) {
189 push @new_items, SL::DB::InvoiceItem->new(parts_id => $assembly_item->{part},
190 description => $assembly_item->{part}->description,
191 unit => $assembly_item->{part}->unit,
192 qty => $assembly_item->{qty},
193 allocated => $assembly_item->{allocated},
196 assemblyitem => 't');
200 $self->invoiceitems(\@new_items);
203 sub _post_update_allocated {
204 my ($self, $allocated) = @_;
206 while (my ($invoice_id, $diff) = each %{ $allocated }) {
207 SL::DB::Manager::InvoiceItem->update_all(set => { allocated => { sql => "allocated + $diff" } },
208 where => [ id => $invoice_id ]);
220 SL::DB::Invoice: Rose model for invoices (table "ar")
226 =item C<new_from $source>
228 Creates a new C<SL::DB::Invoice> instance and copies as much
229 information from C<$source> as possible. At the moment only sales
230 orders and sales quotations are supported as sources.
232 The conversion copies order items into invoice items. Dates are copied
233 as appropriate, e.g. the C<transdate> field from an order will be
234 copied into the invoice's C<orddate> field.
236 Amounts, prices and taxes are not
237 calculated. L<SL::DB::Helper::PriceTaxCalculator::calculate_prices_and_taxes>
238 can be used for this.
240 The object returned is not saved.
242 =item C<post %params>
244 Posts the invoice. Required parameters are:
250 The ID of the accounds receivable chart the invoices amounts are
251 posted to. If it is not set then the first chart configured for
252 accounts receivables is used.
256 This function implements several steps:
260 =item 1. It calculates all prices, amounts and taxes by calling
261 L<SL::DB::Helper::PriceTaxCalculator::calculate_prices_and_taxes>.
263 =item 2. A new and unique invoice number is created.
265 =item 3. All amounts for costs of goods sold are recorded in
268 =item 4. All amounts for parts, services and assemblies are recorded
269 in C<acc_trans> with their respective charts. This is determined by
270 the part's buchungsgruppen.
272 =item 5. The total amount is posted to the accounts receivable chart
273 and recorded in C<acc_trans>.
275 =item 6. Items in C<invoice> are updated according to their allocation
276 status (regarding for costs of goold sold). Will only be done if
277 Lx-Office is not configured to use Einnahmenüberschussrechnungen
280 =item 7. The invoice and its items are saved.
284 Returns C<$self> on success and C<undef> on failure. The whole process
285 is run inside a transaction. If it fails then nothing is saved to or
286 changed in the database. A new transaction is only started if none is
289 =item C<basic_info $field>
291 See L<SL::DB::Object::basic_info>.
297 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>