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 __PACKAGE__->meta->initialize;
62 sub items { goto &invoiceitems; }
64 # it is assumed, that ordnumbers are unique here.
65 sub first_order_by_ordnumber {
68 my $orders = SL::DB::Manager::Order->get_all(
70 ordnumber => $self->ordnumber,
75 return first { $_->is_type('sales_order') } @{ $orders };
78 sub abschlag_percentage {
80 my $order = $self->first_order_by_ordnumber or return;
81 my $order_amount = $order->netamount or return;
82 return $self->abschlag
83 ? $self->netamount / $order_amount
89 die 'not a setter method' if @_;
91 return $self->amount - $self->netamount;
94 __PACKAGE__->meta->make_attr_helpers(taxamount => 'numeric(15,5)');
98 return $self->paid >= $self->amount;
102 my ($class, $source, %params) = @_;
104 croak("Unsupported source object type '" . ref($source) . "'") unless ref($source) =~ m/^ SL::DB:: (?: Order | DeliveryOrder ) $/x;
105 croak("Cannot create invoices for purchase records") unless $source->customer_id;
107 my $terms = $source->can('payment_id') && $source->payment_id ? $source->payment_term->terms_netto : 0;
109 my %args = ( map({ ( $_ => $source->$_ ) } qw(customer_id taxincluded shippingpoint shipvia notes intnotes curr salesman_id cusordnumber ordnumber quonumber
110 department_id cp_id language_id payment_id delivery_customer_id delivery_vendor_id taxzone_id shipto_id
111 globalproject_id transaction_description)),
112 transdate => DateTime->today_local,
113 gldate => DateTime->today_local,
114 duedate => DateTime->today_local->add(days => $terms * 1),
119 employee_id => (SL::DB::Manager::Employee->current || SL::DB::Employee->new(id => $source->employee_id))->id,
122 if ($source->type =~ /_order$/) {
123 $args{deliverydate} = $source->reqdate;
124 $args{orddate} = $source->transdate;
126 $args{quodate} = $source->transdate;
129 my $invoice = $class->new(%args, %params);
132 my $source_item = $_;
133 SL::DB::InvoiceItem->new(map({ ( $_ => $source_item->$_ ) }
134 qw(parts_id description qty sellprice discount project_id
135 serialnumber pricegroup_id ordnumber transdate cusordnumber unit
136 base_qty subtotal longdescription lastcost price_factor_id)),
137 deliverydate => $source_item->reqdate,
138 fxsellprice => $source_item->sellprice,);
139 } @{ $source->items };
141 $invoice->invoiceitems(\@items);
147 my ($self, %params) = @_;
149 if (!$params{ar_id}) {
150 my $chart = SL::DB::Manager::Chart->get_all(query => [ SL::DB::Manager::Chart->link_filter('AR') ],
153 croak("No AR chart found and no parameter `ar_id' given") unless $chart;
154 $params{ar_id} = $chart->id;
158 my %data = $self->calculate_prices_and_taxes;
160 $self->_post_create_assemblyitem_entries($data{assembly_items});
161 $self->create_trans_number;
164 $self->_post_add_acctrans($data{amounts_cogs});
165 $self->_post_add_acctrans($data{amounts});
166 $self->_post_add_acctrans($data{taxes});
168 $self->_post_add_acctrans({ $params{ar_id} => $self->amount * -1 });
170 $self->_post_update_allocated($data{allocated});
173 if ($self->db->in_transaction) {
175 } elsif (!$self->db->do_transaction($worker)) {
176 $::lxdebug->message(0, "convert_to_invoice failed: " . join("\n", (split(/\n/, $self->db->error))[0..2]));
183 sub _post_add_acctrans {
184 my ($self, $entries) = @_;
186 while (my ($chart_id, $spec) = each %{ $entries }) {
187 $spec = { taxkey => 0, amount => $spec } unless ref $spec;
188 SL::DB::AccTransaction->new(trans_id => $self->id,
189 chart_id => $chart_id,
190 amount => $spec->{amount},
191 taxkey => $spec->{taxkey},
192 project_id => $self->globalproject_id,
193 transdate => $self->transdate)->save;
197 sub _post_create_assemblyitem_entries {
198 my ($self, $assembly_entries) = @_;
200 my $items = $self->invoiceitems;
204 foreach my $item (@{ $items }) {
205 next if $item->assemblyitem;
207 push @new_items, $item;
210 foreach my $assembly_item (@{ $assembly_entries->[$item_idx] || [ ] }) {
211 push @new_items, SL::DB::InvoiceItem->new(parts_id => $assembly_item->{part},
212 description => $assembly_item->{part}->description,
213 unit => $assembly_item->{part}->unit,
214 qty => $assembly_item->{qty},
215 allocated => $assembly_item->{allocated},
218 assemblyitem => 't');
222 $self->invoiceitems(\@new_items);
225 sub _post_update_allocated {
226 my ($self, $allocated) = @_;
228 while (my ($invoice_id, $diff) = each %{ $allocated }) {
229 SL::DB::Manager::InvoiceItem->update_all(set => { allocated => { sql => "allocated + $diff" } },
230 where => [ id => $invoice_id ]);
242 SL::DB::Invoice: Rose model for invoices (table "ar")
248 =item C<new_from $source>
250 Creates a new C<SL::DB::Invoice> instance and copies as much
251 information from C<$source> as possible. At the moment only sales
252 orders and sales quotations are supported as sources.
254 The conversion copies order items into invoice items. Dates are copied
255 as appropriate, e.g. the C<transdate> field from an order will be
256 copied into the invoice's C<orddate> field.
258 Amounts, prices and taxes are not
259 calculated. L<SL::DB::Helper::PriceTaxCalculator::calculate_prices_and_taxes>
260 can be used for this.
262 The object returned is not saved.
264 =item C<post %params>
266 Posts the invoice. Required parameters are:
272 The ID of the accounds receivable chart the invoices amounts are
273 posted to. If it is not set then the first chart configured for
274 accounts receivables is used.
278 This function implements several steps:
282 =item 1. It calculates all prices, amounts and taxes by calling
283 L<SL::DB::Helper::PriceTaxCalculator::calculate_prices_and_taxes>.
285 =item 2. A new and unique invoice number is created.
287 =item 3. All amounts for costs of goods sold are recorded in
290 =item 4. All amounts for parts, services and assemblies are recorded
291 in C<acc_trans> with their respective charts. This is determined by
292 the part's buchungsgruppen.
294 =item 5. The total amount is posted to the accounts receivable chart
295 and recorded in C<acc_trans>.
297 =item 6. Items in C<invoice> are updated according to their allocation
298 status (regarding for costs of goold sold). Will only be done if
299 Lx-Office is not configured to use Einnahmenüberschussrechnungen
302 =item 7. The invoice and its items are saved.
306 Returns C<$self> on success and C<undef> on failure. The whole process
307 is run inside a transaction. If it fails then nothing is saved to or
308 changed in the database. A new transaction is only started if none is
311 =item C<basic_info $field>
313 See L<SL::DB::Object::basic_info>.
319 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>