Revision 61fd623b
Von Cem Aydin vor mehr als 1 Jahr hinzugefügt
SL/AP.pm | ||
---|---|---|
50 | 50 |
use Data::Dumper; |
51 | 51 |
use List::Util qw(sum0); |
52 | 52 |
use strict; |
53 |
use URI::Escape; |
|
53 | 54 |
|
54 | 55 |
sub post_transaction { |
55 | 56 |
my ($self, $myconfig, $form, $provided_dbh, %params) = @_; |
... | ... | |
161 | 162 |
transdate = ?, ordnumber = ?, vendor_id = ?, taxincluded = ?, |
162 | 163 |
amount = ?, duedate = ?, deliverydate = ?, tax_point = ?, paid = ?, netamount = ?, |
163 | 164 |
currency_id = (SELECT id FROM currencies WHERE name = ?), notes = ?, department_id = ?, storno = ?, storno_id = ?, |
164 |
globalproject_id = ?, direct_debit = ?, payment_id = ?, transaction_description = ?, intnotes = ? |
|
165 |
globalproject_id = ?, direct_debit = ?, payment_id = ?, transaction_description = ?, intnotes = ?, |
|
166 |
qrbill_data = ? |
|
165 | 167 |
WHERE id = ?|; |
166 | 168 |
@values = ($form->{invnumber}, conv_date($form->{transdate}), |
167 | 169 |
$form->{ordnumber}, conv_i($form->{vendor_id}), |
... | ... | |
174 | 176 |
$form->{direct_debit} ? 't' : 'f', |
175 | 177 |
conv_i($form->{payment_id}), $form->{transaction_description}, |
176 | 178 |
$form->{intnotes}, |
179 |
$form->{qrbill_data_encoded} ? uri_unescape($form->{qrbill_data_encoded}) : undef, |
|
177 | 180 |
$form->{id}); |
178 | 181 |
do_query($form, $dbh, $query, @values); |
179 | 182 |
|
SL/Controller/ScanQRBill.pm | ||
---|---|---|
1 |
package SL::Controller::ScanQRBill; |
|
2 |
|
|
3 |
use strict; |
|
4 |
use parent qw(SL::Controller::Base); |
|
5 |
|
|
6 |
use List::Util qw(first); |
|
7 |
use URI::Escape; |
|
8 |
|
|
9 |
use SL::Helper::QrBillParser; |
|
10 |
use SL::DB::Vendor; |
|
11 |
use SL::DB::Chart; |
|
12 |
use SL::DB::Tax; |
|
13 |
use SL::DB::ValidityToken; |
|
14 |
|
|
15 |
use Rose::Object::MakeMethods::Generic( |
|
16 |
#scalar => [ qw() ], |
|
17 |
'scalar --get_set_init' => [ qw(vendors accounts_AP_amount accounts_AP taxcharts) ], |
|
18 |
); |
|
19 |
|
|
20 |
# check permissions |
|
21 |
__PACKAGE__->run_before(sub { $::auth->assert('ap_transactions'); }); |
|
22 |
|
|
23 |
################ actions ################# |
|
24 |
|
|
25 |
sub action_scan_view { |
|
26 |
my ($self) = @_; |
|
27 |
|
|
28 |
$::request->layout->add_javascripts('html5-qrcode.js'); |
|
29 |
$::request->layout->add_javascripts('kivi.ScanQRBill.js'); |
|
30 |
|
|
31 |
$self->render('scan_qrbill/scan_view', |
|
32 |
transaction_success => $::form->{transaction_success} // '0', |
|
33 |
invoice_number => $::form->{invnumber} // '', |
|
34 |
developer => $::auth->assert('developer', 1) ? '1' : '0', |
|
35 |
); |
|
36 |
} |
|
37 |
|
|
38 |
sub action_handle_scan_result { |
|
39 |
my ($self) = @_; |
|
40 |
|
|
41 |
my $qrtext = $::form->{qrtext}; |
|
42 |
|
|
43 |
# load text into object |
|
44 |
$self->{qr_obj} = SL::Helper::QrBillParser->new($qrtext); |
|
45 |
|
|
46 |
# check if valid qr-bill |
|
47 |
if (!$self->{qr_obj}->is_valid) { |
|
48 |
return $self->js |
|
49 |
->run('kivi.ScanQRBill.popupInvalidQRBill', $self->{qr_obj}->error) |
|
50 |
->render(); |
|
51 |
} |
|
52 |
|
|
53 |
my $vendor_name = $self->{qr_obj}->{creditor}->{name}; |
|
54 |
$self->{vendor} = first { $_->{name} eq $vendor_name } @{ $self->vendors }; |
|
55 |
|
|
56 |
if (!$self->{vendor}) { |
|
57 |
return $self->js |
|
58 |
->run('kivi.ScanQRBill.popupVendorNotFound', $vendor_name) |
|
59 |
->render(); |
|
60 |
} |
|
61 |
|
|
62 |
$self->prepare_add_purchase_transaction(); |
|
63 |
} |
|
64 |
|
|
65 |
################# internal ############### |
|
66 |
|
|
67 |
sub prepare_add_purchase_transaction { |
|
68 |
my ($self) = @_; |
|
69 |
|
|
70 |
my $qr_obj = $self->{qr_obj}; |
|
71 |
|
|
72 |
my $token = SL::DB::ValidityToken->create(scope => SL::DB::ValidityToken::SCOPE_PURCHASE_INVOICE_POST())->token; |
|
73 |
|
|
74 |
my $html = $self->render('scan_qrbill/_add_purchase_transaction', |
|
75 |
{ output => 0 }, |
|
76 |
vendor => { |
|
77 |
name => $self->{vendor}->{name}, |
|
78 |
number => $self->{vendor}->{vendornumber}, |
|
79 |
id => $self->{vendor}->{id}, |
|
80 |
}, |
|
81 |
qrbill => { |
|
82 |
unstructured_message => $qr_obj->{additional_information}->{unstructured_message}, |
|
83 |
reference_type => $qr_obj->{payment_reference}->{reference_type}, |
|
84 |
reference => $qr_obj->{payment_reference}->{reference}, |
|
85 |
amount => $qr_obj->{payment_amount_information}->{amount}, |
|
86 |
currency => $qr_obj->{payment_amount_information}->{currency}, |
|
87 |
data_encoded => uri_escape($qr_obj->raw_data), |
|
88 |
}, |
|
89 |
accounts_AP_amount => $self->accounts_AP_amount, |
|
90 |
accounts_AP => $self->accounts_AP, |
|
91 |
taxcharts => $self->taxcharts, |
|
92 |
form_validity_token => $token, |
|
93 |
); |
|
94 |
|
|
95 |
$self->js->html('#main-content', $html)->render(); |
|
96 |
} |
|
97 |
|
|
98 |
sub init_vendors { |
|
99 |
SL::DB::Manager::Vendor->get_all(); |
|
100 |
} |
|
101 |
|
|
102 |
sub init_accounts_AP_amount { |
|
103 |
[ map { { |
|
104 |
text => "$_->{accno} - $_->{description}", |
|
105 |
accno => $_->{accno}, |
|
106 |
id => $_->{id}, |
|
107 |
chart_id => $_->{id}, |
|
108 |
} } @{ SL::DB::Manager::Chart->get_all( |
|
109 |
query => [ SL::DB::Manager::Chart->link_filter('AP_amount') ], |
|
110 |
sort_by => 'id ASC') } |
|
111 |
]; |
|
112 |
} |
|
113 |
|
|
114 |
sub init_accounts_AP { |
|
115 |
[ map { { |
|
116 |
text => "$_->{accno} - $_->{description}", |
|
117 |
accno => $_->{accno}, |
|
118 |
id => $_->{id}, |
|
119 |
chart_id => $_->{id}, |
|
120 |
} } @{ SL::DB::Manager::Chart->get_all( |
|
121 |
query => [ SL::DB::Manager::Chart->link_filter('AP') ], |
|
122 |
sort_by => 'id ASC') } |
|
123 |
]; |
|
124 |
} |
|
125 |
|
|
126 |
sub init_taxcharts { |
|
127 |
[ map { { |
|
128 |
text => "$_->{taxkey} - $_->{taxdescription} " . ($_->{rate} * 100) .' %', |
|
129 |
id => "$_->{id}--$_->{rate}", |
|
130 |
} } @{ SL::DB::Manager::Tax->get_all( |
|
131 |
where => [ chart_categories => { like => '%E%' }], |
|
132 |
sort_by => 'taxkey, rate') } |
|
133 |
]; |
|
134 |
} |
|
135 |
|
|
136 |
1; |
|
137 |
|
|
138 |
__END__ |
|
139 |
|
|
140 |
=pod |
|
141 |
|
|
142 |
=encoding utf-8 |
|
143 |
|
|
144 |
=head1 NAME |
|
145 |
|
|
146 |
SL::Controller::ScanQRBill - Controller for scanning swiss QR-Bills using the mobile template |
|
147 |
|
|
148 |
=head1 DESCRIPTION |
|
149 |
|
|
150 |
Renders the scan view in the mobile template and handles the scan result. |
|
151 |
|
|
152 |
The scanned QR-Bill data is parsed and the vendor is searched in the database. |
|
153 |
|
|
154 |
If everything is valid an add purchase transaction view is rendered and |
|
155 |
the QR-Bill can be saved as a purchase transaction. |
|
156 |
|
|
157 |
The post function from ap.pl is used to save the purchase transaction. |
|
158 |
|
|
159 |
The raw data of the QR-Bill is stored with the purchase transaction in the ap table |
|
160 |
in the field qrbill_data. |
|
161 |
The data can later be accessed again using the parser module SL::Helper::QrBillParser. |
|
162 |
|
|
163 |
=head1 SECURITY CONSIDERATIONS |
|
164 |
|
|
165 |
In theory an attacker could try to insert a malicious Javascript code into a qr code, |
|
166 |
that is then scanned, and redisplayed in the browser (XSS). |
|
167 |
|
|
168 |
Therefore it is important to escape any data coming from the qr code when it is rendered |
|
169 |
in the templates. For this we use the template toolkit html filter: [% qrdata | html %], |
|
170 |
Jquery's text function: $('#qrdata').text(qrdata);, and URI::Escape; for the raw data. |
|
171 |
|
|
172 |
For database insertion we use prepared statements (AP.pm). |
|
173 |
|
|
174 |
=head1 TESTING |
|
175 |
|
|
176 |
To simplify testing the scan view shows some buttons to send example qr codes, when in |
|
177 |
developer mode. Sending is implemented in Javascript in js/kivi.ScanQRBill.js. |
|
178 |
|
|
179 |
=head1 URL ACTIONS |
|
180 |
|
|
181 |
=over 4 |
|
182 |
|
|
183 |
=item C<scan_view> |
|
184 |
|
|
185 |
Renders the scan view in the mobile template. |
|
186 |
|
|
187 |
=item C<handle_scan_result> |
|
188 |
|
|
189 |
Handles the scan result and renders the add purchase transaction view. |
|
190 |
|
|
191 |
=back |
|
192 |
|
|
193 |
=head1 TODO |
|
194 |
|
|
195 |
=head2 Additional features: |
|
196 |
|
|
197 |
=over 4 |
|
198 |
|
|
199 |
=item * automatically extract invoice number and dates etc. from "SWICO-String" if present |
|
200 |
|
|
201 |
=item * Option to add the vendor if not found |
|
202 |
|
|
203 |
=back |
|
204 |
|
|
205 |
=head1 BUGS |
|
206 |
|
|
207 |
Nothing here yet. |
|
208 |
|
|
209 |
=head1 AUTHOR |
|
210 |
|
|
211 |
Cem Aydin E<lt>cem.aydin@revamp-it.chE<gt> |
|
212 |
|
|
213 |
=cut |
bin/mozilla/ap.pl | ||
---|---|---|
919 | 919 |
SL::Helper::Flash::flash_later('info', $msg); |
920 | 920 |
print $form->redirect_header($form->{callback}); |
921 | 921 |
$::dispatcher->end_request; |
922 |
|
|
922 |
} elsif ($form->{callback} =~ /ScanQRCode/) { |
|
923 |
# callback/redirect when coming from mobile view (swiss qr bill scan) |
|
924 |
print $form->redirect_header(build_std_url( |
|
925 |
"script=controller.pl", |
|
926 |
'action=ScanQRBill/scan_view', |
|
927 |
'transaction_success=1', |
|
928 |
'invnumber=' . E($form->{invnumber}) |
|
929 |
)); |
|
930 |
$::dispatcher->end_request; |
|
923 | 931 |
} elsif ('doc-tab' eq $form->{after_action}) { |
924 | 932 |
# Redirect with callback containing a fragment does not work (by now) |
925 | 933 |
# because the callback info is stored in the session an parsing the |
js/kivi.ScanQRBill.js | ||
---|---|---|
1 |
namespace('kivi.ScanQRBill', function(ns) { |
|
2 |
|
|
3 |
ns.onScanSuccess = async (decodedText, decodedResult) => { |
|
4 |
// stop camera |
|
5 |
await html5Qrcode.stop(); |
|
6 |
|
|
7 |
// send the scanned text to the server |
|
8 |
const data = []; |
|
9 |
data.push({ name: 'qrtext', value: decodedText }); |
|
10 |
data.push({ name: 'action', value: 'ScanQRBill/handle_scan_result' }); |
|
11 |
$.post("controller.pl", data, kivi.eval_json_result); |
|
12 |
} |
|
13 |
|
|
14 |
ns.onScanFailure = (error) => { |
|
15 |
// handle scan failure, usually better to ignore and keep scanning. |
|
16 |
//console.warn(`Code scan error = ${error}`); |
|
17 |
} |
|
18 |
|
|
19 |
ns.popupInvalidQRBill = (error) => { |
|
20 |
console.warn('popupInvalidQRBill', error); |
|
21 |
$('#qr_code_invalid_error').text(error); |
|
22 |
$('#qr_code_invalid_modal').modal('open'); |
|
23 |
} |
|
24 |
|
|
25 |
ns.popupVendorNotFound = (vn) => { |
|
26 |
//console.warn('popupVendorNotFound', vn); |
|
27 |
$('#vendor_name').text(vn); |
|
28 |
$('#vendor_not_found_error').modal('open'); |
|
29 |
} |
|
30 |
|
|
31 |
ns.sendTestCode = async (code) => { |
|
32 |
// function to easily send code without scanning |
|
33 |
// use for testing only |
|
34 |
if (html5Qrcode.isScanning) { |
|
35 |
// stop camera |
|
36 |
await html5Qrcode.stop(); |
|
37 |
} |
|
38 |
const data = []; |
|
39 |
const codes = [ |
|
40 |
"SPC\r\n0200\r\n1\r\nCH4431999123000889012\r\nS\r\nMax Muster & Söhne\r\nMusterstrasse\r\n123\r\n8000\r\nSeldwyla\r\nCH\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n1949.75\r\nCHF\r\nS\r\nSimon Muster\r\nMusterstrasse\r\n1\r\n8000\r\nSeldwyla\r\nCH\r\nQRR\r\n210000000003139471430009017\r\nOrder from 15.10.2020\r\nEPD\r\n//S1/10/1234/11/201021/30/102673386/32/7.7/40/0:30\r\nName AV1: UV;UltraPay005;12345\r\nName AV2: XY;XYService;54321", |
|
41 |
"SPC\n0200\n1\nCH5800791123000889012\nS\nMuster Krankenkasse\nMusterstrasse\n12\n8000\nSeldwyla\nCH\n\n\n\n\n\n\n\n211.00\nCHF\nS\nSarah Beispiel\nMusterstrasse\n1\n8000\nSeldwyla\nCH\nSCOR\nRF240191230100405JSH0438\n\nEPD\n", |
|
42 |
"SPC\n0200\n1\nCH5800791123000889012\nS\nMax Muster & Söhne\nMusterstrasse\n123\n8000\nSeldwyla\nCH\n\n\n\n\n\n\n\n199.95\nCHF\nS\nSarah Beispiel\nMusterstrasse\n1\n78462\nKonstanz\nDE\nSCOR\nRF18539007547034\n\nEPD\n", |
|
43 |
// for testing XSS |
|
44 |
"SPC\r\n0200\r\n1\r\nCH4431999123000889012\r\nS\r\nMax Muster & Söhne\r\nMusterstrasse\r\n123\r\n8000\r\nSeldwyla\r\nCH\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n1949.75\r\nCHF\r\nS\r\nSimon Muster\r\nMusterstrasse\r\n1\r\n8000\r\nSeldwyla\r\nCH\r\nQRR\r\n210000000003139471430009017\r\nOrder from 15.10.2020\r\nEPD\r\n//S1/10/1234/11/201021/30/102673386/32/7.7/40/0:30<script>alert('XSS!');</script>\r\nName AV1: UV;UltraPay005;12345\r\nName AV2: XY;XYService;54321", |
|
45 |
"<script>alert('XSS!');</script>", |
|
46 |
]; |
|
47 |
data.push({ name: 'qrtext', value: codes[code] }); |
|
48 |
data.push({ name: 'action', value: 'ScanQRBill/handle_scan_result' }); |
|
49 |
$.post("controller.pl", data, kivi.eval_json_result); |
|
50 |
} |
|
51 |
|
|
52 |
}); |
menus/mobile/00-erp.yaml | ||
---|---|---|
10 | 10 |
params: |
11 | 11 |
action: ImageUpload/upload_image |
12 | 12 |
object_type: sales_delivery_order |
13 |
- id: scan_qrbill |
|
14 |
name: Scan swiss QR bill |
|
15 |
order: 200 |
|
16 |
access: ap_transactions |
|
17 |
params: |
|
18 |
action: ScanQRBill/scan_view |
|
13 | 19 |
- id: component_test |
14 | 20 |
name: Component Test |
15 |
order: 200
|
|
21 |
order: 300
|
|
16 | 22 |
access: developer |
17 | 23 |
params: |
18 | 24 |
action: MaterializeTest/components |
19 | 25 |
- id: modal_test |
20 | 26 |
name: Modal Test |
21 |
order: 300
|
|
27 |
order: 400
|
|
22 | 28 |
access: developer |
23 | 29 |
params: |
24 | 30 |
action: MaterializeTest/modal |
templates/mobile_webpages/scan_qrbill/_add_purchase_transaction.html | ||
---|---|---|
1 |
[% USE LxERP %] |
|
2 |
[% USE L %] |
|
3 |
[% USE HTML %] |
|
4 |
[% USE P %] |
|
5 |
[% USE T8 %] |
|
6 |
|
|
7 |
<div class="container"> |
|
8 |
<div class="row"> |
|
9 |
|
|
10 |
<form method="post" action="ap.pl?action=post" id="form" class="col s12"> |
|
11 |
[% L.hidden_tag("rowcount", "1") %] |
|
12 |
[% L.hidden_tag('form_validity_token', form_validity_token) %] |
|
13 |
[% L.hidden_tag('callback', 'ScanQRCode') %] |
|
14 |
[% L.hidden_tag('paidaccounts', '1') %] |
|
15 |
[% L.hidden_tag('taxincluded', '1') %] |
|
16 |
|
|
17 |
[% L.hidden_tag('qrbill_data_encoded', qrbill.data_encoded) %] |
|
18 |
|
|
19 |
<h4>[% 'Add Accounts Payables Transaction' | $T8 %]</h4> |
|
20 |
|
|
21 |
<div class="card"> |
|
22 |
<div class="card-content"> |
|
23 |
<span class="card-title">[% 'Vendor & Order' | $T8 %]</span> |
|
24 |
<p> |
|
25 |
[% 'Vendor' | $T8 %]: [% vendor.number | html %] [% vendor.name | html %] |
|
26 |
[% L.hidden_tag("vendor_id", vendor.id) %] |
|
27 |
[% L.hidden_tag("previous_vendor_id", vendor.id) %] |
|
28 |
</p> |
|
29 |
|
|
30 |
<p> |
|
31 |
[% 'Currency' | $T8 %]: [% qrbill.currency | html %] |
|
32 |
[% L.hidden_tag("currency", qrbill.currency) %] |
|
33 |
</p> |
|
34 |
</div> |
|
35 |
</div> |
|
36 |
|
|
37 |
<h5>[% 'Notes' | $T8 %]</h5> |
|
38 |
<div class="row"> |
|
39 |
[% P.M.textarea_tag("notes", notes, label=LxERP.t8('Notes'), class="col s12") %] |
|
40 |
[% P.M.textarea_tag("intnotes", intnotes, label=LxERP.t8('Internal Notes'), class="col s12") %] |
|
41 |
</div> |
|
42 |
<h5>[% 'Numbers & Dates' | $T8 %]</h5> |
|
43 |
<div class="row"> |
|
44 |
[% P.M.input_tag("invnumber", qrbill.unstructured_message, label=LxERP.t8('Invoice Number'), class="col s12") %] |
|
45 |
[% P.M.input_tag("ordnumber", '', label=LxERP.t8('Order Number'), class="col s12") %] |
|
46 |
[% P.M.date_tag('transdate', '', label=LxERP.t8('Invoice Date'), icon="date_range", class="col s12") %] |
|
47 |
[% P.M.date_tag('duedate', '', label=LxERP.t8('Due Date'), icon="date_range", class="col s12") %] |
|
48 |
</div> |
|
49 |
<h5>[% 'Transactions' | $T8 %]</h5> |
|
50 |
<div class="row"> |
|
51 |
[% P.M.select_tag("AP_amount_chart_id_1", accounts_AP_amount, title_key='text' |
|
52 |
label=LxERP.t8('Account'), class="col s12") %] |
|
53 |
[% L.hidden_tag("AP_amount_chart_id_1_type", "AP_amount") %] |
|
54 |
|
|
55 |
[% P.M.input_tag("amount_1", qrbill.amount, label=LxERP.t8('Amount'), class="col s12") %] |
|
56 |
[% P.M.select_tag("taxchart_1", taxcharts, title_key='text' |
|
57 |
label=LxERP.t8('Taxkey'), class="col s12") %] |
|
58 |
|
|
59 |
[% P.M.select_tag("AP_chart_id", accounts_AP, title_key='text' |
|
60 |
label=LxERP.t8('Contra Account'), class="col s12") %] |
|
61 |
[% L.hidden_tag("AP_chart_id_type", "AP") %] |
|
62 |
</div> |
|
63 |
|
|
64 |
<div class="row"> |
|
65 |
[% P.M.submit_tag("", LxERP.t8('Post'), class="col s12") %] |
|
66 |
</div> |
|
67 |
<div class="row"> |
|
68 |
[% P.M.button_tag("", LxERP.t8('Cancel'), class="col s12", flat=1, small=1, href="controller.pl?action=ScanQRBill/scan_view") %] |
|
69 |
</div> |
|
70 |
</form> |
|
71 |
|
|
72 |
</div> |
|
73 |
</div> |
templates/mobile_webpages/scan_qrbill/scan_view.html | ||
---|---|---|
1 |
[% USE LxERP %] |
|
2 |
[% USE L %] |
|
3 |
[% USE HTML %] |
|
4 |
[% USE P %] |
|
5 |
[% USE T8 %] |
|
6 |
|
|
7 |
<div id="main-content"> |
|
8 |
<div class="container"> |
|
9 |
<div class="row"> |
|
10 |
|
|
11 |
<h4>[% 'Scan swiss QR bill' | $T8 %]</h4> |
|
12 |
|
|
13 |
<div id="QRreader" width="600px"></div> |
|
14 |
|
|
15 |
[% IF developer %] |
|
16 |
<p>[% P.M.button_tag("kivi.ScanQRBill.sendTestCode(0);", "sendTestCode 0" ) %]</p> |
|
17 |
<p>[% P.M.button_tag("kivi.ScanQRBill.sendTestCode(1);", "sendTestCode 1" ) %]</p> |
|
18 |
<p>[% P.M.button_tag("kivi.ScanQRBill.sendTestCode(2);", "sendTestCode 2" ) %]</p> |
|
19 |
<p>[% P.M.button_tag("kivi.ScanQRBill.sendTestCode(3);", "sendTestCode 3 XSS" ) %]</p> |
|
20 |
<p>[% P.M.button_tag("kivi.ScanQRBill.sendTestCode(4);", "sendTestCode 4 XSS (invalid)" ) %]</p> |
|
21 |
[% END %] |
|
22 |
</div> |
|
23 |
</div> |
|
24 |
<hr> |
|
25 |
</div> |
|
26 |
|
|
27 |
<div id="qr_code_invalid_modal" class="modal"> |
|
28 |
<div class="modal-content"> |
|
29 |
<h4>[% 'QR-Code invalid' | $T8 %]</h4> |
|
30 |
|
|
31 |
<p>[% 'The scanned code is not a valid QR bill.' | $T8 %]</p> |
|
32 |
<p>[% 'Error' | $T8 %]: <span id="qr_code_invalid_error"></span></p> |
|
33 |
|
|
34 |
</div> |
|
35 |
<div class="modal-footer"> |
|
36 |
[% P.M.button_tag("startCamera();", LxERP.t8("Try again"), class="modal-close") %] |
|
37 |
</div> |
|
38 |
</div> |
|
39 |
|
|
40 |
<div id="vendor_not_found_error" class="modal"> |
|
41 |
<div class="modal-content"> |
|
42 |
<h4>[% 'Vendor not found' | $T8 %]</h4> |
|
43 |
|
|
44 |
<p>[% 'The vendor could not be found. Please register the vendor with the exact name from the QR bill as shown below.' | $T8 %]</p> |
|
45 |
<p>[% 'Vendor Name' | $T8 %]: '<span id="vendor_name"></span>'</p> |
|
46 |
|
|
47 |
</div> |
|
48 |
<div class="modal-footer"> |
|
49 |
[% P.M.button_tag("startCamera();", LxERP.t8("Try again"), class="modal-close") %] |
|
50 |
</div> |
|
51 |
</div> |
|
52 |
|
|
53 |
<div id="transaction_successful_modal" class="modal"> |
|
54 |
<div class="modal-content"> |
|
55 |
<h4>[% 'AP transaction posted successfully' | $T8 %]</h4> |
|
56 |
|
|
57 |
<p>[% 'Invoice number' | $T8 %]: [% invoice_number | html %]</p> |
|
58 |
</div> |
|
59 |
<div class="modal-footer"> |
|
60 |
[% P.M.button_tag("", LxERP.t8("Ok"), class="modal-close") %] |
|
61 |
</div> |
|
62 |
</div> |
|
63 |
|
|
64 |
[% L.hidden_tag("transaction_success", transaction_success) %] |
|
65 |
|
|
66 |
<script> |
|
67 |
const html5Qrcode = new Html5Qrcode("QRreader", |
|
68 |
{ formatsToSupport: [ Html5QrcodeSupportedFormats.QR_CODE ] }); |
|
69 |
|
|
70 |
const startCamera = () => { |
|
71 |
html5Qrcode.start({ facingMode: "environment" }, |
|
72 |
{ fps: 10, qrbox: { width: 250, height: 250 } }, |
|
73 |
kivi.ScanQRBill.onScanSuccess, kivi.ScanQRBill.onScanFailure |
|
74 |
); |
|
75 |
}; |
|
76 |
|
|
77 |
window.onload = () => { |
|
78 |
// using $(document).ready didn't work here |
|
79 |
//$(document).ready(() => { |
|
80 |
if ($('#transaction_success').val() === '1') { |
|
81 |
$('#transaction_successful_modal').modal('open'); |
|
82 |
} |
|
83 |
|
|
84 |
startCamera(); |
|
85 |
}; |
|
86 |
</script> |
Auch abrufbar als: Unified diff
Schweizer QR-Rechnung: Scan Funktion in mobile design eingebaut
Schweizer QR-Rechnungen können via mobile design eingescannt werden.
Die eingescannten Daten werden in einer Maske angezeigt und können
dann als Kreditorbuchung gespeichert werden.
Zum speichern wird die post funktion aus ap.pl verwendet.
- Menüpunkt im mobile design hinzugefügt
- Neue mobile Templates und controller hinzugefügt
- ap.pl: Anpassung redirect nach post
- AP.pm: QR-Code Daten in Kreditorenbuchung speichern (Verwendung von
URI::Escape, da es sich um mehrzeilige Daten mit Sonderzeichen handelt,
welche über die form weitergegeben werden.)