Bugzilla – Attachment 192000 Details for
Bug 41705
Popup blockers may be triggered by options to automatically display payment receipt for printing after making a payments
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 41705: Upgrade POS receipt printing to use PRG pattern
3f01282.patch (text/plain), 19.51 KB, created by
Martin Renvoize (ashimema)
on 2026-01-24 16:51:07 UTC
(
hide
)
Description:
Bug 41705: Upgrade POS receipt printing to use PRG pattern
Filename:
MIME Type:
Creator:
Martin Renvoize (ashimema)
Created:
2026-01-24 16:51:07 UTC
Size:
19.51 KB
patch
obsolete
>From 3f01282d2923e37e0104a54f98a1fee86a62002b Mon Sep 17 00:00:00 2001 >From: Martin Renvoize <martin.renvoize@openfifth.co.uk> >Date: Sat, 24 Jan 2026 15:36:18 +0000 >Subject: [PATCH] Bug 41705: Upgrade POS receipt printing to use PRG pattern > >This commit implements a POST-Redirect-GET (PRG) pattern for the Point of Sale. >We also reverse the receipt printing workflow to make it popup-safe when using >FinePaymentAutoPopup. > >- When FinePaymentAutoPopup is enabled, the POS form submits to a new tab >- The server processes the payment and redirects to the receipt print page >- The receipt page auto-prints and attempts to close itself >- When FinePaymentAutoPopup is disabled, the original in-page flow is preserved >- Added error handling for missing receipts >- Modernized JavaScript printing logic >- Added manual print and close buttons to receipt page > >Test Plan: >1. Enable FinePaymentAutoPopup system preference >2. Go to Point of Sale page and complete a sale >3. Confirm the receipt opens in a new tab and auto-prints >4. Close the receipt tab and verify the POS page is ready for next transaction >5. Disable FinePaymentAutoPopup system preference >6. Complete another sale >7. Verify the POS page reloads with 'Print receipt' and 'Email receipt' buttons >8. Click 'Print receipt' and verify it opens in a new tab >9. Test error case: manually modify receipt URL and verify error message displays >--- > .../prog/css/printreceiptinvoice.css | 430 +++++++++--------- > .../prog/en/includes/slip-print.inc | 3 +- > .../intranet-tmpl/prog/en/modules/pos/pay.tt | 17 +- > .../prog/en/modules/pos/printreceipt.tt | 38 +- > pos/pay.pl | 23 +- > pos/printreceipt.pl | 11 +- > 6 files changed, 292 insertions(+), 230 deletions(-) > >diff --git a/koha-tmpl/intranet-tmpl/prog/css/printreceiptinvoice.css b/koha-tmpl/intranet-tmpl/prog/css/printreceiptinvoice.css >index ab9d2549e1d..f2016845f19 100644 >--- a/koha-tmpl/intranet-tmpl/prog/css/printreceiptinvoice.css >+++ b/koha-tmpl/intranet-tmpl/prog/css/printreceiptinvoice.css >@@ -1,322 +1,331 @@ > a:link { >- color : #000000; >- text-decoration: none; >+ color: #000000; >+ text-decoration: none; > } > > a:visited { >- color : #000000; >- text-decoration: none; >+ color: #000000; >+ text-decoration: none; > } > > a:hover { >- color : #993300; >- text-decoration: none; >+ color: #993300; >+ text-decoration: none; > } > > body { >- background-color : #FFF; >- color : #333333; >- font-family : arial, geneva, sans-serif; >- font-size : 14px; >- margin : 0px 0px 0px 0px; >+ background-color: #fff; >+ color: #333333; >+ font-family: arial, geneva, sans-serif; >+ font-size: 14px; >+ margin: 0px 0px 0px 0px; > } > > caption { >- color : #000000; >- font-size : 18px; >- font-weight : bold; >- margin-top : 5px; >- text-align : left; >+ color: #000000; >+ font-size: 18px; >+ font-weight: bold; >+ margin-top: 5px; >+ text-align: left; > } > > form { >- margin : 0px; >- padding : 0px; >+ margin: 0px; >+ padding: 0px; > } > >-h1 >- { >- color : #000000; >- font-size : 22px; >- font-weight : bold; >- margin-bottom : 3px; >- margin-top : 3px; >+h1 { >+ color: #000000; >+ font-size: 22px; >+ font-weight: bold; >+ margin-bottom: 3px; >+ margin-top: 3px; > } > > h1#logo { >- display: none; >+ display: none; > } > > h2 { >- color : #000000; >- font-size : 20px; >- font-weight : bold; >- margin-bottom : 3px; >- margin-top : 3px; >+ color: #000000; >+ font-size: 20px; >+ font-weight: bold; >+ margin-bottom: 3px; >+ margin-top: 3px; > } > > h3 { >- color : #000000; >- font-size : 18px; >- font-weight : bold; >- margin-bottom : 3px; >- margin-top : 3px; >+ color: #000000; >+ font-size: 18px; >+ font-weight: bold; >+ margin-bottom: 3px; >+ margin-top: 3px; > } > > h4 { >- color : #000000; >- font-size : 16px; >- font-weight : bold; >- margin-bottom : 3px; >- margin-top : 3px; >+ color: #000000; >+ font-size: 16px; >+ font-weight: bold; >+ margin-bottom: 3px; >+ margin-top: 3px; > } > > h5 { >- color : #000000; >- font-size : 15px; >- font-weight : bold; >- margin-bottom : 1px; >- margin-top : 1px; >+ color: #000000; >+ font-size: 15px; >+ font-weight: bold; >+ margin-bottom: 1px; >+ margin-top: 1px; > } > > h6 { >- color : #000000; >- font-size : 14px; >- font-weight : bold; >- margin-bottom : 1px; >- margin-top : 1px; >+ color: #000000; >+ font-size: 14px; >+ font-weight: bold; >+ margin-bottom: 1px; >+ margin-top: 1px; > } > > table { >- background-color : #FFFFFF; >- border-bottom : 1px solid #CCCCCC; >- border-collapse : collapse; >- border-left : 1px solid #CCCCCC; >- margin : 3px 0px 5px 0px; >- padding : 0px; >- width : 99%; >+ background-color: #ffffff; >+ border-bottom: 1px solid #cccccc; >+ border-collapse: collapse; >+ border-left: 1px solid #cccccc; >+ margin: 3px 0px 5px 0px; >+ padding: 0px; >+ width: 99%; > } > > table.list { >- background-color : #FFFFFF; >- border : 0; >- border-collapse : collapse; >- margin : 3px 0px 5px 0px; >- padding : 0px; >- width : 99%; >+ background-color: #ffffff; >+ border: 0; >+ border-collapse: collapse; >+ margin: 3px 0px 5px 0px; >+ padding: 0px; >+ width: 99%; > } > > td { >- background-color : #FFF; >- border-bottom : 1px solid #CCCCCC; >- border-right : 1px solid #CCCCCC; >- border-top : 0; >- border-left : 0; >- font-size : 12px; >- padding : 5px 5px 5px 5px; >+ background-color: #fff; >+ border-bottom: 1px solid #cccccc; >+ border-right: 1px solid #cccccc; >+ border-top: 0; >+ border-left: 0; >+ font-size: 12px; >+ padding: 5px 5px 5px 5px; > } > > td:last-child { >- background-color : #FFF; >- border-bottom : 1px solid #CCCCCC; >- border-right : 0px solid #CCCCCC; >- border-top : 0; >- border-left : 0; >- font-size : 12px; >- padding : 5px 5px 5px 5px; >+ background-color: #fff; >+ border-bottom: 1px solid #cccccc; >+ border-right: 0px solid #cccccc; >+ border-top: 0; >+ border-left: 0; >+ font-size: 12px; >+ padding: 5px 5px 5px 5px; > } > > table.list th { >- background-color : #FFFFFF; >- border-top : 0; >- border-left : 0; >- text-align: left; >- font-size: 12px; >- font-weight: bold; >- padding: 5px; >- width : 25%; >+ background-color: #ffffff; >+ border-top: 0; >+ border-left: 0; >+ text-align: left; >+ font-size: 12px; >+ font-weight: bold; >+ padding: 5px; >+ width: 25%; > } > > th { >- background-color : #E9E9E9; >- border-bottom : 1px solid #CCCCCC; >- border-right : 1px solid #CCCCCC; >- border-top : 0; >- border-left : 0; >- font-size : 14px; >- font-weight : bold; >- padding : 5px 5px 5px 5px; >+ background-color: #e9e9e9; >+ border-bottom: 1px solid #cccccc; >+ border-right: 1px solid #cccccc; >+ border-top: 0; >+ border-left: 0; >+ font-size: 14px; >+ font-weight: bold; >+ padding: 5px 5px 5px 5px; > } > > th:last-child { >- background-color : #E9E9E9; >- border-bottom : 1px solid #CCCCCC; >- border-right : 0px solid #CCCCCC; >- border-top : 0; >- border-left : 0; >- font-size : 14px; >- font-weight : bold; >- padding : 5px 5px 5px 5px; >-} >- >-td.input, div.form td, div.details td { >- border-bottom : 1px solid #CCCCCC; >- border-left : 1px solid #CCCCCC; >- border-right : 1px solid #CCCCCC; >- border-top : 1px solid #CCCCCC; >- color : #000000; >- line-height : 1.5em; >- padding : 4px; >- text-align : left; >-} >- >-td.label, div.form th, div.details th { >- background-color : #FFF; >- border-bottom : 1px solid #CCCCCC; >- border-left : 1px solid #CCCCCC; >- border-right : 0px; >- border-top : 0px; >- color : #000088; >- font-weight : bold; >- line-height : 1.5em; >- padding : 4px; >- text-align : right; >- vertical-align : top; >+ background-color: #e9e9e9; >+ border-bottom: 1px solid #cccccc; >+ border-right: 0px solid #cccccc; >+ border-top: 0; >+ border-left: 0; >+ font-size: 14px; >+ font-weight: bold; >+ padding: 5px 5px 5px 5px; >+} >+ >+td.input, >+div.form td, >+div.details td { >+ border-bottom: 1px solid #cccccc; >+ border-left: 1px solid #cccccc; >+ border-right: 1px solid #cccccc; >+ border-top: 1px solid #cccccc; >+ color: #000000; >+ line-height: 1.5em; >+ padding: 4px; >+ text-align: left; >+} >+ >+td.label, >+div.form th, >+div.details th { >+ background-color: #fff; >+ border-bottom: 1px solid #cccccc; >+ border-left: 1px solid #cccccc; >+ border-right: 0px; >+ border-top: 0px; >+ color: #000088; >+ font-weight: bold; >+ line-height: 1.5em; >+ padding: 4px; >+ text-align: right; >+ vertical-align: top; > } > > tr.highlight { >- background-color: #CCCCCC; >- border-bottom : 1px solid #CCCCCC; >- border-left : 1px solid #CCCCCC; >- border-right : 1px solid #CCCCCC; >- border-top : 1px solid #CCCCCC; >+ background-color: #cccccc; >+ border-bottom: 1px solid #cccccc; >+ border-left: 1px solid #cccccc; >+ border-right: 1px solid #cccccc; >+ border-top: 1px solid #cccccc; > } > > .error { >- font-weight: bold; >+ font-weight: bold; > } > > .ex { >- font-family : "Courier New", Courier, monospace; >+ font-family: "Courier New", Courier, monospace; > } > .inline { >- display : inline; >+ display: inline; > } > > tr.overdue { >- font-weight : bold; >+ font-weight: bold; > } > > tfoot { >- font-weight : bold; >+ font-weight: bold; > } > >-tfoot td, tfoot td:last-child { >- background-color : #EEEEEE; >- border-right : 1px solid #666666; >- border-bottom : 1px solid #666666; >+tfoot td, >+tfoot td:last-child { >+ background-color: #eeeeee; >+ border-right: 1px solid #666666; >+ border-bottom: 1px solid #666666; > } > >-td.credit, td.debit, td.sum { >- font-family : "Courier New", Courier, monospace; >- text-align : right; >- border-bottom : 1px solid #CCCCCC; >- border-left : 1px solid #CCCCCC; >- border-right : 1px solid #CCCCCC; >- border-top : 1px solid #CCCCCC; >+td.credit, >+td.debit, >+td.sum { >+ font-family: "Courier New", Courier, monospace; >+ text-align: right; >+ border-bottom: 1px solid #cccccc; >+ border-left: 1px solid #cccccc; >+ border-right: 1px solid #cccccc; >+ border-top: 1px solid #cccccc; > } > > td.debit { >- color : #990000; >- border-bottom : 1px solid #CCCCCC; >- border-left : 1px solid #CCCCCC; >- border-right : 1px solid #CCCCCC; >- border-top : 1px solid #CCCCCC; >+ color: #990000; >+ border-bottom: 1px solid #cccccc; >+ border-left: 1px solid #cccccc; >+ border-right: 1px solid #cccccc; >+ border-top: 1px solid #cccccc; > } > > #receipt { >- background-color : #FFFFFF; >- font-family : serif; >- font-size : .9em; >- margin : 0 0px 0px 0; >- padding : 0px; >- position : relative; >+ background-color: #ffffff; >+ font-family: serif; >+ font-size: 0.9em; >+ margin: 0 0px 0px 0; >+ padding: 0px; >+ position: relative; > } > > #receipt a:link { >- color : #000000; >- text-decoration: none; >+ color: #000000; >+ text-decoration: none; > } > > #receipt a:visited { >- color : #000000; >- text-decoration: none; >+ color: #000000; >+ text-decoration: none; > } > > #receipt a:hover { >- color : #993300; >- text-decoration: none; >+ color: #993300; >+ text-decoration: none; > } > > #receipt caption { >- color : #000000; >- font-weight : bold; >- margin-top : 5px; >- text-align : left; >+ color: #000000; >+ font-weight: bold; >+ margin-top: 5px; >+ text-align: left; > } > > #receipt div.table { >- background-color : transparent; >- font-weight: normal; >+ background-color: transparent; >+ font-weight: normal; > } > >-#receipt h3, #receipt caption, #receipt h4 { >- background-color : transparent; >- color : #000000; >- font-size : 1em; >- font-weight : bold; >- margin-bottom : 3px; >- margin-top : 3px; >+#receipt h3, >+#receipt caption, >+#receipt h4 { >+ background-color: transparent; >+ color: #000000; >+ font-size: 1em; >+ font-weight: bold; >+ margin-bottom: 3px; >+ margin-top: 3px; > } > > #receipt h2 { >- background-color : transparent; >- color : #000000; >- font-weight : bold; >- margin-bottom : 3px; >- margin-top : 3px; >+ background-color: transparent; >+ color: #000000; >+ font-weight: bold; >+ margin-bottom: 3px; >+ margin-top: 3px; > } > > #receipt table { >- background-color : transparent; >- border : 0; >- border-collapse : collapse; >- width : auto; >-} >- >-#receipt td, #receipt th { >- background-color : transparent; >- border : 0; >- font-size : 1em; >- font-weight : normal; >- padding : 1px; >- border-bottom : 1px solid #000000; >- border-left : 1px solid #000000; >- border-right : 1px solid #000000; >- border-top : 1px solid #000000; >- } >+ background-color: transparent; >+ border: 0; >+ border-collapse: collapse; >+ width: auto; >+} >+ >+#receipt td, >+#receipt th { >+ background-color: transparent; >+ border: 0; >+ font-size: 1em; >+ font-weight: normal; >+ padding: 1px; >+ border-bottom: 1px solid #000000; >+ border-left: 1px solid #000000; >+ border-right: 1px solid #000000; >+ border-top: 1px solid #000000; >+} > > #receipt th { >- font-weight : bold; >- text-align : left; >- } >+ font-weight: bold; >+ text-align: left; >+} > > #receipt th.centerednames { >- font-weight : bold; >- text-align: center; >- color: red; >- border-top : 1px solid #000000; >- border-bottom : 0px; >+ font-weight: bold; >+ text-align: center; >+ color: red; >+ border-top: 1px solid #000000; >+ border-bottom: 0px; > } > > div#breadcrumbs, >@@ -327,6 +336,7 @@ div#toolbar, > div#changelanguage, > div#menu, > .noprint, >+.no-print, > form#sortbyform, > #cartDetails, > fieldset.action, >@@ -336,8 +346,8 @@ fieldset.action, > } > > fieldset { >- border : 0; >+ border: 0; > } > #cataloguing_additem_itemlist { >- overflow : visible !important; >+ overflow: visible !important; > } >diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/slip-print.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/slip-print.inc >index 4a39a40a1d2..c6e354db112 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/includes/slip-print.inc >+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/slip-print.inc >@@ -6,12 +6,11 @@ > [% Koha.Preference('IntranetSlipPrinterJS') | $raw %] > [% ELSE %] > let autoprint = true; >- $( window ).load(function() { >+ window.addEventListener('load', function() { > window.print(); > window.onafterprint = function () { > window.close(); > } >- setTimeout('window.close()', 1000); //Hack from Chrome < 63 > }); > [% END %] > </script> >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/pos/pay.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/pos/pay.tt >index f5cb9b2dd03..d5e02ccdf09 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/pos/pay.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/pos/pay.tt >@@ -59,7 +59,11 @@ > </div> > [% END %] > >+ [% IF Koha.Preference('FinePaymentAutoPopup') %] >+ <form name="payForm" id="payForm" method="post" action="/cgi-bin/koha/pos/pay.pl" target="_blank"> >+ [% ELSE %] > <form name="payForm" id="payForm" method="post" action="/cgi-bin/koha/pos/pay.pl"> >+ [% END %] > [% INCLUDE 'csrf-token.inc' %] > <input type="hidden" name="op" value="cud-pay" /> > <div class="row"> >@@ -240,10 +244,7 @@ > </div> > </div> > >-[% IF payment_id && Koha.Preference('FinePaymentAutoPopup') %] >- <!-- Automatic Print Receipt --> >- <a id="printReceipt" style="display: none" href="#"></a> >-[% END %] >+ > > [% MACRO jsinclude BLOCK %] > [% INCLUDE 'format_price.inc' %] >@@ -519,13 +520,7 @@ > } > }); > >- [% IF payment_id && Koha.Preference('FinePaymentAutoPopup') %] >- $("#printReceipt").click(function() { >- var win = window.open('/cgi-bin/koha/pos/printreceipt.pl?action=print&accountlines_id=[% payment_id | uri %]&tendered=[% tendered | uri %]&change=[% change | uri %]', '_blank'); >- win.focus(); >- }); >- $("#printReceipt").click(); >- [% END %] >+ > }); > </script> > [% END %] >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/pos/printreceipt.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/pos/printreceipt.tt >index 53c8b56a851..a2be9ba6b3c 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/pos/printreceipt.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/pos/printreceipt.tt >@@ -38,8 +38,44 @@ > [% END %] > </div> > >+[% IF error_msg %] >+ <div class="alert alert-danger"> >+ <p>[% error_msg | html %]</p> >+ <button id="close-window" class="btn btn-secondary" onclick="window.close();"> >+ [% t("Close") %] >+ </button> >+ </div> >+[% ELSE %] >+ <div class="no-print" style="margin-top: 20px; text-align: center;"> >+ <button id="print-receipt" class="btn btn-primary"> >+ <i class="fa fa-print"></i> [% t("Print receipt") %] >+ </button> >+ <button id="close-window" class="btn btn-secondary" onclick="window.close();"> >+ [% t("Close") %] >+ </button> >+ </div> >+[% END %] >+ > [% MACRO jsinclude BLOCK %] >- [% INCLUDE 'slip-print.inc' #printThenClose %] >+ <script> >+ function doPrint() { >+ window.print(); >+ } >+ >+ // Best-effort auto-close >+ window.onafterprint = () => { >+ window.close(); >+ }; >+ >+ window.addEventListener('load', () => { >+ if ( [% autoprint | js %] == 1 ) { >+ doPrint(); >+ } >+ }); >+ >+ document.getElementById('print-receipt') >+ .addEventListener('click', doPrint); >+ </script> > [% END %] > > [% INCLUDE 'intranet-bottom.inc' %] >diff --git a/pos/pay.pl b/pos/pay.pl >index 73070c9d44e..069983e2f99 100755 >--- a/pos/pay.pl >+++ b/pos/pay.pl >@@ -78,11 +78,24 @@ if ( $op eq 'cud-pay' ) { > > my $payment = $sale->purchase( { payment_type => $payment_type } ); > >- $template->param( >- payment_id => $payment->accountlines_id, >- tendered => scalar $input->param('tendered'), >- change => scalar $input->param('change') >- ); >+ my $redirect_url; >+ if ( C4::Context->preference('FinePaymentAutoPopup') ) { >+ # PRG Flow: Redirect to receipt print page >+ $redirect_url = C4::Context->uri_for("/cgi-bin/koha/pos/printreceipt.pl") >+ . "?accountlines_id=" . $payment->accountlines_id >+ . "&tendered=" . scalar $input->param('tendered') >+ . "&change=" . scalar $input->param('change') >+ . "&autoprint=1"; >+ >+ } else { >+ # PRG Flow: Redirect to pos with payment details >+ $redirect_url = C4::Context->uri_for("/cgi-bin/koha/pos/pay.pl") >+ . '?payment_id=' . $payment->accountlines_id >+ . '&tendered=' . scalar $input->param('tendered') >+ . '&change=' . scalar $input->param('change'); >+ } >+ print $input->redirect( -uri => $redirect_url, -status => 303 ); >+ exit; # Terminate the script immediately after redirect headers are sent > } > } > >diff --git a/pos/printreceipt.pl b/pos/printreceipt.pl >index 321e3db6040..78b48ab01eb 100755 >--- a/pos/printreceipt.pl >+++ b/pos/printreceipt.pl >@@ -37,7 +37,15 @@ my ( $template, $loggedinuser, $cookie ) = get_template_and_user( > > my $payment_id = $input->param('accountlines_id'); > my $payment = Koha::Account::Lines->find($payment_id); >-my $patron = $payment->patron; >+if ( !$payment ) { >+ $template->param( >+ error_msg => 'Receipt not found or access denied.', >+ ); >+ output_html_with_http_headers $input, $cookie, $template->output; >+ exit; >+} >+my $patron = $payment->patron; >+my $autoprint = $input->param('autoprint') // 0; > > my $logged_in_user = Koha::Patrons->find($loggedinuser) or die "Not logged in"; > output_and_exit_if_error( >@@ -71,6 +79,7 @@ $template->param( > slip => $letter->{content}, > plain => !$letter->{is_html}, > style => $letter->{style}, >+ autoprint => $autoprint, > ); > > output_html_with_http_headers $input, $cookie, $template->output; >-- >2.52.0 >
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 41705
: 192000