Revision b7e394f2
Von Jan Büren vor mehr als 9 Jahren hinzugefügt
SL/BackgroundJob/MassRecordCreationAndPrinting.pm | ||
---|---|---|
1 |
package SL::BackgroundJob::MassRecordCreationAndPrinting; |
|
2 |
|
|
3 |
use strict; |
|
4 |
use warnings; |
|
5 |
|
|
6 |
use parent qw(SL::BackgroundJob::Base); |
|
7 |
|
|
8 |
use SL::DB::DeliveryOrder; |
|
9 |
use SL::DB::Order; # origin order to delivery_order |
|
10 |
use SL::DB::Invoice; |
|
11 |
use SL::DB::Printer; |
|
12 |
use SL::SessionFile; |
|
13 |
use SL::Template; |
|
14 |
|
|
15 |
use constant WAITING_FOR_EXECUTION => 0; |
|
16 |
use constant CONVERTING_DELIVERY_ORDERS => 1; |
|
17 |
use constant PRINTING_INVOICES => 2; |
|
18 |
use constant DONE => 3; |
|
19 |
# Data format: |
|
20 |
# my $data = { |
|
21 |
# record_ids => [ 123, 124, 127, ], |
|
22 |
# printer_id => 4711, |
|
23 |
# num_created => 0, |
|
24 |
# num_printed => 0, |
|
25 |
# invoice_ids => [ 234, 235, ], |
|
26 |
# conversion_errors => [ { id => 124, number => 'A981723', message => "Stuff went boom" }, ], |
|
27 |
# print_errors => [ { id => 234, number => 'L87123123', message => "Printer is out of coffee" }, ], |
|
28 |
# pdf_file_name => 'qweqwe.pdf', |
|
29 |
# }; |
|
30 |
|
|
31 |
sub create_invoices { |
|
32 |
my ($self) = @_; |
|
33 |
|
|
34 |
my $job_obj = $self->{job_obj}; |
|
35 |
my $db = $job_obj->db; |
|
36 |
|
|
37 |
$job_obj->set_data(status => CONVERTING_DELIVERY_ORDERS())->save; |
|
38 |
|
|
39 |
foreach my $delivery_order_id (@{ $job_obj->data_as_hash->{record_ids} }) { |
|
40 |
my $number = $delivery_order_id; |
|
41 |
my $data = $job_obj->data_as_hash; |
|
42 |
|
|
43 |
eval { |
|
44 |
my $invoice; |
|
45 |
my $sales_delivery_order = SL::DB::DeliveryOrder->new(id => $delivery_order_id)->load; |
|
46 |
$number = $sales_delivery_order->donumber; |
|
47 |
|
|
48 |
if (!$db->do_transaction(sub { |
|
49 |
$invoice = $sales_delivery_order->convert_to_invoice(item_filter => \&delivery_order_item_filter, queue_sort => 1) || die $db->error; |
|
50 |
# $delivery_order->post_save_sanity_check; # just a hint at e8521eee (#90 od) |
|
51 |
1; |
|
52 |
})) { |
|
53 |
die $db->error; |
|
54 |
} |
|
55 |
|
|
56 |
$data->{num_created}++; |
|
57 |
push @{ $data->{invoice_ids} }, $invoice->id; |
|
58 |
push @{ $self->{invoices} }, $invoice; |
|
59 |
|
|
60 |
1; |
|
61 |
} or do { |
|
62 |
push @{ $data->{conversion_errors} }, { id => $delivery_order_id, number => $number, message => $@ }; |
|
63 |
}; |
|
64 |
|
|
65 |
$job_obj->update_attributes(data_as_hash => $data); |
|
66 |
} |
|
67 |
} |
|
68 |
|
|
69 |
sub convert_invoices_to_pdf { |
|
70 |
my ($self) = @_; |
|
71 |
|
|
72 |
return if !@{ $self->{invoices} }; |
|
73 |
|
|
74 |
my $job_obj = $self->{job_obj}; |
|
75 |
my $db = $job_obj->db; |
|
76 |
|
|
77 |
$job_obj->set_data(status => PRINTING_INVOICES())->save; |
|
78 |
|
|
79 |
require SL::Controller::MassInvoiceCreatePrint; |
|
80 |
|
|
81 |
my $printer_id = $job_obj->data_as_hash->{printer_id}; |
|
82 |
my $ctrl = SL::Controller::MassInvoiceCreatePrint->new; |
|
83 |
my %variables = ( |
|
84 |
type => 'invoice', |
|
85 |
formname => 'invoice', |
|
86 |
format => 'pdf', |
|
87 |
media => $printer_id ? 'printer' : 'file', |
|
88 |
); |
|
89 |
|
|
90 |
my @pdf_file_names; |
|
91 |
|
|
92 |
foreach my $invoice (@{ $self->{invoices} }) { |
|
93 |
my $data = $job_obj->data_as_hash; |
|
94 |
|
|
95 |
eval { |
|
96 |
my %create_params = ( |
|
97 |
template => $ctrl->find_template(name => 'invoice', printer_id => $printer_id), |
|
98 |
variables => Form->new(''), |
|
99 |
return => 'file_name', |
|
100 |
); |
|
101 |
|
|
102 |
$create_params{variables}->{$_} = $variables{$_} for keys %variables; |
|
103 |
|
|
104 |
$invoice->flatten_to_form($create_params{variables}, format_amounts => 1); |
|
105 |
$create_params{variables}->prepare_for_printing; |
|
106 |
|
|
107 |
push @pdf_file_names, $ctrl->create_pdf(%create_params); |
|
108 |
|
|
109 |
$data->{num_printed}++; |
|
110 |
|
|
111 |
1; |
|
112 |
|
|
113 |
} or do { |
|
114 |
push @{ $data->{print_errors} }, { id => $invoice->id, number => $invoice->invnumber, message => $@ }; |
|
115 |
}; |
|
116 |
|
|
117 |
$job_obj->update_attributes(data_as_hash => $data); |
|
118 |
} |
|
119 |
|
|
120 |
if (@pdf_file_names) { |
|
121 |
my $data = $job_obj->data_as_hash; |
|
122 |
|
|
123 |
eval { |
|
124 |
$self->{merged_pdf} = $ctrl->merge_pdfs(file_names => \@pdf_file_names); |
|
125 |
unlink @pdf_file_names; |
|
126 |
|
|
127 |
if (!$printer_id) { |
|
128 |
my $file_name = 'mass_invoice' . $job_obj->id . '.pdf'; |
|
129 |
my $sfile = SL::SessionFile->new($file_name, mode => 'w'); |
|
130 |
$sfile->fh->print($self->{merged_pdf}); |
|
131 |
$sfile->fh->close; |
|
132 |
|
|
133 |
$data->{pdf_file_name} = $file_name; |
|
134 |
} |
|
135 |
|
|
136 |
1; |
|
137 |
|
|
138 |
} or do { |
|
139 |
push @{ $data->{print_errors} }, { message => $@ }; |
|
140 |
}; |
|
141 |
|
|
142 |
$job_obj->update_attributes(data_as_hash => $data); |
|
143 |
} |
|
144 |
} |
|
145 |
|
|
146 |
sub print_pdfs { |
|
147 |
my ($self) = @_; |
|
148 |
|
|
149 |
my $job_obj = $self->{job_obj}; |
|
150 |
my $data = $job_obj->data_as_hash; |
|
151 |
my $printer_id = $data->{printer_id}; |
|
152 |
|
|
153 |
return if !$printer_id; |
|
154 |
|
|
155 |
my $printer = SL::DB::Printer->new(id => $printer_id)->load; |
|
156 |
my $command = SL::Template::create(type => 'ShellCommand', form => Form->new(''))->parse($printer->printer_command); |
|
157 |
my $out; |
|
158 |
|
|
159 |
if (!open $out, '|-', $command) { |
|
160 |
push @{ $data->{print_errors} }, { message => $::locale->text('Could not execute printer command: #1', $!) }; |
|
161 |
$job_obj->update_attributes(data_as_hash => $data); |
|
162 |
return; |
|
163 |
} |
|
164 |
|
|
165 |
binmode $out; |
|
166 |
print $out $self->{merged_pdf}; |
|
167 |
close $out; |
|
168 |
} |
|
169 |
|
|
170 |
sub run { |
|
171 |
my ($self, $job_obj) = @_; |
|
172 |
|
|
173 |
$self->{job_obj} = $job_obj; |
|
174 |
$self->{invoices} = []; |
|
175 |
|
|
176 |
$self->create_invoices; |
|
177 |
$self->convert_invoices_to_pdf; |
|
178 |
$self->print_pdfs; |
|
179 |
|
|
180 |
$job_obj->set_data(status => DONE())->save; |
|
181 |
|
|
182 |
return 1; |
|
183 |
} |
|
184 |
|
|
185 |
1; |
|
186 |
|
|
187 |
__END__ |
|
188 |
|
|
189 |
=pod |
|
190 |
|
|
191 |
=encoding utf8 |
|
192 |
|
|
193 |
=head1 NAME |
|
194 |
|
|
195 |
SL::BackgroundJob::MassRecordCreationAndPrinting |
|
196 |
|
|
197 |
|
|
198 |
=head1 SYNOPSIS |
|
199 |
|
|
200 |
In controller: |
|
201 |
|
|
202 |
use SL::BackgroundJob::MassRecordCreationAndPrinting |
|
203 |
|
|
204 |
my $job = SL::DB::BackgroundJob->new( |
|
205 |
type => 'once', |
|
206 |
active => 1, |
|
207 |
package_name => 'MassRecordCreationAndPrinting', |
|
208 |
|
|
209 |
)->set_data( |
|
210 |
record_ids => [ map { $_->id } @records[0..$num - 1] ], |
|
211 |
printer_id => $::form->{printer_id}, |
|
212 |
status => SL::BackgroundJob::MassRecordCreationAndPrinting->WAITING_FOR_EXECUTION(), |
|
213 |
num_created => 0, |
|
214 |
num_printed => 0, |
|
215 |
invoice_ids => [ ], |
|
216 |
conversion_errors => [ ], |
|
217 |
print_errors => [ ], |
|
218 |
|
|
219 |
)->update_next_run_at; |
|
220 |
SL::System::TaskServer->new->wake_up; |
|
221 |
|
|
222 |
=head1 OVERVIEW |
|
223 |
|
|
224 |
This background job has 4 states which are described by the four constants above. |
|
225 |
|
|
226 |
=over 2 |
|
227 |
|
|
228 |
=item * WAITING_FOR_EXECUTION |
|
229 |
Background has been initialised and needs to be picked up by the task_server |
|
230 |
|
|
231 |
=item * CONVERTING_DELIVERY_ORDERS |
|
232 |
Object conversion |
|
233 |
|
|
234 |
=item * PRINTING_INVOICES |
|
235 |
Printing, if done via print command |
|
236 |
|
|
237 |
=item * DONE |
|
238 |
To release the process and for the user information |
|
239 |
|
|
240 |
=back |
|
241 |
|
|
242 |
=head1 FUNCTIONS |
|
243 |
|
|
244 |
=over 2 |
|
245 |
|
|
246 |
=item C<create_invoices> |
|
247 |
|
|
248 |
Converts the source objects (DeliveryOrder) to destination objects (Invoice). |
|
249 |
On success objects will be saved. |
|
250 |
|
|
251 |
=item C<convert_invoices_to_pdf> |
|
252 |
|
|
253 |
Takes the new destination objects and merges them via print template in one pdf. |
|
254 |
|
|
255 |
=item C<print_pdfs> |
|
256 |
|
|
257 |
Sent the pdf to the printer command (if checked). |
|
258 |
|
|
259 |
=back |
|
260 |
|
|
261 |
=head1 BUGS |
|
262 |
Currently the calculation from the gui (form) differs from the calculation via convert (PTC). |
|
263 |
Furthermore mass conversion with foreign currencies could lead to problems (daily rate check). |
|
264 |
|
|
265 |
=head1 AUTHOR |
|
266 |
|
|
267 |
Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt> |
|
268 |
|
|
269 |
Jan Büren E<lt>jan@kivitendo-premium.deE<gt> |
|
270 |
=cut |
SL/Controller/MassInvoiceCreatePrint.pm | ||
---|---|---|
1 |
package SL::Controller::MassInvoiceCreatePrint; |
|
2 |
|
|
3 |
use strict; |
|
4 |
|
|
5 |
use parent qw(SL::Controller::Base); |
|
6 |
|
|
7 |
use File::Slurp (); |
|
8 |
use List::MoreUtils qw(all uniq); |
|
9 |
use List::Util qw(first min); |
|
10 |
|
|
11 |
use SL::BackgroundJob::MassRecordCreationAndPrinting; |
|
12 |
use SL::Controller::Helper::GetModels; |
|
13 |
use SL::DB::DeliveryOrder; |
|
14 |
use SL::DB::Order; |
|
15 |
use SL::DB::Printer; |
|
16 |
use SL::Helper::CreatePDF qw(:all); |
|
17 |
use SL::Helper::Flash; |
|
18 |
use SL::Locale::String; |
|
19 |
use SL::SessionFile; |
|
20 |
use SL::System::TaskServer; |
|
21 |
|
|
22 |
use Rose::Object::MakeMethods::Generic |
|
23 |
( |
|
24 |
'scalar --get_set_init' => [ qw(invoice_models invoice_ids sales_delivery_order_models printers default_printer_id js) ], |
|
25 |
); |
|
26 |
|
|
27 |
__PACKAGE__->run_before('setup'); |
|
28 |
|
|
29 |
# |
|
30 |
# actions |
|
31 |
# |
|
32 |
|
|
33 |
sub action_list_sales_delivery_orders { |
|
34 |
my ($self) = @_; |
|
35 |
|
|
36 |
# default is usually no show, exception here |
|
37 |
my $show = ($::form->{noshow} ? 0 : 1); |
|
38 |
delete $::form->{noshow}; |
|
39 |
|
|
40 |
# if a filter is choosen, the filter info should be visible |
|
41 |
$self->make_filter_summary; |
|
42 |
$self->sales_delivery_order_models->get; |
|
43 |
$self->render('mass_invoice_create_print_from_do/list_sales_delivery_orders', |
|
44 |
noshow => $show, |
|
45 |
title => $::locale->text('Open sales delivery orders')); |
|
46 |
} |
|
47 |
|
|
48 |
sub action_create_invoices { |
|
49 |
my ($self) = @_; |
|
50 |
|
|
51 |
my @sales_delivery_order_ids = @{ $::form->{id} || [] }; |
|
52 |
if (!@sales_delivery_order_ids) { |
|
53 |
# should never be executed, double catch via js |
|
54 |
flash_later('error', t8('No delivery orders have been selected.')); |
|
55 |
return $self->redirect_to(action => 'list_sales_delivery_orders'); |
|
56 |
} |
|
57 |
|
|
58 |
my $db = SL::DB::Invoice->new->db; |
|
59 |
|
|
60 |
if (!$db->do_transaction(sub { |
|
61 |
my @invoices; |
|
62 |
foreach my $id (@sales_delivery_order_ids) { |
|
63 |
my $delivery_order = SL::DB::DeliveryOrder->new(id => $id)->load; |
|
64 |
|
|
65 |
my $invoice = $delivery_order->convert_to_invoice() || die $db->error; |
|
66 |
push @invoices, $invoice; |
|
67 |
} |
|
68 |
|
|
69 |
my $key = sprintf('%d-%d', Time::HiRes::gettimeofday()); |
|
70 |
$::auth->set_session_value("MassInvoiceCreatePrint::ids-${key}" => [ map { $_->id } @invoices ]); |
|
71 |
|
|
72 |
flash_later('info', t8('The invoices have been created. They\'re pre-selected below.')); |
|
73 |
$self->redirect_to(action => 'list_invoices', ids => $key); |
|
74 |
|
|
75 |
1; |
|
76 |
})) { |
|
77 |
$::lxdebug->message(LXDebug::WARN(), "Error: " . $db->error); |
|
78 |
$::form->error($db->error); |
|
79 |
} |
|
80 |
} |
|
81 |
|
|
82 |
sub action_list_invoices { |
|
83 |
my ($self) = @_; |
|
84 |
|
|
85 |
my $show = $::form->{noshow} ? 0 : 1; |
|
86 |
delete $::form->{noshow}; |
|
87 |
|
|
88 |
if ($::form->{ids}) { |
|
89 |
my $key = 'MassInvoiceCreatePrint::ids-' . $::form->{ids}; |
|
90 |
$self->invoice_ids($::auth->get_session_value($key) || []); |
|
91 |
$self->invoice_models->add_additional_url_params(ids => $::form->{ids}); |
|
92 |
} |
|
93 |
|
|
94 |
my %selected_ids = map { +($_ => 1) } @{ $self->invoice_ids }; |
|
95 |
|
|
96 |
$::form->{printer_id} ||= $self->default_printer_id; |
|
97 |
|
|
98 |
$self->render('mass_invoice_create_print_from_do/list_invoices', |
|
99 |
title => $::locale->text('Open invoice'), |
|
100 |
noshow => $show, |
|
101 |
selected_ids => \%selected_ids); |
|
102 |
} |
|
103 |
|
|
104 |
sub action_print { |
|
105 |
my ($self) = @_; |
|
106 |
|
|
107 |
my @invoices = map { SL::DB::Invoice->new(id => $_)->load } @{ $::form->{id} || [] }; |
|
108 |
if (!@invoices) { |
|
109 |
flash_later('error', t8('No invoices have been selected.')); |
|
110 |
return $self->redirect_to(action => 'list_invoices'); |
|
111 |
} |
|
112 |
|
|
113 |
$self->download_or_print_documents(printer_id => $::form->{printer_id}, invoices => \@invoices); |
|
114 |
} |
|
115 |
|
|
116 |
sub action_create_print_all_start { |
|
117 |
my ($self) = @_; |
|
118 |
|
|
119 |
$self->sales_delivery_order_models->disable_plugin('paginated'); |
|
120 |
|
|
121 |
my @records = @{ $self->sales_delivery_order_models->get }; |
|
122 |
my $num = min(scalar(@records), $::form->{number_of_invoices} // scalar(@records)); |
|
123 |
|
|
124 |
my $job = SL::DB::BackgroundJob->new( |
|
125 |
type => 'once', |
|
126 |
active => 1, |
|
127 |
package_name => 'MassRecordCreationAndPrinting', |
|
128 |
|
|
129 |
)->set_data( |
|
130 |
record_ids => [ map { $_->id } @records[0..$num - 1] ], |
|
131 |
printer_id => $::form->{printer_id}, |
|
132 |
status => SL::BackgroundJob::MassRecordCreationAndPrinting->WAITING_FOR_EXECUTION(), |
|
133 |
num_created => 0, |
|
134 |
num_printed => 0, |
|
135 |
invoice_ids => [ ], |
|
136 |
conversion_errors => [ ], |
|
137 |
print_errors => [ ], |
|
138 |
|
|
139 |
)->update_next_run_at; |
|
140 |
|
|
141 |
SL::System::TaskServer->new->wake_up; |
|
142 |
|
|
143 |
my $html = $self->render('mass_invoice_create_print_from_do/_create_print_all_status', { output => 0 }, job => $job); |
|
144 |
|
|
145 |
$self->js |
|
146 |
->html('#create_print_all_dialog', $html) |
|
147 |
->run('kivi.MassInvoiceCreatePrint.massConversionStarted') |
|
148 |
->render; |
|
149 |
} |
|
150 |
|
|
151 |
sub action_create_print_all_status { |
|
152 |
my ($self) = @_; |
|
153 |
my $job = SL::DB::BackgroundJob->new(id => $::form->{job_id})->load; |
|
154 |
my $html = $self->render('mass_invoice_create_print_from_do/_create_print_all_status', { output => 0 }, job => $job); |
|
155 |
|
|
156 |
$self->js->html('#create_print_all_dialog', $html); |
|
157 |
$self->js->run('kivi.MassInvoiceCreatePrint.massConversionFinished') if $job->data_as_hash->{status} == SL::BackgroundJob::MassRecordCreationAndPrinting->DONE(); |
|
158 |
$self->js->render; |
|
159 |
} |
|
160 |
|
|
161 |
sub action_create_print_all_download { |
|
162 |
my ($self) = @_; |
|
163 |
my $job = SL::DB::BackgroundJob->new(id => $::form->{job_id})->load; |
|
164 |
|
|
165 |
my $sfile = SL::SessionFile->new($job->data_as_hash->{pdf_file_name}, mode => 'r'); |
|
166 |
die $! if !$sfile->fh; |
|
167 |
|
|
168 |
my $merged_pdf = do { local $/; my $fh = $sfile->fh; <$fh> }; |
|
169 |
$sfile->fh->close; |
|
170 |
|
|
171 |
my $type = 'Invoices'; |
|
172 |
my $file_name = t8($type) . '-' . DateTime->today_local->strftime('%Y%m%d%H%M%S') . '.pdf'; |
|
173 |
$file_name =~ s{[^\w\.]+}{_}g; |
|
174 |
|
|
175 |
return $self->send_file( |
|
176 |
\$merged_pdf, |
|
177 |
type => 'application/pdf', |
|
178 |
name => $file_name, |
|
179 |
); |
|
180 |
} |
|
181 |
|
|
182 |
# |
|
183 |
# filters |
|
184 |
# |
|
185 |
|
|
186 |
sub init_js { SL::ClientJS->new(controller => $_[0]) } |
|
187 |
sub init_printers { SL::DB::Manager::Printer->get_all_sorted } |
|
188 |
sub init_invoice_ids { [] } |
|
189 |
|
|
190 |
sub init_sales_delivery_order_models { |
|
191 |
my ($self) = @_; |
|
192 |
return $self->_init_sales_delivery_order_models(sortby => 'donumber'); |
|
193 |
} |
|
194 |
|
|
195 |
sub _init_sales_delivery_order_models { |
|
196 |
my ($self, %params) = @_; |
|
197 |
|
|
198 |
SL::Controller::Helper::GetModels->new( |
|
199 |
controller => $_[0], |
|
200 |
model => 'DeliveryOrder', |
|
201 |
# model => 'Order', |
|
202 |
sorted => { |
|
203 |
_default => { |
|
204 |
by => $params{sortby}, |
|
205 |
dir => 1, |
|
206 |
}, |
|
207 |
customer => t8('Customer'), |
|
208 |
employee => t8('Employee'), |
|
209 |
transdate => t8('Date'), |
|
210 |
donumber => t8('Delivery Order Number'), |
|
211 |
ordnumber => t8('Order Number'), |
|
212 |
}, |
|
213 |
with_objects => [ qw(customer employee) ], |
|
214 |
query => [ |
|
215 |
'!customer_id' => undef, |
|
216 |
or => [ closed => undef, closed => 0 ], |
|
217 |
or => [ delivered => undef, delivered => 0 ], |
|
218 |
], |
|
219 |
); |
|
220 |
} |
|
221 |
|
|
222 |
|
|
223 |
sub init_invoice_models { |
|
224 |
my ($self) = @_; |
|
225 |
my @invoice_ids = @{ $self->invoice_ids }; |
|
226 |
|
|
227 |
SL::Controller::Helper::GetModels->new( |
|
228 |
controller => $_[0], |
|
229 |
model => 'Invoice', |
|
230 |
(paginated => 0,) x !!@invoice_ids, |
|
231 |
sorted => { |
|
232 |
_default => { |
|
233 |
by => 'transdate', |
|
234 |
dir => 0, |
|
235 |
}, |
|
236 |
customer => t8('Customer'), |
|
237 |
invnumber => t8('Invoice Number'), |
|
238 |
employee => t8('Employee'), |
|
239 |
donumber => t8('Delivery Order Number'), |
|
240 |
ordnumber => t8('Order Number'), |
|
241 |
reqdate => t8('Delivery Date'), |
|
242 |
transdate => t8('Date'), |
|
243 |
}, |
|
244 |
with_objects => [ qw(customer employee) ], |
|
245 |
query => [ |
|
246 |
'!customer_id' => undef, |
|
247 |
(id => \@invoice_ids) x !!@invoice_ids, |
|
248 |
], |
|
249 |
); |
|
250 |
} |
|
251 |
|
|
252 |
|
|
253 |
sub init_default_printer_id { |
|
254 |
my $pr = SL::DB::Manager::Printer->find_by(printer_description => $::locale->text("sales_invoice_printer")); |
|
255 |
return $pr ? $pr->id : undef; |
|
256 |
} |
|
257 |
|
|
258 |
sub setup { |
|
259 |
my ($self) = @_; |
|
260 |
$::auth->assert('invoice_edit'); |
|
261 |
|
|
262 |
$::request->layout->use_javascript("${_}.js") for qw(kivi.MassInvoiceCreatePrint); |
|
263 |
} |
|
264 |
|
|
265 |
# |
|
266 |
# helpers |
|
267 |
# |
|
268 |
|
|
269 |
sub create_pdfs { |
|
270 |
my ($self, %params) = @_; |
|
271 |
|
|
272 |
my @pdf_file_names; |
|
273 |
foreach my $invoice (@{ $params{invoices} }) { |
|
274 |
my %create_params = ( |
|
275 |
template => $self->find_template(name => 'invoice', printer_id => $params{printer_id}), |
|
276 |
variables => Form->new(''), |
|
277 |
return => 'file_name', |
|
278 |
); |
|
279 |
|
|
280 |
$create_params{variables}->{$_} = $params{variables}->{$_} for keys %{ $params{variables} }; |
|
281 |
|
|
282 |
$invoice->flatten_to_form($create_params{variables}, format_amounts => 1); |
|
283 |
$create_params{variables}->prepare_for_printing; |
|
284 |
|
|
285 |
push @pdf_file_names, $self->create_pdf(%create_params); |
|
286 |
} |
|
287 |
|
|
288 |
return @pdf_file_names; |
|
289 |
} |
|
290 |
|
|
291 |
sub download_or_print_documents { |
|
292 |
my ($self, %params) = @_; |
|
293 |
|
|
294 |
my @pdf_file_names; |
|
295 |
|
|
296 |
eval { |
|
297 |
my %pdf_params = ( |
|
298 |
invoices => $params{invoices}, |
|
299 |
printer_id => $params{printer_id}, |
|
300 |
variables => { |
|
301 |
type => 'invoice', |
|
302 |
formname => 'invoice', |
|
303 |
format => 'pdf', |
|
304 |
media => $params{printer_id} ? 'printer' : 'file', |
|
305 |
}); |
|
306 |
|
|
307 |
@pdf_file_names = $self->create_pdfs(%pdf_params); |
|
308 |
my $merged_pdf = $self->merge_pdfs(file_names => \@pdf_file_names); |
|
309 |
unlink @pdf_file_names; |
|
310 |
|
|
311 |
if (!$params{printer_id}) { |
|
312 |
my $file_name = t8("Invoices") . '-' . DateTime->today_local->strftime('%Y%m%d%H%M%S') . '.pdf'; |
|
313 |
$file_name =~ s{[^\w\.]+}{_}g; |
|
314 |
|
|
315 |
return $self->send_file( |
|
316 |
\$merged_pdf, |
|
317 |
type => 'application/pdf', |
|
318 |
name => $file_name, |
|
319 |
); |
|
320 |
} |
|
321 |
|
|
322 |
my $printer = SL::DB::Printer->new(id => $params{printer_id})->load; |
|
323 |
my $command = SL::Template::create(type => 'ShellCommand', form => Form->new(''))->parse($printer->printer_command); |
|
324 |
|
|
325 |
open my $out, '|-', $command or die $!; |
|
326 |
binmode $out; |
|
327 |
print $out $merged_pdf; |
|
328 |
close $out; |
|
329 |
|
|
330 |
flash_later('info', t8('The documents have been sent to the printer \'#1\'.', $printer->printer_description)); |
|
331 |
return $self->redirect_to(action => 'list_invoices', printer_id => $params{printer_id}); |
|
332 |
|
|
333 |
} or do { |
|
334 |
unlink @pdf_file_names; |
|
335 |
$::form->error(t8("Creating the PDF failed:") . " " . $@); |
|
336 |
}; |
|
337 |
} |
|
338 |
|
|
339 |
sub make_filter_summary { |
|
340 |
my ($self) = @_; |
|
341 |
|
|
342 |
my $filter = $::form->{filter} || {}; |
|
343 |
my @filter_strings; |
|
344 |
|
|
345 |
my @filters = ( |
|
346 |
[ $filter->{customer}{"name:substr::ilike"}, t8('Customer') ], |
|
347 |
[ $filter->{"transdate:date::ge"}, t8('Delivery Date') . " " . t8('From Date') ], |
|
348 |
[ $filter->{"transdate:date::le"}, t8('Delivery Date') . " " . t8('To Date') ], |
|
349 |
); |
|
350 |
|
|
351 |
for (@filters) { |
|
352 |
push @filter_strings, "$_->[1]: " . ($_->[2] ? $_->[2]->() : $_->[0]) if $_->[0]; |
|
353 |
} |
|
354 |
|
|
355 |
$self->{filter_summary} = join ', ', @filter_strings; |
|
356 |
} |
|
357 |
1; |
|
358 |
|
|
359 |
__END__ |
|
360 |
|
|
361 |
=pod |
|
362 |
|
|
363 |
=encoding utf8 |
|
364 |
|
|
365 |
=head1 NAME |
|
366 |
|
|
367 |
SL::Controller::MassInvoiceCreatePrint - Controller for Mass Create Print Sales Invoice from Delivery Order |
|
368 |
|
|
369 |
=head2 OVERVIEW |
|
370 |
|
|
371 |
Controller class for the conversion and processing (printing) of objects. |
|
372 |
|
|
373 |
|
|
374 |
Inherited from the base controller class, this controller implements the Sales Mass Invoice Creation. |
|
375 |
In general there are two major distinctions: |
|
376 |
This class implements the conversion and the printing via clickable action AND triggers the same |
|
377 |
conversion towards a Background-Job with a good user interaction. |
|
378 |
|
|
379 |
Analysis hints: All this is more or less boilerplate code around the great convert_to_invoice method |
|
380 |
in DeliverOrder.pm. If you need to debug stuff, take a look at the corresponding test case |
|
381 |
($ t/test.pl t/db_helper/convert_invoices.t). There are some redundant code parts in this controller |
|
382 |
and in the background job, i.e. compare the actions create and print. |
|
383 |
From a reverse engineering point of view the actions in this controller were written before the |
|
384 |
background job existed, therefore if anything goes boom take a look at the single steps done via gui |
|
385 |
in this controller and after that take a deeper look at the MassRecordCreationAndPrinting job. |
|
386 |
|
|
387 |
|
|
388 |
=head1 FUNCTIONS |
|
389 |
|
|
390 |
=over 2 |
|
391 |
|
|
392 |
=item C<action_list_sales_delivery_orders> |
|
393 |
|
|
394 |
List all open sales delivery orders. The filter can be in two states show or "no show" the |
|
395 |
original, probably gorash idea, is to increase performance and not to be forced to click on the |
|
396 |
next button (like in all other reports). Therefore use this option and this filter for a good |
|
397 |
project default and hide it again. Filters can be added in _filter.html. Take a look at |
|
398 |
SL::Controlle::Helper::GetModels::Filtered.pm and SL::DB::Helper::Filtered. |
|
399 |
|
|
400 |
=item C<action_create_invoices> |
|
401 |
|
|
402 |
Creates or to be more correctly converts delivery orders to invoices. All items are |
|
403 |
converted 1:1 and after conversion the delivery order(s) are closed. |
|
404 |
|
|
405 |
=item C<action_list_invoices> |
|
406 |
|
|
407 |
List the created invoices, if created via gui (see action above) |
|
408 |
|
|
409 |
=item C<action_print> |
|
410 |
|
|
411 |
Print the selected invoices. Yes, it really is all boring linear (see action above). |
|
412 |
Calls download_or_print method. |
|
413 |
|
|
414 |
=item C<action_create_print_all_start> |
|
415 |
|
|
416 |
Initialises the webform for the creation and printing via background job. Now we get to |
|
417 |
the more fun part ... Mosu did a great user interaction job here, we can choose how many |
|
418 |
objects are converted in one strike and if or if not they are downloadable or will be sent to |
|
419 |
a printer (if defined as a printing command) right away. |
|
420 |
Background job is started and status is watched via js and the next action. |
|
421 |
|
|
422 |
=item C<action_create_print_all_status> |
|
423 |
|
|
424 |
Action for watching status, default is refreshing every 5 seconds |
|
425 |
|
|
426 |
=item C<action_create_print_all_download> |
|
427 |
|
|
428 |
If the above is done (did I already said: boring linear?). Documents will |
|
429 |
be either printed or downloaded. |
|
430 |
|
|
431 |
=item C<init_js> |
|
432 |
|
|
433 |
Inits js/kivi.MassInvoiceCreatePrint; |
|
434 |
|
|
435 |
=item C<init_printers> |
|
436 |
|
|
437 |
Gets all printer commands |
|
438 |
|
|
439 |
=item C<init_invoice_ids> |
|
440 |
|
|
441 |
Gets a list of (empty) invoice ids |
|
442 |
|
|
443 |
=item C<init_sales_delivery_order_models> |
|
444 |
|
|
445 |
Calls _init_sales_delivery_order_models with a param |
|
446 |
|
|
447 |
=item C<_init_sales_delivery_order_models> |
|
448 |
|
|
449 |
Private function, called by init_sales_delivery_order_models. |
|
450 |
Gets all open sales delivery orders. |
|
451 |
|
|
452 |
=item C<init_invoice_models> |
|
453 |
|
|
454 |
Gets all invoice_models via the ids in invoice_ids (at the beginning no ids exist) |
|
455 |
|
|
456 |
=item C<init_default_printer_id> |
|
457 |
|
|
458 |
Gets the default printer for sales_invoices. Maybe this function is not used, but |
|
459 |
might be useful in the next version (working in client project). |
|
460 |
|
|
461 |
=item C<setup> |
|
462 |
|
|
463 |
Currently sets / checks only the access right. |
|
464 |
|
|
465 |
=item C<create_pdfs> |
|
466 |
|
|
467 |
=item C<download_or_print_documents> |
|
468 |
|
|
469 |
Backend function for printing or downloading docs. Only used for gui processing (called |
|
470 |
via action_print). |
|
471 |
|
|
472 |
=item C<make_filter_summary> |
|
473 |
Creates the filter option summary in the header. By the time of writing three filters are |
|
474 |
supported: Customer and date from/to of the Delivery Order (database field transdate). |
|
475 |
|
|
476 |
=back |
|
477 |
|
|
478 |
=head1 TODO |
|
479 |
|
|
480 |
Should be more generalized. Right now just one conversion (delivery order to invoice) is supported. |
|
481 |
Using BackgroundJobs to mass create / transfer stuff is the way to do it. The original idea |
|
482 |
was taken from one client project (mosu) with some extra (maybe not standard compliant) customized |
|
483 |
stuff (using cvars for extra filters and a very compressed Controller for linking (ODSalesOrder.pm)). |
|
484 |
|
|
485 |
Filtering needs to be extended for Delivery Order Number (Natural Sort). |
|
486 |
|
|
487 |
A second printer (copy) needs to be implemented. |
|
488 |
|
|
489 |
Both todos are marked in the template code. |
|
490 |
|
|
491 |
|
|
492 |
=head1 AUTHOR |
|
493 |
|
|
494 |
Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt> |
|
495 |
|
|
496 |
Jan Büren E<lt>jan@kivitendo-premium.deE<gt> |
|
497 |
=cut |
js/kivi.MassInvoiceCreatePrint.js | ||
---|---|---|
1 |
namespace('kivi.MassInvoiceCreatePrint', function(ns) { |
|
2 |
this.checkSalesOrderSelection = function() { |
|
3 |
if ($("[data-checkall=1]:checked").size() > 0) |
|
4 |
return true; |
|
5 |
alert(kivi.t8('No delivery orders have been selected.')); |
|
6 |
return false; |
|
7 |
}; |
|
8 |
|
|
9 |
this.checkDeliveryOrderSelection = function() { |
|
10 |
if ($("[data-checkall=1]:checked").size() > 0) |
|
11 |
return true; |
|
12 |
alert(kivi.t8('No delivery orders have been selected.')); |
|
13 |
return false; |
|
14 |
}; |
|
15 |
this.checkInvoiceSelection = function() { |
|
16 |
if ($("[data-checkall=1]:checked").size() > 0) |
|
17 |
return true; |
|
18 |
alert(kivi.t8('No invoices have been selected.')); |
|
19 |
return false; |
|
20 |
}; |
|
21 |
|
|
22 |
this.submitMassCreationForm = function() { |
|
23 |
if (!kivi.MassInvoiceCreatePrint.checkDeliveryOrderSelection()) |
|
24 |
return false; |
|
25 |
|
|
26 |
$('body').addClass('loading'); |
|
27 |
$('form').submit(); |
|
28 |
return false; |
|
29 |
}; |
|
30 |
|
|
31 |
this.createPrintAllInitialize = function() { |
|
32 |
kivi.popup_dialog({ |
|
33 |
id: 'create_print_all_dialog', |
|
34 |
dialog: { |
|
35 |
title: kivi.t8('Create and print all invoices') |
|
36 |
} |
|
37 |
}); |
|
38 |
}; |
|
39 |
|
|
40 |
this.createPrintAllStartProcess = function() { |
|
41 |
$('#cpa_start_process_button,.ui-dialog-titlebar button.ui-dialog-titlebar-close').prop('disabled', 'disabled'); |
|
42 |
$('#cpa_start_process_abort_link').remove(); |
|
43 |
|
|
44 |
var data = { |
|
45 |
number_of_invoices: $('#cpa_number_of_invoices').val(), |
|
46 |
printer_id: $('#cpa_printer_id').val() |
|
47 |
}; |
|
48 |
kivi.submit_ajax_form('controller.pl?action=MassInvoiceCreatePrint/create_print_all_start', '[name^=filter\\.]', data); |
|
49 |
}; |
|
50 |
|
|
51 |
this.createPrintAllFinishProcess = function() { |
|
52 |
$('#create_print_all_dialog').dialog('close'); |
|
53 |
window.location.href = 'controller.pl?action=MassInvoiceCreatePrint%2flist_invoices&noshow=1'; |
|
54 |
}; |
|
55 |
|
|
56 |
this.massConversionStarted = function() { |
|
57 |
$('#create_print_all_dialog').data('timerId', setInterval(function() { |
|
58 |
$.get("controller.pl", { |
|
59 |
action: 'MassInvoiceCreatePrint/create_print_all_status', |
|
60 |
job_id: $('#cpa_job_id').val() |
|
61 |
}, kivi.eval_json_result); |
|
62 |
}, 5000)); |
|
63 |
}; |
|
64 |
|
|
65 |
this.massConversionFinished = function() { |
|
66 |
clearInterval($('#create_print_all_dialog').data('timerId')); |
|
67 |
$('.ui-dialog-titlebar button.ui-dialog-titlebar-close').prop('disabled', '') |
|
68 |
}; |
|
69 |
|
|
70 |
this.setup = function() { |
|
71 |
$('#create_button').click(kivi.MassInvoiceCreatePrint.submitMassCreationForm); |
|
72 |
$('#create_print_all_button').click(kivi.MassInvoiceCreatePrint.createPrintAllInitialize); |
|
73 |
$('#action_print').click(kivi.MassInvoiceCreatePrint.checkInvoiceSelection); |
|
74 |
}; |
|
75 |
}); |
|
76 |
|
|
77 |
$(kivi.MassInvoiceCreatePrint.setup); |
menus/user/00-erp.yaml | ||
---|---|---|
265 | 265 |
module: letter.pl |
266 | 266 |
params: |
267 | 267 |
action: add |
268 |
- parent: ar |
|
269 |
id: ar_invoices |
|
270 |
name: Invoices |
|
271 |
icon: sales_invoice_add |
|
272 |
order: 850 |
|
273 |
- parent: ar_invoices |
|
274 |
id: ar_invoices_mass_add_sales_invoice |
|
275 |
name: Mass Create Print Sales Invoice from Delivery Order |
|
276 |
order: 100 |
|
277 |
access: invoice_edit |
|
278 |
params: |
|
279 |
noshow: 1 |
|
280 |
action: MassInvoiceCreatePrint/list_sales_delivery_orders |
|
268 | 281 |
- parent: ar |
269 | 282 |
id: ar_reports |
270 | 283 |
name: Reports |
templates/webpages/mass_invoice_create_print_from_do/_create_print_all_status.html | ||
---|---|---|
1 |
[%- USE LxERP -%][%- USE L -%][%- USE HTML -%] |
|
2 |
[% SET data = job.data_as_hash %] |
|
3 |
<h2>[% LxERP.t8("Step 2 -- Watch status") %]</h2> |
|
4 |
|
|
5 |
[% L.hidden_tag('', job.id, id="cpa_job_id") %] |
|
6 |
|
|
7 |
<p> |
|
8 |
[% LxERP.t8("This status output will be refreshed every five seconds.") %] |
|
9 |
</p> |
|
10 |
|
|
11 |
<p> |
|
12 |
[% IF data.status < 3 %] |
|
13 |
[% L.link("login.pl?action=company_logo", LxERP.t8("Open new tab"), target="_blank") %] |
|
14 |
|
|
15 |
[% ELSE %] |
|
16 |
[% IF data.pdf_file_name %] |
|
17 |
[% L.link(SELF.url_for(action="create_print_all_download", job_id=job.id), LxERP.t8("Download PDF")) %] |
|
18 |
[% END %] |
|
19 |
[% L.link("#", LxERP.t8("Close window"), onclick="kivi.MassInvoiceCreatePrint.createPrintAllFinishProcess();") %] |
|
20 |
[% END %] |
|
21 |
</p> |
|
22 |
|
|
23 |
<p> |
|
24 |
<table> |
|
25 |
<tr> |
|
26 |
<th valign="top" align="left">[% LxERP.t8("Current status:") %]</th> |
|
27 |
<td valign="top"> |
|
28 |
[% IF !data.status %] |
|
29 |
[% LxERP.t8("waiting for job to be started") %] |
|
30 |
[% ELSIF data.status == 1 %] |
|
31 |
[% LxERP.t8("Creating invoices") %] |
|
32 |
[% ELSIF data.status == 2 %] |
|
33 |
[% LxERP.t8("Printing invoices (this can take a while)") %] |
|
34 |
[% ELSE %] |
|
35 |
[% LxERP.t8("Done.") %] |
|
36 |
[% IF data.pdf_file_name %] |
|
37 |
[% LxERP.t8("The file is available for download.") %] |
|
38 |
[% ELSIF data.printer_id %] |
|
39 |
[% LxERP.t8("The file has been sent to the printer.") %] |
|
40 |
[% END %] |
|
41 |
[% END %] |
|
42 |
</td> |
|
43 |
</tr> |
|
44 |
|
|
45 |
<tr> |
|
46 |
<th valign="top" align="left">[% LxERP.t8("Number of invoices created:") %]</th> |
|
47 |
<td valign="top">[% IF data.status > 0 %][% HTML.escape(data.num_created) %] / [% HTML.escape(data.record_ids.size) %][% ELSE %]–[% END %]</td> |
|
48 |
</tr> |
|
49 |
|
|
50 |
<tr> |
|
51 |
<th valign="top" align="left">[% LxERP.t8("Number of invoices printed:") %]</th> |
|
52 |
<td valign="top">[% IF data.status > 1 %][% HTML.escape(data.num_printed) %] / [% HTML.escape(data.invoice_ids.size) %][% ELSE %]–[% END %]</td> |
|
53 |
</tr> |
|
54 |
|
|
55 |
<tr> |
|
56 |
<th valign="top" align="left">[% LxERP.t8("Errors during conversion:") %]</th> |
|
57 |
<td valign="top"> |
|
58 |
[% IF !data.status %] |
|
59 |
– |
|
60 |
[% ELSIF !data.conversion_errors.size %] |
|
61 |
[% LxERP.t8("No errors have occurred.") %] |
|
62 |
[% ELSE %] |
|
63 |
<table> |
|
64 |
<tr class="listheader"> |
|
65 |
<th>[% LxERP.t8("Delivery Order") %]</th> |
|
66 |
<th>[% LxERP.t8("Error") %]</th> |
|
67 |
</tr> |
|
68 |
|
|
69 |
[% FOREACH error = data.conversion_errors %] |
|
70 |
<tr> |
|
71 |
<td valign="top">[% IF error.id %][% L.link(SELF.url_for(controller='do.pl', action='edit', type='sales_delivery_order', id=error.id), HTML.escape(error.number), target="_blank") %][% ELSE %]–[% END %]</td> |
|
72 |
<td valign="top">[% HTML.escape(error.message) %]</td> |
|
73 |
</tr> |
|
74 |
[% END %] |
|
75 |
</table> |
|
76 |
[% END %] |
|
77 |
</td> |
|
78 |
</tr> |
|
79 |
|
|
80 |
<tr> |
|
81 |
<th valign="top" align="left">[% LxERP.t8("Errors during printing:") %]</th> |
|
82 |
<td valign="top"> |
|
83 |
[% IF data.status < 2 %] |
|
84 |
– |
|
85 |
[% ELSIF !data.print_errors.size %] |
|
86 |
[% LxERP.t8("No errors have occurred.") %] |
|
87 |
[% ELSE %] |
|
88 |
<table> |
|
89 |
<tr class="listheader"> |
|
90 |
<th>[% LxERP.t8("Invoice") %]</th> |
|
91 |
<th>[% LxERP.t8("Error") %]</th> |
|
92 |
</tr> |
|
93 |
|
|
94 |
[% FOREACH error = data.print_errors %] |
|
95 |
<tr> |
|
96 |
<td valign="top">[% IF error.id %][% L.link(SELF.url_for(controller='is.pl', action='edit', type='sales_invoice',id=error.id), HTML.escape(error.number), target="_blank") %][% ELSE %]–[% END %]</td> |
|
97 |
<td valign="top">[% HTML.escape(error.message) %]</td> |
|
98 |
</tr> |
|
99 |
[% END %] |
|
100 |
</table> |
|
101 |
[% END %] |
|
102 |
</td> |
|
103 |
</tr> |
|
104 |
|
|
105 |
</table> |
|
106 |
</p> |
templates/webpages/mass_invoice_create_print_from_do/_create_print_all_step_1.html | ||
---|---|---|
1 |
[%- USE LxERP -%][%- USE L -%] |
|
2 |
[%- SET num_delivery_orders = SELF.sales_delivery_order_models.count %] |
|
3 |
<h2>[% LxERP.t8("Step 1 -- limit number of delivery orders to process") %]</h2> |
|
4 |
|
|
5 |
<p> |
|
6 |
[% LxERP.t8("Currently #1 delivery orders can be converted into invoices and printed.", num_delivery_orders) %] |
|
7 |
[% LxERP.t8("How many do you want to create and print?") %] |
|
8 |
</p> |
|
9 |
|
|
10 |
<table> |
|
11 |
<tr> |
|
12 |
<td>[% LxERP.t8("Number of invoices to create") %]:</td> |
|
13 |
<td>[% L.input_tag('', num_delivery_orders, size="5", id="cpa_number_of_invoices") %]</td> |
|
14 |
</tr> |
|
15 |
|
|
16 |
<tr> |
|
17 |
<td>[% LxERP.t8("Print destination") %]:</td> |
|
18 |
<td> |
|
19 |
[% SET printers = [ { description=LxERP.t8("Download PDF, do not print") } ] ; |
|
20 |
CALL printers.import(SELF.printers); |
|
21 |
L.select_tag("", printers, title_key="description", default=SELF.default_printer_id, id="cpa_printer_id") %] |
|
22 |
</td> |
|
23 |
</tr> |
|
24 |
<!--tr> |
|
25 |
<td>[% LxERP.t8("Print destination (copy)") %]:</td> |
|
26 |
<td> |
|
27 |
[% SET printers = [ { description=LxERP.t8("Download PDF, do not print") } ] ; |
|
28 |
CALL printers.import(SELF.printers); |
|
29 |
L.select_tag("", printers, title_key="description", default=SELF.default_printer_id, id="cpa_printer_id") %] |
|
30 |
</td> |
|
31 |
</tr --> |
|
32 |
</table> |
|
33 |
|
|
34 |
<p> |
|
35 |
[% L.button_tag("kivi.MassInvoiceCreatePrint.createPrintAllStartProcess();", LxERP.t8("Start process"), id="cpa_start_process_button") %] |
|
36 |
[% L.link("#", LxERP.t8("Abort"), onclick="\$('#create_print_all_dialog').dialog('close');", id="cpa_start_process_abort_link") %] |
|
37 |
</p> |
templates/webpages/mass_invoice_create_print_from_do/_filter.html | ||
---|---|---|
1 |
[%- USE L %][%- USE LxERP %][%- USE HTML %] |
|
2 |
<div> |
|
3 |
<form action="controller.pl" method="post"> |
|
4 |
<div class="filter_toggle" [% IF noshow == 0 %]style="display:none"[% END %]> |
|
5 |
<a href="#" onClick="javascript:$('.filter_toggle').toggle()">[% LxERP.t8('Show Filter') %]</a> |
|
6 |
[% SELF.filter_summary %] |
|
7 |
</div> |
|
8 |
|
|
9 |
<div class="filter_toggle" [% IF noshow == 1 %]style="display:none"[% END %]> |
|
10 |
<a href="#" onClick="javascript:$('.filter_toggle').toggle()">[% LxERP.t8('Hide Filter') %]</a> |
|
11 |
<table id="filter_table"> |
|
12 |
<tr> |
|
13 |
<th align="right">[% LxERP.t8('Customer') %]</th> |
|
14 |
<td>[% L.input_tag('filter.customer.name:substr::ilike', filter.customer.name_substr__ilike, size = 20) %]</td> |
|
15 |
</tr> |
|
16 |
<th align="right">[% LxERP.t8('Delivery Date') %] [% LxERP.t8('From Date') %]</th> |
|
17 |
<td>[% L.date_tag('filter.transdate:date::ge', filter.transdate_date__ge) %]</td> |
|
18 |
</tr> |
|
19 |
<tr> |
|
20 |
<th align="right">[% LxERP.t8('Delivery Date') %] [% LxERP.t8('To Date') %]</th> |
|
21 |
<td>[% L.date_tag('filter.transdate:date::le', filter.transdate_date__le) %]</td> |
|
22 |
</tr> |
|
23 |
<!-- TODO implement helper function nat sort here --> |
|
24 |
<!-- tr> |
|
25 |
<th align="right">[% LxERP.t8('From') %] [% LxERP.t8('Delivery Order Number') %]</th> |
|
26 |
<td>[% L.input_tag('filter.donumber:number::ge', filter.donumber_number__ge) %]</td> |
|
27 |
</tr> |
|
28 |
<tr> |
|
29 |
<th align="right">[% LxERP.t8('To') %] [% LxERP.t8('Delivery Order Number') %]</th> |
|
30 |
<td>[% L.input_tag('filter.donumber:number::le', filter.donumber_number__le) %]</td> |
|
31 |
</tr --> |
|
32 |
|
|
33 |
</table> |
|
34 |
|
|
35 |
[% L.hidden_tag('action', 'MassInvoiceCreatePrint/dispatch') %] |
|
36 |
[% L.hidden_tag('sort_by', FORM.sort_by) %] |
|
37 |
[% L.hidden_tag('sort_dir', FORM.sort_dir) %] |
|
38 |
[% L.hidden_tag('page', FORM.page) %] |
|
39 |
[% L.submit_tag(LIST_ACTION, LxERP.t8('Continue'))%] |
|
40 |
|
|
41 |
<a href="#" onClick="javascript:$('#filter_table input,#filter_table select').val('');">[% LxERP.t8('Reset') %]</a> |
|
42 |
|
|
43 |
</div> |
|
44 |
|
|
45 |
</form> |
|
46 |
</div> |
templates/webpages/mass_invoice_create_print_from_do/list_invoices.html | ||
---|---|---|
1 |
[% USE HTML %][% USE L %][% USE LxERP %] |
|
2 |
|
|
3 |
<h1>[% FORM.title %]</h1> |
|
4 |
|
|
5 |
[%- INCLUDE "common/flash.html" %] |
|
6 |
|
|
7 |
[% LIST_ACTION = 'action_list_invoices' %] |
|
8 |
[%- PROCESS 'mass_invoice_create_print_from_do/_filter.html' filter=SELF.filter %] |
|
9 |
|
|
10 |
[% IF noshow == 1 %] |
|
11 |
[% invoices = SELF.invoice_models.get; |
|
12 |
MODELS = SELF.invoice_models %] |
|
13 |
[%- IF !invoices.size %] |
|
14 |
<p> |
|
15 |
[%- LxERP.t8("There are currently no open invoices, or none matches your filter conditions.") %] |
|
16 |
</p> |
|
17 |
[%- ELSE %] |
|
18 |
|
|
19 |
<form method="post" action="controller.pl"> |
|
20 |
<table width="100%"> |
|
21 |
<thead> |
|
22 |
<tr class="listheading"> |
|
23 |
<th>[% L.checkbox_tag("", id="check_all", checkall="[data-checkall=1]") %]</th> |
|
24 |
<th>[% L.sortable_table_header("transdate") %]</th> |
|
25 |
<th>[% L.sortable_table_header("reqdate") %]</th> |
|
26 |
<th>[% L.sortable_table_header("invnumber") %]</th> |
|
27 |
<th>[% L.sortable_table_header("donumber") %]</th> |
|
28 |
<th>[% L.sortable_table_header("customer") %]</th> |
|
29 |
<th>[% LxERP.t8("Shipto") %]</th> |
|
30 |
</tr> |
|
31 |
</thead> |
|
32 |
|
|
33 |
<tbody> |
|
34 |
[%- FOREACH invoice = invoices %] |
|
35 |
[% invoice_id = invoice.id |
|
36 |
delivery_order = invoice.delivery_order %] |
|
37 |
<tr class="listrow"> |
|
38 |
<td>[% L.checkbox_tag('id[]', value=invoice.id, "data-checkall"=1, checked=selected_ids.$invoice_id) %]</td> |
|
39 |
<td>[% HTML.escape(invoice.transdate_as_date) %]</td> |
|
40 |
<td>[% HTML.escape(invoice.deliverydate_as_date) %]</td> |
|
41 |
<td>[% L.link(SELF.url_for(controller="is.pl", action="edit", type="sales_invoice", id=invoice.id), invoice.invnumber) %]</td> |
|
42 |
<td> |
|
43 |
[% IF delivery_order %] |
|
44 |
[% L.link(SELF.url_for(controller="do.pl", action="edit", id=delivery_order.id), delivery_order.donumber) %] |
|
45 |
[% ELSE %] |
|
46 |
[% HTML.escape(invoice.donumber) %] |
|
47 |
[% END %] |
|
48 |
</td> |
|
49 |
<td>[% HTML.escape(invoice.customer.name) %]</td> |
|
50 |
<td>[% HTML.escape(SELF.make_shipto_title(invoice.shipto || delivery_order.custom_shipto)) %]</td> |
|
51 |
</tr> |
|
52 |
[%- END %] |
|
53 |
</tbody> |
|
54 |
</table> |
|
55 |
|
|
56 |
[% IF !SELF.invoice_ids.size %] |
|
57 |
[% L.paginate_controls %] |
|
58 |
[% END %] |
|
59 |
|
|
60 |
<hr size="3" noshade> |
|
61 |
|
|
62 |
[% IF SELF.printers.size %] |
|
63 |
<p> |
|
64 |
[% LxERP.t8("Print destination") %]: |
|
65 |
[% SET printers = [ { description=LxERP.t8("Download PDF, do not print") } ] ; |
|
66 |
CALL printers.import(SELF.printers); |
|
67 |
L.select_tag("printer_id", printers, title_key="description", default=FORM.printer_id) %] |
|
68 |
</p> |
|
69 |
[% END %] |
|
70 |
|
|
71 |
<p> |
|
72 |
[% L.hidden_tag("action", "MassInvoiceCreatePrint/dispatch") %] |
|
73 |
[% L.submit_tag("action_print", LxERP.t8("Print")) %] |
|
74 |
</p> |
|
75 |
</form> |
|
76 |
[%- END %] |
|
77 |
[%- END %] |
templates/webpages/mass_invoice_create_print_from_do/list_sales_delivery_orders.html | ||
---|---|---|
1 |
[% USE Dumper %][% USE HTML %][% USE L %][% USE LxERP %] |
|
2 |
|
|
3 |
|
|
4 |
<h1>[% FORM.title %]</h1> |
|
5 |
|
|
6 |
[%- INCLUDE "common/flash.html" %] |
|
7 |
|
|
8 |
[% LIST_ACTION = 'action_list_sales_delivery_orders' %] |
|
9 |
[% SET MODELS = SELF.sales_delivery_order_models; |
|
10 |
dummy = MODELS.finalize %] |
|
11 |
|
|
12 |
[%- PROCESS 'mass_invoice_create_print_from_do/_filter.html' filter=SELF.sales_delivery_order_models.filtered.laundered %] |
|
13 |
|
|
14 |
[% IF noshow == 1 %] |
|
15 |
[% SET sales_delivery_orders = MODELS.get %] |
|
16 |
<form method="post" action="controller.pl"> |
|
17 |
[% IF !sales_delivery_orders.size %] |
|
18 |
<p> |
|
19 |
[%- LxERP.t8("There are currently no open sales delivery orders.") %] |
|
20 |
</p> |
|
21 |
[%- ELSE %] |
|
22 |
<table width="100%"> |
|
23 |
<thead> |
|
24 |
<tr class="listheading"> |
|
25 |
<th>[% L.checkbox_tag("", id="check_all", checkall="[data-checkall=1]") %]</th> |
|
26 |
<th>[% L.sortable_table_header("transdate") %]</th> |
|
27 |
<th>[% L.sortable_table_header("donumber") %]</th> |
|
28 |
<th>[% L.sortable_table_header("ordnumber") %]</th> |
|
29 |
<th>[% L.sortable_table_header("customer") %]</th> |
|
30 |
</tr> |
|
31 |
</thead> |
|
32 |
|
|
33 |
<tbody> |
|
34 |
[%- FOREACH sales_delivery_order = sales_delivery_orders %] |
|
35 |
<tr class="listrow"> |
|
36 |
<td>[% L.checkbox_tag('id[]', value=sales_delivery_order.id, "data-checkall"=1) %]</td> |
|
37 |
<td>[% HTML.escape(sales_delivery_order.transdate_as_date) %]</td> |
|
38 |
<td>[% L.link(SELF.url_for(controller="do.pl", action="edit", type="sales_delivery_order", id=sales_delivery_order.id), sales_delivery_order.donumber) %]</td> |
|
39 |
<td>[% L.link(SELF.url_for(controller="oe.pl", action="edit", type="sales_order", id=sales_delivery_order.sales_order.id), sales_delivery_order.ordnumber) %]</td> |
|
40 |
<td>[% HTML.escape(sales_delivery_order.customer.name) %]</td> |
|
41 |
</tr> |
|
42 |
[%- END %] |
|
43 |
</tbody> |
|
44 |
</table> |
|
45 |
|
|
46 |
[% L.paginate_controls %] |
|
47 |
|
|
48 |
<hr size="3" noshade> |
|
49 |
|
|
50 |
<p> |
|
51 |
[% L.hidden_tag("action", "MassInvoiceCreatePrint/create_invoices") %] |
|
52 |
[% L.button_tag("", LxERP.t8("Create invoices"), name="create_button") %] |
|
53 |
[% L.button_tag("", LxERP.t8("For all delivery orders create and print invoices"), name="create_print_all_button") %] |
|
54 |
</p> |
|
55 |
<div id="create_print_all_dialog" style="display: none;"> |
|
56 |
[%- INCLUDE 'mass_invoice_create_print_from_do/_create_print_all_step_1.html' %] |
|
57 |
</div> |
|
58 |
[%- END %] |
|
59 |
</form> |
|
60 |
[%- END %] |
Auch abrufbar als: Unified diff
Massenkonvertierung von Lieferscheinen nach Rechnung ink. Druck
Die Konvertierung als auch das Generieren des PDFs erfolgt als
Background-Job. Entsprechend muss der task_server für den.
Mandanten eingestellt sein.
Details und bekannte offene Punkte im POD der beiden Perl-Module.
Folgecommit: changelog und all