|  | package SL::Controller::ScanQRBill;
 | 
  
    |  | 
 | 
  
    |  | use strict;
 | 
  
    |  | use parent qw(SL::Controller::Base);
 | 
  
    |  | 
 | 
  
    |  | use List::Util qw(first);
 | 
  
    |  | use URI::Escape;
 | 
  
    |  | 
 | 
  
    |  | use SL::Helper::QrBillParser;
 | 
  
    |  | use SL::DB::Vendor;
 | 
  
    |  | use SL::DB::Chart;
 | 
  
    |  | use SL::DB::Tax;
 | 
  
    |  | use SL::DB::ValidityToken;
 | 
  
    |  | 
 | 
  
    |  | use Rose::Object::MakeMethods::Generic(
 | 
  
    |  |   #scalar => [ qw() ],
 | 
  
    |  |   'scalar --get_set_init' => [ qw(vendors accounts_AP_amount accounts_AP taxcharts) ],
 | 
  
    |  | );
 | 
  
    |  | 
 | 
  
    |  | # check permissions
 | 
  
    |  | __PACKAGE__->run_before(sub { $::auth->assert('ap_transactions'); });
 | 
  
    |  | 
 | 
  
    |  | ################ actions #################
 | 
  
    |  | 
 | 
  
    |  | sub action_scan_view {
 | 
  
    |  |   my ($self) = @_;
 | 
  
    |  | 
 | 
  
    |  |   $::request->layout->add_javascripts('html5-qrcode.js');
 | 
  
    |  |   $::request->layout->add_javascripts('kivi.ScanQRBill.js');
 | 
  
    |  | 
 | 
  
    |  |   $self->render('scan_qrbill/scan_view',
 | 
  
    |  |     transaction_success => $::form->{transaction_success} // '0',
 | 
  
    |  |     invoice_number => $::form->{invnumber} // '',
 | 
  
    |  |     developer => $::auth->assert('developer', 1) ? '1' : '0',
 | 
  
    |  |   );
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | sub action_handle_scan_result {
 | 
  
    |  |   my ($self) = @_;
 | 
  
    |  | 
 | 
  
    |  |   my $qrtext = $::form->{qrtext};
 | 
  
    |  | 
 | 
  
    |  |   # load text into object
 | 
  
    |  |   $self->{qr_obj} = SL::Helper::QrBillParser->new($qrtext);
 | 
  
    |  | 
 | 
  
    |  |   # check if valid qr-bill
 | 
  
    |  |   if (!$self->{qr_obj}->is_valid) {
 | 
  
    |  |     return $self->js
 | 
  
    |  |       ->run('kivi.ScanQRBill.popupInvalidQRBill', $self->{qr_obj}->error)
 | 
  
    |  |       ->render();
 | 
  
    |  |   }
 | 
  
    |  | 
 | 
  
    |  |   my $vendor_name = $self->{qr_obj}->{creditor}->{name};
 | 
  
    |  |   $self->{vendor} = first { $_->{name} eq $vendor_name } @{ $self->vendors };
 | 
  
    |  | 
 | 
  
    |  |   if (!$self->{vendor}) {
 | 
  
    |  |     return $self->js
 | 
  
    |  |       ->run('kivi.ScanQRBill.popupVendorNotFound', $vendor_name)
 | 
  
    |  |       ->render();
 | 
  
    |  |   }
 | 
  
    |  | 
 | 
  
    |  |   $self->prepare_add_purchase_transaction();
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | ################# internal ###############
 | 
  
    |  | 
 | 
  
    |  | sub prepare_add_purchase_transaction {
 | 
  
    |  |   my ($self) = @_;
 | 
  
    |  | 
 | 
  
    |  |   my $qr_obj = $self->{qr_obj};
 | 
  
    |  | 
 | 
  
    |  |   my $token = SL::DB::ValidityToken->create(scope => SL::DB::ValidityToken::SCOPE_PURCHASE_INVOICE_POST())->token;
 | 
  
    |  | 
 | 
  
    |  |   my $html = $self->render('scan_qrbill/_add_purchase_transaction',
 | 
  
    |  |     { output => 0 },
 | 
  
    |  |     vendor => {
 | 
  
    |  |       name => $self->{vendor}->{name},
 | 
  
    |  |       number => $self->{vendor}->{vendornumber},
 | 
  
    |  |       id => $self->{vendor}->{id},
 | 
  
    |  |     },
 | 
  
    |  |     qrbill => {
 | 
  
    |  |       unstructured_message => $qr_obj->{additional_information}->{unstructured_message},
 | 
  
    |  |       reference_type => $qr_obj->{payment_reference}->{reference_type},
 | 
  
    |  |       reference => $qr_obj->{payment_reference}->{reference},
 | 
  
    |  |       amount => $qr_obj->{payment_amount_information}->{amount},
 | 
  
    |  |       currency => $qr_obj->{payment_amount_information}->{currency},
 | 
  
    |  |       data_encoded => uri_escape($qr_obj->raw_data),
 | 
  
    |  |     },
 | 
  
    |  |     accounts_AP_amount => $self->accounts_AP_amount,
 | 
  
    |  |     accounts_AP => $self->accounts_AP,
 | 
  
    |  |     taxcharts => $self->taxcharts,
 | 
  
    |  |     form_validity_token => $token,
 | 
  
    |  |   );
 | 
  
    |  | 
 | 
  
    |  |   $self->js->html('#main-content', $html)->render();
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | sub init_vendors {
 | 
  
    |  |   SL::DB::Manager::Vendor->get_all();
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | sub init_accounts_AP_amount {
 | 
  
    |  |   [ map { {
 | 
  
    |  |       text => "$_->{accno} - $_->{description}",
 | 
  
    |  |       accno => $_->{accno},
 | 
  
    |  |       id => $_->{id},
 | 
  
    |  |       chart_id => $_->{id},
 | 
  
    |  |     } } @{ SL::DB::Manager::Chart->get_all(
 | 
  
    |  |       query   => [ SL::DB::Manager::Chart->link_filter('AP_amount') ],
 | 
  
    |  |       sort_by => 'id ASC') }
 | 
  
    |  |   ];
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | sub init_accounts_AP {
 | 
  
    |  |   [ map { {
 | 
  
    |  |       text => "$_->{accno} - $_->{description}",
 | 
  
    |  |       accno => $_->{accno},
 | 
  
    |  |       id => $_->{id},
 | 
  
    |  |       chart_id => $_->{id},
 | 
  
    |  |     } } @{ SL::DB::Manager::Chart->get_all(
 | 
  
    |  |       query   => [ SL::DB::Manager::Chart->link_filter('AP') ],
 | 
  
    |  |       sort_by => 'id ASC') }
 | 
  
    |  |   ];
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | sub init_taxcharts {
 | 
  
    |  |   [ map { {
 | 
  
    |  |       text => "$_->{taxkey} - $_->{taxdescription} " . ($_->{rate} * 100) .' %',
 | 
  
    |  |       id => "$_->{id}--$_->{rate}",
 | 
  
    |  |     } } @{ SL::DB::Manager::Tax->get_all(
 | 
  
    |  |     where   => [ chart_categories => { like => '%E%' }],
 | 
  
    |  |     sort_by => 'taxkey, rate') }
 | 
  
    |  |   ];
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | 1;
 | 
  
    |  | 
 | 
  
    |  | __END__
 | 
  
    |  | 
 | 
  
    |  | =pod
 | 
  
    |  | 
 | 
  
    |  | =encoding utf-8
 | 
  
    |  | 
 | 
  
    |  | =head1 NAME
 | 
  
    |  | 
 | 
  
    |  | SL::Controller::ScanQRBill - Controller for scanning swiss QR-Bills using the mobile template
 | 
  
    |  | 
 | 
  
    |  | =head1 DESCRIPTION
 | 
  
    |  | 
 | 
  
    |  | Renders the scan view in the mobile template and handles the scan result.
 | 
  
    |  | 
 | 
  
    |  | The scanned QR-Bill data is parsed and the vendor is searched in the database.
 | 
  
    |  | 
 | 
  
    |  | If everything is valid an add purchase transaction view is rendered and
 | 
  
    |  | the QR-Bill can be saved as a purchase transaction.
 | 
  
    |  | 
 | 
  
    |  | The post function from ap.pl is used to save the purchase transaction.
 | 
  
    |  | 
 | 
  
    |  | The raw data of the QR-Bill is stored with the purchase transaction in the ap table
 | 
  
    |  | in the field qrbill_data.
 | 
  
    |  | The data can later be accessed again using the parser module SL::Helper::QrBillParser.
 | 
  
    |  | 
 | 
  
    |  | =head1 SECURITY CONSIDERATIONS
 | 
  
    |  | 
 | 
  
    |  | In theory an attacker could try to insert a malicious Javascript code into a qr code,
 | 
  
    |  | that is then scanned, and redisplayed in the browser (XSS).
 | 
  
    |  | 
 | 
  
    |  | Therefore it is important to escape any data coming from the qr code when it is rendered
 | 
  
    |  | in the templates. For this we use the template toolkit html filter: [% qrdata | html %],
 | 
  
    |  | Jquery's text function: $('#qrdata').text(qrdata);, and URI::Escape; for the raw data.
 | 
  
    |  | 
 | 
  
    |  | For database insertion we use prepared statements (AP.pm).
 | 
  
    |  | 
 | 
  
    |  | =head1 TESTING
 | 
  
    |  | 
 | 
  
    |  | To simplify testing the scan view shows some buttons to send example qr codes, when in
 | 
  
    |  | developer mode. Sending is implemented in Javascript in js/kivi.ScanQRBill.js.
 | 
  
    |  | 
 | 
  
    |  | =head1 URL ACTIONS
 | 
  
    |  | 
 | 
  
    |  | =over 4
 | 
  
    |  | 
 | 
  
    |  | =item C<scan_view>
 | 
  
    |  | 
 | 
  
    |  | Renders the scan view in the mobile template.
 | 
  
    |  | 
 | 
  
    |  | =item C<handle_scan_result>
 | 
  
    |  | 
 | 
  
    |  | Handles the scan result and renders the add purchase transaction view.
 | 
  
    |  | 
 | 
  
    |  | =back
 | 
  
    |  | 
 | 
  
    |  | =head1 TODO
 | 
  
    |  | 
 | 
  
    |  | =head2 Additional features:
 | 
  
    |  | 
 | 
  
    |  | =over 4
 | 
  
    |  | 
 | 
  
    |  | =item * automatically extract invoice number and dates etc. from "SWICO-String" if present
 | 
  
    |  | 
 | 
  
    |  | =item * Option to add the vendor if not found
 | 
  
    |  | 
 | 
  
    |  | =back
 | 
  
    |  | 
 | 
  
    |  | =head1 BUGS
 | 
  
    |  | 
 | 
  
    |  | Nothing here yet.
 | 
  
    |  | 
 | 
  
    |  | =head1 AUTHOR
 | 
  
    |  | 
 | 
  
    |  | Cem Aydin E<lt>cem.aydin@revamp-it.chE<gt>
 | 
  
    |  | 
 | 
  
    |  | =cut
 |