Bugzilla – Attachment 190414 Details for
Bug 40445
Point of Sale reconciliation input during daily summaries
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 40445: Refactor cashup modals to eliminate duplication
dc2f59f.patch (text/plain), 59.16 KB, created by
Martin Renvoize (ashimema)
on 2025-12-10 18:37:03 UTC
(
hide
)
Description:
Bug 40445: Refactor cashup modals to eliminate duplication
Filename:
MIME Type:
Creator:
Martin Renvoize (ashimema)
Created:
2025-12-10 18:37:03 UTC
Size:
59.16 KB
patch
obsolete
>From dc2f59f2481ac84eabf7e369138ae0e852d8ef4b Mon Sep 17 00:00:00 2001 >From: Martin Renvoize <martin.renvoize@openfifth.co.uk> >Date: Wed, 10 Dec 2025 17:58:37 +0000 >Subject: [PATCH] Bug 40445: Refactor cashup modals to eliminate duplication > >This patch refactors the cashup modal implementation across register.tt >and registers.tt pages to eliminate significant code duplication by >following Koha's conventions for shared modal templates and JavaScript. > >Changes: > >1. Created shared modal templates: > - includes/modals/trigger_cashup.inc: Shared trigger cashup modal > - includes/modals/confirm_cashup.inc: Shared confirm cashup modal > with reconciliation support > >2. Created shared JavaScript module: > - js/modals/cashup_modals.js: Initialization functions for both > modals with configuration-driven behavior > >3. Updated backend scripts: > - pos/register.pl: Added redirect_to parameter support for flexible > redirects, fixed regex to accept negative cashup amounts > - pos/registers.pl: Added authorized value and system preference > parameters for consistent functionality > >4. Updated template files: > - pos/register.tt: Replaced inline modals and JavaScript with shared > includes (~165 lines removed) > - pos/registers.tt: Replaced inline modals and JavaScript with > shared includes (~150 lines removed) > >Benefits: >- Eliminates ~355 lines of duplicated code >- Single source of truth for cashup modal logic >- Consistent functionality across both pages >- Flexible redirect behavior (stay on registers page or go to > individual register page) >- Proper support for negative cashup amounts (float deficits) >- Follows established Koha patterns for shared modals > >Test plan: >1. Apply patch and run: yarn build >2. Test cashup workflows on register.tt: > - Start cashup and complete with reconciliation > - Quick cashup > - Test with positive and negative amounts >3. Test cashup workflows on registers.tt: > - Individual register cashup (should redirect back to registers) > - Verify authorized value dropdown appears if configured > - Test required note validation if enabled >4. Verify reconciliation calculations work correctly >5. Test cross-page workflow: start cashup in registers.tt, complete > in register.tt > >Sponsored-by: OpenFifth <https://openfifth.co.uk/> >--- > .../en/includes/modals/confirm_cashup.inc | 106 +++++++ > .../en/includes/modals/trigger_cashup.inc | 50 +++ > .../prog/en/modules/pos/register.tt | 286 ++---------------- > .../prog/en/modules/pos/registers.tt | 265 ++-------------- > .../prog/js/modals/cashup_modals.js | 273 +++++++++++++++++ > pos/register.pl | 18 +- > pos/registers.pl | 17 +- > 7 files changed, 505 insertions(+), 510 deletions(-) > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/includes/modals/confirm_cashup.inc > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/includes/modals/trigger_cashup.inc > create mode 100644 koha-tmpl/intranet-tmpl/prog/js/modals/cashup_modals.js > >diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/modals/confirm_cashup.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/modals/confirm_cashup.inc >new file mode 100644 >index 00000000000..e0f4e53c377 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/modals/confirm_cashup.inc >@@ -0,0 +1,106 @@ >+[% USE raw %] >+<!-- Confirm cashup modal --> >+<!-- Parameters: >+ - modal_id: ID for the modal (required) >+ - reconciliation_note_avs: Authorized values for notes (optional) >+ - reconciliation_note_required: Whether note is required (optional) >+ - cashup_in_progress: Whether completing in-progress cashup (optional) >+ - form_action: Form action URL (optional, defaults to current page) >+ - redirect_to: Where to redirect after completion (optional: 'register' or 'registers') >+--> >+<div class="modal" id="[% modal_id | html %]" tabindex="-1" role="dialog" aria-labelledby="[% modal_id | html %]Label"> >+ <form method="post" enctype="multipart/form-data" class="validated confirm-cashup-form" [% IF form_action %]action="[% form_action | html %]"[% END %]> >+ [% INCLUDE 'csrf-token.inc' %] >+ <div class="modal-dialog"> >+ <div class="modal-content"> >+ <div class="modal-header"> >+ <h1 class="modal-title" id="[% modal_id | html %]Label"> >+ [% IF cashup_in_progress %] >+ Complete cashup of <em><span class="register-name">[% register.description | html %]</span></em> >+ [% ELSE %] >+ Confirm cashup of <em><span class="register-name"></span></em> >+ [% END %] >+ </h1> >+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> >+ </div> >+ <div class="modal-body"> >+ <fieldset class="rows"> >+ <ol> >+ <li> >+ <span class="label expected-amount-label"> >+ [% IF cashup_in_progress %] >+ [% IF cashup_in_progress.amount < 0 %] >+ Expected amount to add: >+ [% ELSE %] >+ Expected cashup amount: >+ [% END %] >+ [% ELSE %] >+ Expected cashup amount: >+ [% END %] >+ </span> >+ <span class="expected-amount">[% IF cashup_in_progress %][% cashup_in_progress.amount | $Price %][% END %]</span> >+ </li> >+ <li> >+ <label class="required actual-amount-label" for="cashup_amount"> >+ [% IF cashup_in_progress %] >+ [% IF cashup_in_progress.amount < 0 %] >+ Actual amount added to register: >+ [% ELSE %] >+ Actual cashup amount counted: >+ [% END %] >+ [% ELSE %] >+ Actual cashup amount counted: >+ [% END %] >+ </label> >+ <input type="text" inputmode="decimal" pattern="^-?\d+(\.\d{2})?$" id="cashup_amount" name="amount" class="cashup-amount-input" required="required" /> >+ <span class="required">Required</span> >+ </li> >+ <li class="reconciliation-display" style="display: none;"> >+ <span class="label">Reconciliation:</span> >+ <span class="reconciliation-text"></span> >+ </li> >+ <li class="reconciliation-note-field" style="display: none;"> >+ <label class="reconciliation-note-label" for="cashup_reconciliation_note"> Note[% IF reconciliation_note_required %](required)[% ELSE %](optional)[% END %]: </label> >+ [% IF reconciliation_note_avs %] >+ <select id="cashup_reconciliation_note" class="reconciliation-note-input" name="reconciliation_note"> >+ <option value="">-- Select a reason --</option> >+ [% FOREACH av IN reconciliation_note_avs %] >+ <option value="[% av.authorised_value | html %]">[% av.lib | html %]</option> >+ [% END %] >+ </select> >+ [% IF reconciliation_note_required %] >+ <span class="required">Required</span> >+ [% END %] >+ [% ELSE %] >+ <textarea >+ id="cashup_reconciliation_note" >+ class="reconciliation-note-input" >+ name="reconciliation_note" >+ rows="3" >+ cols="40" >+ maxlength="1000" >+ placeholder="Enter a note explaining the surplus or deficit..." >+ ></textarea> >+ [% IF reconciliation_note_required %] >+ <span class="required">Required</span> >+ [% END %] >+ <div class="hint">Maximum 1000 characters</div> >+ [% END %] >+ </li> >+ </ol> >+ </fieldset> >+ </div> >+ <div class="modal-footer"> >+ <input type="hidden" name="registerid" class="register-id-field" value="[% IF cashup_in_progress %][% register.id | html %][% END %]" /> >+ <input type="hidden" name="op" value="cud-cashup" /> >+ [% IF redirect_to %] >+ <input type="hidden" name="redirect_to" value="[% redirect_to | html %]" /> >+ [% END %] >+ <button type="submit" class="btn btn-primary"> [% IF cashup_in_progress %]Complete cashup[% ELSE %]Confirm cashup[% END %] </button> >+ <button type="button" class="btn btn-default" data-bs-dismiss="modal">Cancel</button> >+ </div> >+ </div> >+ </div> >+ </form> >+</div> >+<!-- /#[% modal_id | html %] --> >diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/modals/trigger_cashup.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/modals/trigger_cashup.inc >new file mode 100644 >index 00000000000..302e985f5c6 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/modals/trigger_cashup.inc >@@ -0,0 +1,50 @@ >+[% USE raw %] >+<!-- Trigger cashup modal --> >+<!-- Parameters: >+ - modal_id: ID for the modal (required) >+ - register_description: Register description to pre-populate (optional, for register.tt) >+ - register_id: Register ID to pre-populate (optional, for register.tt) >+ - form_action: Form action URL (optional, defaults to current page) >+ - redirect_to: Where to redirect after completion (optional: 'register' or 'registers') >+--> >+<div class="modal" id="[% modal_id | html %]" tabindex="-1" role="dialog" aria-labelledby="[% modal_id | html %]Label"> >+ <form method="post" class="validated trigger-cashup-form" [% IF form_action %]action="[% form_action | html %]"[% END %]> >+ [% INCLUDE 'csrf-token.inc' %] >+ <div class="modal-dialog"> >+ <div class="modal-content"> >+ <div class="modal-header"> >+ <h1 class="modal-title" id="[% modal_id | html %]Label"> >+ Cashup for <em><span class="register-description">[% IF register_description %][% register_description | html %][% END %]</span></em> >+ </h1> >+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> >+ </div> >+ <div class="modal-body"> >+ <p><strong>Choose how to proceed with the cashup:</strong></p> >+ <p><strong>Start cashup</strong></p> >+ <ul class="start-cashup-instructions"> >+ <!-- JavaScript will populate this based on positive/negative amount --> >+ </ul> >+ <p><strong>Quick cashup</strong></p> >+ <ul class="quick-cashup-instructions"> >+ <!-- JavaScript will populate this based on positive/negative amount --> >+ </ul> >+ <p class="float-reminder-text"> >+ <!-- JavaScript will populate this based on positive/negative amount --> >+ </p> >+ </div> >+ <div class="modal-footer"> >+ <input type="hidden" name="registerid" class="register-id-field" value="[% IF register_id %][% register_id | html %][% END %]" /> >+ <input type="hidden" name="op" value="cud-cashup_start" /> >+ <input type="hidden" name="amount" value="" /> >+ [% IF redirect_to %] >+ <input type="hidden" name="redirect_to" value="[% redirect_to | html %]" /> >+ [% END %] >+ <button type="submit" class="btn btn-primary">Start cashup</button> >+ <button type="button" class="btn btn-success quick-cashup-btn">Quick cashup</button> >+ <button type="button" class="btn btn-default" data-bs-dismiss="modal">Cancel</button> >+ </div> >+ </div> >+ </div> >+ </form> >+</div> >+<!-- /#[% modal_id | html %] --> >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/pos/register.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/pos/register.tt >index 70ff0ae0133..6f98d0ba496 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/pos/register.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/pos/register.tt >@@ -114,7 +114,9 @@ > [% IF cashup_in_progress %] > <button id="pos_complete_cashup" type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#confirmCashupModal"> <i class="fa-solid fa-check"></i> Complete cashup </button> > [% ELSE %] >- <button type="button" class="btn btn-default" data-bs-toggle="modal" data-bs-target="#triggerCashupModal" [% IF total_bankable == 0 %]disabled title="No bankable transactions"[% END %]> <i class="fa fa-money-bill-alt"></i> Record cashup </button> >+ <button type="button" class="btn btn-default" data-bs-toggle="modal" data-bs-target="#triggerCashupModal" [% IF total_bankable == 0 %]disabled title="No bankable transactions"[% END %]> >+ <i class="fa fa-money-bill-alt"></i> Record cashup >+ </button> > [% END %] > </div> > [% END %] >@@ -425,97 +427,6 @@ > [% END %] > [% END %] > >-<!-- Confirm cashup modal --> >-<div class="modal" id="confirmCashupModal" tabindex="-1" role="dialog" aria-labelledby="confirmCashupLabel"> >- <form id="cashup_form" method="post" enctype="multipart/form-data" class="validated"> >- [% INCLUDE 'csrf-token.inc' %] >- <div class="modal-dialog"> >- <div class="modal-content"> >- <div class="modal-header"> >- <h1 class="modal-title" id="confirmCashupLabel"> >- [% IF cashup_in_progress %] >- Complete cashup of <em>[% register.description | html %]</em> >- [% ELSE %] >- Confirm cashup of <em>[% register.description | html %]</em> >- [% END %] >- </h1> >- <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> >- </div> >- <div class="modal-body"> >- <fieldset class="rows"> >- <ol> >- <li> >- <span class="label"> >- [% IF cashup_in_progress %] >- [% IF cashup_in_progress.amount < 0 %] >- Expected amount to add: >- [% ELSE %] >- Expected cashup amount: >- [% END %] >- [% ELSE %] >- Expected amount to remove: >- [% END %] >- </span> >- <span id="expected_amount" class="expected-amount">[% cashup_in_progress.amount | $Price %]</span> >- </li> >- <li> >- <label class="required" for="amount"> >- [% IF cashup_in_progress %] >- [% IF cashup_in_progress.amount < 0 %] >- Actual amount added to register: >- [% ELSE %] >- Actual cashup amount counted: >- [% END %] >- [% ELSE %] >- Actual amount removed from register: >- [% END %] >- </label> >- <input type="text" inputmode="decimal" pattern="^-?\d+(\.\d{2})?$" id="amount" name="amount" required="required" /> >- <span class="required">Required</span> >- </li> >- <li id="reconciliation_display" style="display: none;"> >- <span class="label">Reconciliation:</span> >- <span id="reconciliation_text"></span> >- </li> >- <li id="reconciliation_note_field" style="display: none;"> >- <label for="reconciliation_note" id="reconciliation_note_label">Note:</label> >- [% IF reconciliation_note_avs %] >- <select id="reconciliation_note" name="reconciliation_note"> >- <option value="">-- Select a reason --</option> >- [% FOREACH av IN reconciliation_note_avs %] >- <option value="[% av.authorised_value | html %]">[% av.lib | html %]</option> >- [% END %] >- </select> >- [% IF reconciliation_note_required %] >- <span class="required">Required</span> >- [% END %] >- [% ELSE %] >- <textarea id="reconciliation_note" name="reconciliation_note" rows="3" cols="40" maxlength="1000" placeholder="Enter a note explaining the surplus or deficit..."></textarea> >- [% IF reconciliation_note_required %] >- <span class="required">Required</span> >- [% END %] >- <div class="hint">Maximum 1000 characters</div> >- [% END %] >- </li> >- </ol> >- </fieldset> >- </div> >- <!-- /.modal-body --> >- <div class="modal-footer"> >- <input type="hidden" name="registerid" value="[% register.id | html %]" /> >- <input type="hidden" name="op" value="cud-cashup" /> >- <button type="submit" class="btn btn-primary" id="pos_cashup_confirm"> [% IF cashup_in_progress %]Complete cashup[% ELSE %]Confirm cashup[% END %] </button> >- <button type="button" class="btn btn-default" data-bs-dismiss="modal">Cancel</button> >- </div> >- <!-- /.modal-footer --> >- </div> >- <!-- /.modal-content --> >- </div> >- <!-- /.modal-dialog --> >- </form> >-</div> >-<!-- /#confirmCashupModal --> >- > <!-- Issue refund modal --> > <div class="modal" id="issueRefundModal" tabindex="-1" role="dialog" aria-labelledby="issueRefundLabel"> > <form id="refund_form" method="post" enctype="multipart/form-data" class="validated"> >@@ -559,62 +470,27 @@ > </div> > <!-- /#issueRefundModal --> > >-<!-- Trigger cashup modal --> >-<div class="modal" id="triggerCashupModal" tabindex="-1" role="dialog" aria-labelledby="triggerCashupLabel"> >- <form method="post" class="validated"> >- [% INCLUDE 'csrf-token.inc' %] >- <div class="modal-dialog"> >- <div class="modal-content"> >- <div class="modal-header"> >- <h1 class="modal-title" id="triggerCashupLabel"> Cashup for <em>[% register.description | html %]</em> </h1> >- <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> >- </div> >- <div class="modal-body"> >- <p><strong>Choose how to proceed with the cashup:</strong></p> >- <p><strong>Start cashup</strong></p> >- <ul id="start_cashup_instructions_individual"> >- <!-- JavaScript will populate this based on positive/negative amount --> >- </ul> >- <p><strong>Quick cashup</strong></p> >- <ul id="quick_cashup_instructions_individual"> >- <!-- JavaScript will populate this based on positive/negative amount --> >- </ul> >- <p id="float_reminder_text_individual"> >- <!-- JavaScript will populate this based on positive/negative amount --> >- </p> >- </div> >- <div class="modal-footer"> >- <input type="hidden" name="registerid" value="[% register.id | html %]" /> >- <input type="hidden" name="op" value="cud-cashup_start" /> >- <input type="hidden" name="amount" value="" /> >- <input type="hidden" id="register_bankable_amount" value="[% accountlines.total( payment_type => [ 'CASH', 'SIP00' ]) * -1 | html %]" /> >- <button type="submit" class="btn btn-primary">Start cashup</button> >- <button type="button" class="btn btn-success" id="quick_cashup_btn_individual">Quick cashup</button> >- <button type="button" class="btn btn-default" data-bs-dismiss="modal">Cancel</button> >- </div> >- </div> >- </div> >- </form> >-</div> >-<!-- /#triggerCashupModal --> >- > [% INCLUDE 'modals/cashup_summary.inc' %] >+[% >+ INCLUDE 'modals/trigger_cashup.inc' >+ modal_id='triggerCashupModal' >+ register_description=register.description >+ register_id=register.id >+%] >+[% >+ INCLUDE 'modals/confirm_cashup.inc' >+ modal_id='confirmCashupModal' >+ reconciliation_note_avs=reconciliation_note_avs >+ reconciliation_note_required=reconciliation_note_required >+ cashup_in_progress=cashup_in_progress >+%] > > [% MACRO jsinclude BLOCK %] > [% INCLUDE 'datatables.inc' %] > [% INCLUDE 'format_price.inc' %] > [% INCLUDE 'js-date-format.inc' %] >- <script> >- // Map of authorized value codes to descriptions for reconciliation notes >- var reconciliation_note_avs = { >- [% IF reconciliation_note_avs %] >- [% FOREACH av IN reconciliation_note_avs %] >- "[% av.authorised_value | html %]": "[% av.lib | html %]"[% UNLESS loop.last %],[% END %] >- [% END %] >- [% END %] >- }; >- </script> > [% Asset.js("js/cashup_modal.js") | $raw %] >+ [% Asset.js("js/modals/cashup_modals.js") | $raw %] > [% Asset.js("js/modal_printer.js") | $raw %] > [% INCLUDE 'calendar.inc' %] > <script> >@@ -746,128 +622,16 @@ > ] > }, null, 1); > >- // Real-time reconciliation calculation for cashup modal >- $("#amount").on("input", function() { >- var actualAmount = parseFloat($(this).val()) || 0; >- var expectedText = $("#expected_amount").text().replace(/[£$,]/g, ''); >- var expectedAmount = parseFloat(expectedText) || 0; >- var difference = actualAmount - expectedAmount; >- >- if ($(this).val() && !isNaN(actualAmount)) { >- var reconciliationText = ""; >- var reconciliationClass = ""; >- var hasDiscrepancy = false; >- >- if (difference > 0) { >- reconciliationText = "Surplus: " + difference.format_price(); >- reconciliationClass = "success"; >- hasDiscrepancy = true; >- } else if (difference < 0) { >- reconciliationText = "Deficit: " + Math.abs(difference).format_price(); >- reconciliationClass = "warning"; >- hasDiscrepancy = true; >- } else { >- reconciliationText = "Balanced - no surplus or deficit"; >- reconciliationClass = "success"; >- hasDiscrepancy = false; >- } >- >- $("#reconciliation_text").text(reconciliationText) >- .removeClass("success warning") >- .addClass(reconciliationClass); >- $("#reconciliation_display").show(); >- >- // Show/hide note field based on whether there's a discrepancy >- if (hasDiscrepancy) { >- $("#reconciliation_note_field").show(); >- // Update required attribute and label based on system preference >- [% IF reconciliation_note_required %] >- $("#reconciliation_note").attr('required', 'required'); >- $("#reconciliation_note_label").addClass("required"); >- [% ELSE %] >- $("#reconciliation_note").removeAttr('required'); >- $("#reconciliation_note_label").removeClass("required"); >- [% END %] >- } else { >- $("#reconciliation_note_field").hide(); >- $("#reconciliation_note").val(''); // Clear note when balanced >- $("#reconciliation_note").removeAttr('required'); >- $("#reconciliation_note_label").removeClass("required"); >- } >- } else { >- $("#reconciliation_display").hide(); >- $("#reconciliation_note_field").hide(); >- } >- }); >- >- // Reset modal when opened >- $("#confirmCashupModal").on("shown.bs.modal", function() { >- // Start with empty actual amount field (user must enter amount) >- $("#amount").val('').focus(); >- $("#reconciliation_display").hide(); >- $("#reconciliation_note_field").hide(); >- $("#reconciliation_note").val(''); >+ // Initialize cashup modals >+ initTriggerCashupModal('#triggerCashupModal', { >+ bankableAmount: [% accountlines.total( payment_type => [ 'CASH', 'SIP00' ]) * -1 | html %], >+ registerFloat: [% register.starting_float | html %] > }); > >- // Handle triggerCashupModal for individual register page >- $("#triggerCashupModal").on("shown.bs.modal", function(e){ >- var bankableAmount = parseFloat($("#register_bankable_amount").val()); >- var isNegative = bankableAmount < 0; >- var formattedAmount = Math.abs(bankableAmount).format_price(); >- var floatAmount = parseFloat([% register.starting_float | html %]).format_price(); >- >- // Update Start cashup instructions >- if (isNegative) { >- $("#start_cashup_instructions_individual").html( >- '<li>' + _("Count cash in the register") + '</li>' + >- '<li>' + _("The register can continue operating during counting") + '</li>' + >- '<li>' + _("Complete the cashup by adding cash to restore the float") + '</li>' >- ); >- } else { >- $("#start_cashup_instructions_individual").html( >- '<li>' + _("Remove cash from the register for counting") + '</li>' + >- '<li>' + _("The register can continue operating during counting") + '</li>' + >- '<li>' + _("Complete the cashup once counted") + '</li>' >- ); >- } >- >- // Update Quick cashup instructions >- if (isNegative) { >- $("#quick_cashup_instructions_individual").html( >- '<li>' + _("Top up the register with %s to restore the float").format(formattedAmount) + '</li>' >- ); >- } else { >- $("#quick_cashup_instructions_individual").html( >- '<li>' + _("Confirm you have removed %s cash from the register to bank immediately").format(formattedAmount) + '</li>' >- ); >- } >- >- // Update float reminder >- if (isNegative) { >- $("#float_reminder_text_individual").html( >- _("This will bring the register back to the expected float of <strong>%s</strong>").format(floatAmount) >- ); >- } else { >- $("#float_reminder_text_individual").html( >- _("Remember to leave the float amount of <strong>%s</strong> in the register.").format(floatAmount) >- ); >- } >- }); >- >- // Handle Quick cashup button click for individual register >- $("#quick_cashup_btn_individual").on("click", function(e){ >- e.preventDefault(); >- var form = $(this).closest('form'); >- var bankableAmount = $("#register_bankable_amount").val(); >- >- // Change operation to cud-cashup (quick cashup) >- form.find('input[name="op"]').val('cud-cashup'); >- >- // Set the amount to the expected bankable amount >- form.find('input[name="amount"]').val(bankableAmount); >- >- // Submit the form >- form.submit(); >+ initConfirmCashupModal('#confirmCashupModal', { >+ hasAuthorisedValues: [% reconciliation_note_avs ? 'true' : 'false' %], >+ noteRequired: [% reconciliation_note_required ? 'true' : 'false' %], >+ isInProgress: [% cashup_in_progress ? 'true' : 'false' %] > }); > </script> > [% END %] >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/pos/registers.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/pos/registers.tt >index e983c8ed7c2..657fdff2e6d 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/pos/registers.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/pos/registers.tt >@@ -257,46 +257,6 @@ > [% END %] > [% END %] > >-<!-- Trigger cashup modal for individual registers --> >-<div class="modal" id="triggerCashupModalRegister" tabindex="-1" role="dialog" aria-labelledby="triggerCashupLabelRegister"> >- <form method="post" class="validated"> >- [% INCLUDE 'csrf-token.inc' %] >- <div class="modal-dialog"> >- <div class="modal-content"> >- <div class="modal-header"> >- <h1 class="modal-title" id="triggerCashupLabelRegister"> >- Cashup for <em><span id="register_desc"></span></em> >- </h1> >- <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> >- </div> >- <div class="modal-body"> >- <p><strong>Choose how to proceed with the cashup:</strong></p> >- <p><strong>Start cashup</strong></p> >- <ul id="start_cashup_instructions"> >- <!-- JavaScript will populate this based on positive/negative amount --> >- </ul> >- <p><strong>Quick cashup</strong></p> >- <ul id="quick_cashup_instructions"> >- <!-- JavaScript will populate this based on positive/negative amount --> >- </ul> >- <p id="float_reminder_text"> >- <!-- JavaScript will populate this based on positive/negative amount --> >- </p> >- </div> >- <div class="modal-footer"> >- <input type="hidden" name="registerid" id="register_id_field" value="" /> >- <input type="hidden" name="op" value="cud-cashup_start" /> >- <input type="hidden" name="amount" value="" /> >- <button type="submit" class="btn btn-primary">Start cashup</button> >- <button type="button" class="btn btn-success" id="quick_cashup_btn">Quick cashup</button> >- <button type="button" class="btn btn-default" data-bs-dismiss="modal">Cancel</button> >- </div> >- </div> >- </div> >- </form> >-</div> >-<!-- /#triggerCashupModalRegister --> >- > <!-- Confirm cashup selected modal --> > <div class="modal" id="confirmCashupSelectedModal" tabindex="-1" role="dialog" aria-labelledby="confirmCashupSelectedLabel"> > <form method="post" class="validated"> >@@ -359,58 +319,6 @@ > </div> > <!-- /#confirmCashupSelectedModal --> > >-<!-- Confirm cashup modal --> >-<div class="modal" id="confirmCashupModal" tabindex="-1" role="dialog" aria-labelledby="confirmCashupLabel"> >- <form id="cashup_individual_form" method="post" enctype="multipart/form-data" class="validated"> >- [% INCLUDE 'csrf-token.inc' %] >- <div class="modal-dialog"> >- <div class="modal-content"> >- <div class="modal-header"> >- <h1 class="modal-title" id="confirmCashupLabel"> >- Confirm cashup of <em><span id="registerc"></span></em> >- </h1> >- <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> >- </div> >- <div class="modal-body"> >- <fieldset class="rows"> >- <ol> >- <li> >- <span class="label" id="expected_amount_label">Expected cashup amount:</span> >- <span id="cashc" class="expected-amount"></span> >- </li> >- <li> >- <label class="required" for="amount" id="actual_amount_label">Actual cashup amount counted:</label> >- <input type="text" inputmode="decimal" pattern="^-?\d+(\.\d{2})?$" id="amount" name="amount" required="required" /> >- <span class="required">Required</span> >- </li> >- <li id="reconciliation_display" style="display: none;"> >- <span class="label">Reconciliation:</span> >- <span id="reconciliation_text"></span> >- </li> >- <li id="reconciliation_note_field" style="display: none;"> >- <label for="reconciliation_note">Note (optional):</label> >- <textarea id="reconciliation_note" name="reconciliation_note" rows="3" cols="40" maxlength="1000" placeholder="Enter a note explaining the surplus or deficit..."></textarea> >- <div class="hint">Maximum 1000 characters</div> >- </li> >- </ol> >- </fieldset> >- </div> >- <!-- /.modal-body --> >- <div class="modal-footer"> >- <input type="hidden" name="registerid" id="cashup_registerid" value="" /> >- <input type="hidden" name="op" value="cud-cashup" /> >- <button type="submit" class="btn btn-primary" id="cashup_confirm"> Complete cashup</button> >- <button type="button" class="btn btn-default" data-bs-dismiss="modal">Cancel</button> >- </div> >- <!-- /.modal-footer --> >- </div> >- <!-- /.modal-content --> >- </div> >- <!-- /.modal-dialog --> >- </form> >-</div> >-<!-- /#confirmCashupModal --> >- > <!-- Confirm cashupall modal --> > <div class="modal" id="confirmCashupAllModal" tabindex="-1" role="dialog" aria-labelledby="confirmCashupAllLabel"> > <form id="cashup_all_form" method="post" enctype="multipart/form-data"> >@@ -444,12 +352,27 @@ > <!-- /#confirmCashupAllModal --> > > [% INCLUDE 'modals/cashup_summary.inc' %] >+[% >+ INCLUDE 'modals/trigger_cashup.inc' >+ modal_id='triggerCashupModalRegister' >+ form_action='/cgi-bin/koha/pos/register.pl' >+ redirect_to='registers' >+%] >+[% >+ INCLUDE 'modals/confirm_cashup.inc' >+ modal_id='confirmCashupModal' >+ reconciliation_note_avs=reconciliation_note_avs >+ reconciliation_note_required=reconciliation_note_required >+ form_action='/cgi-bin/koha/pos/register.pl' >+ redirect_to='registers' >+%] > > [% MACRO jsinclude BLOCK %] > [% INCLUDE 'datatables.inc' %] > [% INCLUDE 'format_price.inc' %] > [% INCLUDE 'js-date-format.inc' %] > [% Asset.js("js/cashup_modal.js") | $raw %] >+ [% Asset.js("js/modals/cashup_modals.js") | $raw %] > [% Asset.js("js/modal_printer.js") | $raw %] > <script> > $(document).ready(function () { >@@ -465,160 +388,12 @@ > paginationType: "full", > }); > >- // Real-time reconciliation calculation for cashup modal >- $("#amount").on("input", function() { >- var actualAmount = parseFloat($(this).val()) || 0; >- var expectedText = $("#expected_amount").text().replace(/[£$,]/g, ''); >- var expectedAmount = parseFloat(expectedText) || 0; >- var difference = actualAmount - expectedAmount; >- >- if ($(this).val() && !isNaN(actualAmount)) { >- var reconciliationText = ""; >- var reconciliationClass = ""; >- var hasDiscrepancy = false; >- >- if (difference > 0) { >- reconciliationText = "Surplus: " + difference.format_price(); >- reconciliationClass = "success"; >- hasDiscrepancy = true; >- } else if (difference < 0) { >- reconciliationText = "Deficit: " + Math.abs(difference).format_price(); >- reconciliationClass = "warning"; >- hasDiscrepancy = true; >- } else { >- reconciliationText = "Balanced - no surplus or deficit"; >- reconciliationClass = "success"; >- hasDiscrepancy = false; >- } >- >- $("#reconciliation_text").text(reconciliationText) >- .removeClass("success warning") >- .addClass(reconciliationClass); >- $("#reconciliation_display").show(); >- >- // Show/hide note field based on whether there's a discrepancy >- if (hasDiscrepancy) { >- $("#reconciliation_note_field").show(); >- } else { >- $("#reconciliation_note_field").hide(); >- $("#reconciliation_note").val(''); // Clear note when balanced >- } >- } else { >- $("#reconciliation_display").hide(); >- $("#reconciliation_note_field").hide(); >- } >- }); >- >- // Reset modal when opened >- $("#confirmCashupModal").on("shown.bs.modal", function(e){ >- var button = $(e.relatedTarget); >- var register = button.data('register'); >- $("#registerc").text(register); >- var expected = button.data('expected'); >- $("#cashc").text(expected); >- >- // Parse expected amount to check if negative >- var expectedAmount = expected.replace(/[^0-9.-]/g, ''); >- var isNegative = parseFloat(expectedAmount) < 0; >- >- // Update labels based on sign >- if (isNegative) { >- $("#expected_amount_label").text(_("Expected amount to add:")); >- $("#actual_amount_label").text(_("Actual amount added to register:")); >- } else { >- $("#expected_amount_label").text(_("Expected cashup amount:")); >- $("#actual_amount_label").text(_("Actual cashup amount counted:")); >- } >- >- var rfloat = button.data('float'); >- $('#floatc').text(rfloat); >- var rid = button.data('registerid'); >- $('#cashup_registerid').val(rid); >- $("#amount").val('').focus(); >- $("#reconciliation_display").hide(); >- $("#reconciliation_note_field").hide(); >- $("#reconciliation_note").val(''); >- }); >- >- // Handle the new trigger cashup modal for individual registers >- $("#triggerCashupModalRegister").on("shown.bs.modal", function(e){ >- var button = $(e.relatedTarget); >- var register = button.data('register'); >- $("#register_desc").text(register); >- var bankable = button.data('bankable'); >- var rfloat = button.data('float'); >- var rid = button.data('registerid'); >- $('#register_id_field').val(rid); >- >- // Guard against undefined/null bankable value >- if (!bankable) { >- console.error("Bankable amount is undefined"); >- return; >- } >- >- // Parse bankable amount (remove currency formatting, keep minus sign) >- var bankableAmount = String(bankable).replace(/[^0-9.-]/g, ''); >- var numericAmount = parseFloat(bankableAmount); >- var isNegative = numericAmount < 0; >- >- // Format the absolute amount for display >- var absAmountFormatted = Math.abs(numericAmount).format_price(); >- >- // Update Start cashup instructions >- if (isNegative) { >- $("#start_cashup_instructions").html( >- '<li>' + _("Count cash in the register") + '</li>' + >- '<li>' + _("The register can continue operating during counting") + '</li>' + >- '<li>' + _("Complete the cashup by adding cash to restore the float") + '</li>' >- ); >- } else { >- $("#start_cashup_instructions").html( >- '<li>' + _("Remove cash from the register for counting") + '</li>' + >- '<li>' + _("The register can continue operating during counting") + '</li>' + >- '<li>' + _("Complete the cashup once counted") + '</li>' >- ); >- } >- >- // Update Quick cashup instructions >- if (isNegative) { >- $("#quick_cashup_instructions").html( >- '<li>' + _("Top up the register with %s to restore the float").format(absAmountFormatted) + '</li>' >- ); >- } else { >- $("#quick_cashup_instructions").html( >- '<li>' + _("Confirm you have removed %s cash from the register to bank immediately").format(absAmountFormatted) + '</li>' >- ); >- } >- >- // Update float reminder >- if (isNegative) { >- $("#float_reminder_text").html( >- _("This will bring the register back to the expected float of <strong>%s</strong>").format(rfloat) >- ); >- } else { >- $("#float_reminder_text").html( >- _("Remember to leave the float amount of <strong>%s</strong> in the register.").format(rfloat) >- ); >- } >- >- // Store bankable amount for quick cashup (with sign) >- $('#triggerCashupModalRegister').data('bankable-amount', bankableAmount); >- }); >- >- // Handle Quick cashup button click >- $("#quick_cashup_btn").on("click", function(e){ >- e.preventDefault(); >- var form = $(this).closest('form'); >- var bankableAmount = $('#triggerCashupModalRegister').data('bankable-amount'); >- >- // Change operation to cud-cashup (quick cashup) >- form.find('input[name="op"]').val('cud-cashup'); >- >- // Set the amount to the expected bankable amount >- form.find('input[name="amount"]').val(bankableAmount); >+ // Initialize cashup modals >+ initTriggerCashupModal('#triggerCashupModalRegister'); > >- // Submit the form >- form.submit(); >+ initConfirmCashupModal('#confirmCashupModal', { >+ hasAuthorisedValues: [% reconciliation_note_avs ? 'true' : 'false' %], >+ noteRequired: [% reconciliation_note_required ? 'true' : 'false' %] > }); > > // Select all registers functionality >diff --git a/koha-tmpl/intranet-tmpl/prog/js/modals/cashup_modals.js b/koha-tmpl/intranet-tmpl/prog/js/modals/cashup_modals.js >new file mode 100644 >index 00000000000..5ced6d1bce9 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/js/modals/cashup_modals.js >@@ -0,0 +1,273 @@ >+/** >+ * Cashup Modal JavaScript Module >+ * Shared initialization functions for cashup modals across POS register pages >+ */ >+ >+/** >+ * Initialize trigger cashup modal behavior >+ * @param {string} modalSelector - jQuery selector for the modal (e.g., '#triggerCashupModal') >+ * @param {object} options - Configuration options >+ * @param {number} options.registerFloat - Starting float amount (for register.tt) >+ * @param {number} options.bankableAmount - Bankable amount (for register.tt) >+ */ >+function initTriggerCashupModal(modalSelector, options) { >+ options = options || {}; >+ >+ $(modalSelector).on("shown.bs.modal", function (e) { >+ var button = $(e.relatedTarget); >+ var modal = $(this); >+ >+ // Get data from button (for registers.tt) or options (for register.tt) >+ var register = button.data("register"); >+ var bankable = button.data("bankable"); >+ var rfloat = button.data("float"); >+ var rid = button.data("registerid"); >+ >+ // For register.tt, use options if provided >+ if (options.bankableAmount !== undefined) { >+ bankable = options.bankableAmount; >+ } >+ if (options.registerFloat !== undefined) { >+ rfloat = options.registerFloat; >+ } >+ >+ // Populate register description if available >+ if (register) { >+ modal.find(".register-description").text(register); >+ } >+ >+ // Set register ID if available >+ if (rid) { >+ modal.find(".register-id-field").val(rid); >+ } >+ >+ // Guard against undefined/null bankable value >+ if (bankable === undefined || bankable === null) { >+ console.error("Bankable amount is undefined"); >+ return; >+ } >+ >+ // Parse bankable amount (remove currency formatting, keep minus sign) >+ var bankableAmount = String(bankable).replace(/[^0-9.-]/g, ""); >+ var numericAmount = parseFloat(bankableAmount); >+ var isNegative = numericAmount < 0; >+ >+ // Format amounts for display >+ var absAmountFormatted = Math.abs(numericAmount).format_price(); >+ var floatFormatted = rfloat; >+ if (typeof rfloat === "number") { >+ floatFormatted = rfloat.format_price(); >+ } >+ >+ // Update Start cashup instructions >+ var startInstructions; >+ if (isNegative) { >+ startInstructions = >+ "<li>" + >+ _("Count cash in the register") + >+ "</li>" + >+ "<li>" + >+ _("The register can continue operating during counting") + >+ "</li>" + >+ "<li>" + >+ _("Complete the cashup by adding cash to restore the float") + >+ "</li>"; >+ } else { >+ startInstructions = >+ "<li>" + >+ _("Remove cash from the register for counting") + >+ "</li>" + >+ "<li>" + >+ _("The register can continue operating during counting") + >+ "</li>" + >+ "<li>" + >+ _("Complete the cashup once counted") + >+ "</li>"; >+ } >+ modal.find(".start-cashup-instructions").html(startInstructions); >+ >+ // Update Quick cashup instructions >+ var quickInstructions; >+ if (isNegative) { >+ quickInstructions = >+ "<li>" + >+ _("Top up the register with %s to restore the float").format( >+ absAmountFormatted >+ ) + >+ "</li>"; >+ } else { >+ quickInstructions = >+ "<li>" + >+ _( >+ "Confirm you have removed %s cash from the register to bank immediately" >+ ).format(absAmountFormatted) + >+ "</li>"; >+ } >+ modal.find(".quick-cashup-instructions").html(quickInstructions); >+ >+ // Update float reminder >+ var floatReminder; >+ if (isNegative) { >+ floatReminder = _( >+ "This will bring the register back to the expected float of <strong>%s</strong>" >+ ).format(floatFormatted); >+ } else { >+ floatReminder = _( >+ "Remember to leave the float amount of <strong>%s</strong> in the register." >+ ).format(floatFormatted); >+ } >+ modal.find(".float-reminder-text").html(floatReminder); >+ >+ // Store bankable amount for quick cashup (with sign) >+ modal.data("bankable-amount", bankableAmount); >+ }); >+ >+ // Handle Quick cashup button click >+ $(modalSelector + " .quick-cashup-btn").on("click", function (e) { >+ e.preventDefault(); >+ var form = $(this).closest("form"); >+ var modal = $(this).closest(".modal"); >+ var bankableAmount = modal.data("bankable-amount"); >+ >+ // Change operation to cud-cashup (quick cashup) >+ form.find('input[name="op"]').val("cud-cashup"); >+ >+ // Set the amount to the expected bankable amount >+ form.find('input[name="amount"]').val(bankableAmount); >+ >+ // Submit the form >+ form.submit(); >+ }); >+} >+ >+/** >+ * Initialize confirm cashup modal behavior with reconciliation calculation >+ * @param {string} modalSelector - jQuery selector for the modal (e.g., '#confirmCashupModal') >+ * @param {object} options - Configuration options >+ * @param {boolean} options.noteRequired - Whether reconciliation note is required when there's a discrepancy >+ * @param {boolean} options.hasAuthorisedValues - Whether authorized values are configured for notes >+ * @param {boolean} options.isInProgress - Whether this is completing an in-progress cashup (for register.tt) >+ */ >+function initConfirmCashupModal(modalSelector, options) { >+ options = options || {}; >+ var noteRequired = options.noteRequired || false; >+ var hasAuthorisedValues = options.hasAuthorisedValues || false; >+ var isInProgress = options.isInProgress || false; >+ >+ // Real-time reconciliation calculation >+ $(modalSelector + " .cashup-amount-input").on("input", function () { >+ var modal = $(this).closest(".modal"); >+ var actualAmount = parseFloat($(this).val()) || 0; >+ var expectedText = modal >+ .find(".expected-amount") >+ .text() >+ .replace(/[£$,]/g, ""); >+ var expectedAmount = parseFloat(expectedText) || 0; >+ var difference = actualAmount - expectedAmount; >+ >+ if ($(this).val() && !isNaN(actualAmount)) { >+ var reconciliationText = ""; >+ var reconciliationClass = ""; >+ var hasDiscrepancy = false; >+ >+ if (difference > 0) { >+ reconciliationText = "Surplus: " + difference.format_price(); >+ reconciliationClass = "success"; >+ hasDiscrepancy = true; >+ } else if (difference < 0) { >+ reconciliationText = >+ "Deficit: " + Math.abs(difference).format_price(); >+ reconciliationClass = "warning"; >+ hasDiscrepancy = true; >+ } else { >+ reconciliationText = "Balanced - no surplus or deficit"; >+ reconciliationClass = "success"; >+ hasDiscrepancy = false; >+ } >+ >+ modal >+ .find(".reconciliation-text") >+ .text(reconciliationText) >+ .removeClass("success warning") >+ .addClass(reconciliationClass); >+ modal.find(".reconciliation-display").show(); >+ >+ // Show/hide note field based on whether there's a discrepancy >+ if (hasDiscrepancy) { >+ modal.find(".reconciliation-note-field").show(); >+ >+ // Update required attribute and label based on system preference >+ if (noteRequired) { >+ modal >+ .find(".reconciliation-note-input") >+ .attr("required", "required"); >+ modal >+ .find(".reconciliation-note-label") >+ .addClass("required"); >+ } else { >+ modal >+ .find(".reconciliation-note-input") >+ .removeAttr("required"); >+ modal >+ .find(".reconciliation-note-label") >+ .removeClass("required"); >+ } >+ } else { >+ modal.find(".reconciliation-note-field").hide(); >+ modal.find(".reconciliation-note-input").val(""); // Clear note when balanced >+ modal.find(".reconciliation-note-input").removeAttr("required"); >+ modal >+ .find(".reconciliation-note-label") >+ .removeClass("required"); >+ } >+ } else { >+ modal.find(".reconciliation-display").hide(); >+ modal.find(".reconciliation-note-field").hide(); >+ } >+ }); >+ >+ // Reset/populate modal when opened >+ $(modalSelector).on("shown.bs.modal", function (e) { >+ var button = $(e.relatedTarget); >+ var modal = $(this); >+ >+ // For registers.tt: populate from button data >+ if (button.length && button.data("register")) { >+ var register = button.data("register"); >+ modal.find(".register-name").text(register); >+ >+ var expected = button.data("expected"); >+ modal.find(".expected-amount").text(expected); >+ >+ var rid = button.data("registerid"); >+ modal.find(".register-id-field").val(rid); >+ >+ // Parse expected amount to check if negative >+ var expectedAmount = expected.replace(/[^0-9.-]/g, ""); >+ var isNegative = parseFloat(expectedAmount) < 0; >+ >+ // Update labels based on sign >+ if (isNegative) { >+ modal >+ .find(".expected-amount-label") >+ .text(_("Expected amount to add:")); >+ modal >+ .find(".actual-amount-label") >+ .text(_("Actual amount added to register:")); >+ } else { >+ modal >+ .find(".expected-amount-label") >+ .text(_("Expected cashup amount:")); >+ modal >+ .find(".actual-amount-label") >+ .text(_("Actual cashup amount counted:")); >+ } >+ } >+ >+ // Reset fields >+ modal.find(".cashup-amount-input").val("").focus(); >+ modal.find(".reconciliation-display").hide(); >+ modal.find(".reconciliation-note-field").hide(); >+ modal.find(".reconciliation-note-input").val(""); >+ }); >+} >diff --git a/pos/register.pl b/pos/register.pl >index a0752689c37..307437fb528 100755 >--- a/pos/register.pl >+++ b/pos/register.pl >@@ -138,7 +138,13 @@ if ( !$registers->count ) { > } else { > > # Redirect to prevent duplicate submissions (POST/REDIRECT/GET pattern) >- print $input->redirect( "/cgi-bin/koha/pos/register.pl?registerid=" . $registerid ); >+ my $redirect_to = $input->param('redirect_to') || 'register'; >+ >+ if ( $redirect_to eq 'registers' ) { >+ print $input->redirect("/cgi-bin/koha/pos/registers.pl?cashup_start_success=1"); >+ } else { >+ print $input->redirect( "/cgi-bin/koha/pos/register.pl?registerid=" . $registerid ); >+ } > exit; > } > } else { >@@ -149,7 +155,7 @@ if ( !$registers->count ) { > my $amount = $input->param('amount'); > my $reconciliation_note = $input->param('reconciliation_note'); > >- if ( defined $amount && $amount =~ /^\d+(?:\.\d{1,2})?$/ ) { >+ if ( defined $amount && $amount =~ /^-?\d+(?:\.\d{1,2})?$/ ) { > > # Sanitize and limit note length > if ( defined $reconciliation_note ) { >@@ -190,7 +196,13 @@ if ( !$registers->count ) { > } else { > > # Redirect to prevent duplicate submissions (POST/REDIRECT/GET pattern) >- print $input->redirect( "/cgi-bin/koha/pos/register.pl?registerid=" . $registerid ); >+ my $redirect_to = $input->param('redirect_to') || 'register'; >+ >+ if ( $redirect_to eq 'registers' ) { >+ print $input->redirect("/cgi-bin/koha/pos/registers.pl?cashup_complete_success=1"); >+ } else { >+ print $input->redirect( "/cgi-bin/koha/pos/register.pl?registerid=" . $registerid ); >+ } > exit; > } > } else { >diff --git a/pos/registers.pl b/pos/registers.pl >index 2dc7e0fa441..251363e01df 100755 >--- a/pos/registers.pl >+++ b/pos/registers.pl >@@ -42,6 +42,17 @@ my $logged_in_user = Koha::Patrons->find($loggedinuser) or die "Not logged in"; > my $library = Koha::Libraries->find( C4::Context->userenv->{'branch'} ); > $template->param( library => $library ); > >+# Get authorized values for reconciliation notes if configured >+my $note_av_category = C4::Context->preference('CashupReconciliationNoteAuthorisedValue'); >+my $reconciliation_note_avs; >+if ($note_av_category) { >+ require Koha::AuthorisedValues; >+ $reconciliation_note_avs = Koha::AuthorisedValues->search( >+ { category => $note_av_category }, >+ { order_by => { '-asc' => 'lib' } } >+ ); >+} >+ > my $registers = Koha::Cash::Registers->search( > { branch => $library->id, archived => 0 }, > { order_by => { '-asc' => 'name' } } >@@ -50,7 +61,11 @@ my $registers = Koha::Cash::Registers->search( > if ( !$registers->count ) { > $template->param( error_registers => 1 ); > } else { >- $template->param( registers => $registers ); >+ $template->param( >+ registers => $registers, >+ reconciliation_note_avs => $reconciliation_note_avs, >+ reconciliation_note_required => C4::Context->preference('CashupReconciliationNoteRequired'), >+ ); > } > > # Handle success/error messages from redirects >-- >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 40445
:
185289
|
185290
|
185291
|
185292
|
185809
|
186636
|
186637
|
186638
|
186639
|
186640
|
186641
|
186642
|
186643
|
189333
|
189334
|
189335
|
189336
|
189337
|
189338
|
189339
|
189340
|
189341
|
189353
|
189354
|
189355
|
189356
|
189357
|
189358
|
189359
|
189360
|
189361
|
189362
|
189363
|
189364
|
189410
|
189411
|
189412
|
189413
|
189414
|
189415
|
189420
|
189421
|
189422
|
189423
|
189424
|
189425
|
189426
|
189427
|
189428
|
189429
|
189430
|
189476
|
189477
|
189478
|
189479
|
189480
|
189481
|
189482
|
190361
|
190362
|
190363
|
190364
|
190365
|
190366
|
190367
|
190368
|
190369
|
190370
|
190371
|
190372
|
190373
|
190374
|
190375
|
190376
|
190379
|
190380
|
190388
|
190389
|
190404
|
190405
|
190406
|
190407
|
190408
|
190409
|
190410
|
190411
|
190412
|
190413
| 190414 |
190415