View | Details | Raw Unified | Return to bug 40445
Collapse All | Expand All

(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/modals/confirm_cashup.inc (+106 lines)
Line 0 Link Here
1
[% USE raw %]
2
<!-- Confirm cashup modal -->
3
<!-- Parameters:
4
     - modal_id: ID for the modal (required)
5
     - reconciliation_note_avs: Authorized values for notes (optional)
6
     - reconciliation_note_required: Whether note is required (optional)
7
     - cashup_in_progress: Whether completing in-progress cashup (optional)
8
     - form_action: Form action URL (optional, defaults to current page)
9
     - redirect_to: Where to redirect after completion (optional: 'register' or 'registers')
10
-->
11
<div class="modal" id="[% modal_id | html %]" tabindex="-1" role="dialog" aria-labelledby="[% modal_id | html %]Label">
12
    <form method="post" enctype="multipart/form-data" class="validated confirm-cashup-form" [% IF form_action %]action="[% form_action | html %]"[% END %]>
13
        [% INCLUDE 'csrf-token.inc' %]
14
        <div class="modal-dialog">
15
            <div class="modal-content">
16
                <div class="modal-header">
17
                    <h1 class="modal-title" id="[% modal_id | html %]Label">
18
                        [% IF cashup_in_progress %]
19
                            Complete cashup of <em><span class="register-name">[% register.description | html %]</span></em>
20
                        [% ELSE %]
21
                            Confirm cashup of <em><span class="register-name"></span></em>
22
                        [% END %]
23
                    </h1>
24
                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
25
                </div>
26
                <div class="modal-body">
27
                    <fieldset class="rows">
28
                        <ol>
29
                            <li>
30
                                <span class="label expected-amount-label">
31
                                    [% IF cashup_in_progress %]
32
                                        [% IF cashup_in_progress.amount < 0 %]
33
                                            Expected amount to add:
34
                                        [% ELSE %]
35
                                            Expected cashup amount:
36
                                        [% END %]
37
                                    [% ELSE %]
38
                                        Expected cashup amount:
39
                                    [% END %]
40
                                </span>
41
                                <span class="expected-amount">[% IF cashup_in_progress %][% cashup_in_progress.amount | $Price %][% END %]</span>
42
                            </li>
43
                            <li>
44
                                <label class="required actual-amount-label" for="cashup_amount">
45
                                    [% IF cashup_in_progress %]
46
                                        [% IF cashup_in_progress.amount < 0 %]
47
                                            Actual amount added to register:
48
                                        [% ELSE %]
49
                                            Actual cashup amount counted:
50
                                        [% END %]
51
                                    [% ELSE %]
52
                                        Actual cashup amount counted:
53
                                    [% END %]
54
                                </label>
55
                                <input type="text" inputmode="decimal" pattern="^-?\d+(\.\d{2})?$" id="cashup_amount" name="amount" class="cashup-amount-input" required="required" />
56
                                <span class="required">Required</span>
57
                            </li>
58
                            <li class="reconciliation-display" style="display: none;">
59
                                <span class="label">Reconciliation:</span>
60
                                <span class="reconciliation-text"></span>
61
                            </li>
62
                            <li class="reconciliation-note-field" style="display: none;">
63
                                <label class="reconciliation-note-label" for="cashup_reconciliation_note"> Note[% IF reconciliation_note_required %](required)[% ELSE %](optional)[% END %]: </label>
64
                                [% IF reconciliation_note_avs %]
65
                                    <select id="cashup_reconciliation_note" class="reconciliation-note-input" name="reconciliation_note">
66
                                        <option value="">-- Select a reason --</option>
67
                                        [% FOREACH av IN reconciliation_note_avs %]
68
                                            <option value="[% av.authorised_value | html %]">[% av.lib | html %]</option>
69
                                        [% END %]
70
                                    </select>
71
                                    [% IF reconciliation_note_required %]
72
                                        <span class="required">Required</span>
73
                                    [% END %]
74
                                [% ELSE %]
75
                                    <textarea
76
                                        id="cashup_reconciliation_note"
77
                                        class="reconciliation-note-input"
78
                                        name="reconciliation_note"
79
                                        rows="3"
80
                                        cols="40"
81
                                        maxlength="1000"
82
                                        placeholder="Enter a note explaining the surplus or deficit..."
83
                                    ></textarea>
84
                                    [% IF reconciliation_note_required %]
85
                                        <span class="required">Required</span>
86
                                    [% END %]
87
                                    <div class="hint">Maximum 1000 characters</div>
88
                                [% END %]
89
                            </li>
90
                        </ol>
91
                    </fieldset>
92
                </div>
93
                <div class="modal-footer">
94
                    <input type="hidden" name="registerid" class="register-id-field" value="[% IF cashup_in_progress %][% register.id | html %][% END %]" />
95
                    <input type="hidden" name="op" value="cud-cashup" />
96
                    [% IF redirect_to %]
97
                        <input type="hidden" name="redirect_to" value="[% redirect_to | html %]" />
98
                    [% END %]
99
                    <button type="submit" class="btn btn-primary"> [% IF cashup_in_progress %]Complete cashup[% ELSE %]Confirm cashup[% END %] </button>
100
                    <button type="button" class="btn btn-default" data-bs-dismiss="modal">Cancel</button>
101
                </div>
102
            </div>
103
        </div>
104
    </form>
105
</div>
106
<!-- /#[% modal_id | html %] -->
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/modals/trigger_cashup.inc (+50 lines)
Line 0 Link Here
1
[% USE raw %]
2
<!-- Trigger cashup modal -->
3
<!-- Parameters:
4
     - modal_id: ID for the modal (required)
5
     - register_description: Register description to pre-populate (optional, for register.tt)
6
     - register_id: Register ID to pre-populate (optional, for register.tt)
7
     - form_action: Form action URL (optional, defaults to current page)
8
     - redirect_to: Where to redirect after completion (optional: 'register' or 'registers')
9
-->
10
<div class="modal" id="[% modal_id | html %]" tabindex="-1" role="dialog" aria-labelledby="[% modal_id | html %]Label">
11
    <form method="post" class="validated trigger-cashup-form" [% IF form_action %]action="[% form_action | html %]"[% END %]>
12
        [% INCLUDE 'csrf-token.inc' %]
13
        <div class="modal-dialog">
14
            <div class="modal-content">
15
                <div class="modal-header">
16
                    <h1 class="modal-title" id="[% modal_id | html %]Label">
17
                        Cashup for <em><span class="register-description">[% IF register_description %][% register_description | html %][% END %]</span></em>
18
                    </h1>
19
                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
20
                </div>
21
                <div class="modal-body">
22
                    <p><strong>Choose how to proceed with the cashup:</strong></p>
23
                    <p><strong>Start cashup</strong></p>
24
                    <ul class="start-cashup-instructions">
25
                        <!-- JavaScript will populate this based on positive/negative amount -->
26
                    </ul>
27
                    <p><strong>Quick cashup</strong></p>
28
                    <ul class="quick-cashup-instructions">
29
                        <!-- JavaScript will populate this based on positive/negative amount -->
30
                    </ul>
31
                    <p class="float-reminder-text">
32
                        <!-- JavaScript will populate this based on positive/negative amount -->
33
                    </p>
34
                </div>
35
                <div class="modal-footer">
36
                    <input type="hidden" name="registerid" class="register-id-field" value="[% IF register_id %][% register_id | html %][% END %]" />
37
                    <input type="hidden" name="op" value="cud-cashup_start" />
38
                    <input type="hidden" name="amount" value="" />
39
                    [% IF redirect_to %]
40
                        <input type="hidden" name="redirect_to" value="[% redirect_to | html %]" />
41
                    [% END %]
42
                    <button type="submit" class="btn btn-primary">Start cashup</button>
43
                    <button type="button" class="btn btn-success quick-cashup-btn">Quick cashup</button>
44
                    <button type="button" class="btn btn-default" data-bs-dismiss="modal">Cancel</button>
45
                </div>
46
            </div>
47
        </div>
48
    </form>
49
</div>
50
<!-- /#[% modal_id | html %] -->
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/pos/register.tt (-261 / +25 lines)
Lines 114-120 Link Here
114
                [% IF cashup_in_progress %]
114
                [% IF cashup_in_progress %]
115
                    <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>
115
                    <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>
116
                [% ELSE %]
116
                [% ELSE %]
117
                    <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>
117
                    <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 %]>
118
                        <i class="fa fa-money-bill-alt"></i> Record cashup
119
                    </button>
118
                [% END %]
120
                [% END %]
119
            </div>
121
            </div>
120
        [% END %]
122
        [% END %]
Lines 425-521 Link Here
425
    [% END %]
427
    [% END %]
426
[% END %]
428
[% END %]
427
429
428
<!-- Confirm cashup modal -->
429
<div class="modal" id="confirmCashupModal" tabindex="-1" role="dialog" aria-labelledby="confirmCashupLabel">
430
    <form id="cashup_form" method="post" enctype="multipart/form-data" class="validated">
431
        [% INCLUDE 'csrf-token.inc' %]
432
        <div class="modal-dialog">
433
            <div class="modal-content">
434
                <div class="modal-header">
435
                    <h1 class="modal-title" id="confirmCashupLabel">
436
                        [% IF cashup_in_progress %]
437
                            Complete cashup of <em>[% register.description | html %]</em>
438
                        [% ELSE %]
439
                            Confirm cashup of <em>[% register.description | html %]</em>
440
                        [% END %]
441
                    </h1>
442
                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
443
                </div>
444
                <div class="modal-body">
445
                    <fieldset class="rows">
446
                        <ol>
447
                            <li>
448
                                <span class="label">
449
                                    [% IF cashup_in_progress %]
450
                                        [% IF cashup_in_progress.amount < 0 %]
451
                                            Expected amount to add:
452
                                        [% ELSE %]
453
                                            Expected cashup amount:
454
                                        [% END %]
455
                                    [% ELSE %]
456
                                        Expected amount to remove:
457
                                    [% END %]
458
                                </span>
459
                                <span id="expected_amount" class="expected-amount">[% cashup_in_progress.amount | $Price %]</span>
460
                            </li>
461
                            <li>
462
                                <label class="required" for="amount">
463
                                    [% IF cashup_in_progress %]
464
                                        [% IF cashup_in_progress.amount < 0 %]
465
                                            Actual amount added to register:
466
                                        [% ELSE %]
467
                                            Actual cashup amount counted:
468
                                        [% END %]
469
                                    [% ELSE %]
470
                                        Actual amount removed from register:
471
                                    [% END %]
472
                                </label>
473
                                <input type="text" inputmode="decimal" pattern="^-?\d+(\.\d{2})?$" id="amount" name="amount" required="required" />
474
                                <span class="required">Required</span>
475
                            </li>
476
                            <li id="reconciliation_display" style="display: none;">
477
                                <span class="label">Reconciliation:</span>
478
                                <span id="reconciliation_text"></span>
479
                            </li>
480
                            <li id="reconciliation_note_field" style="display: none;">
481
                                <label for="reconciliation_note" id="reconciliation_note_label">Note:</label>
482
                                [% IF reconciliation_note_avs %]
483
                                    <select id="reconciliation_note" name="reconciliation_note">
484
                                        <option value="">-- Select a reason --</option>
485
                                        [% FOREACH av IN reconciliation_note_avs %]
486
                                            <option value="[% av.authorised_value | html %]">[% av.lib | html %]</option>
487
                                        [% END %]
488
                                    </select>
489
                                    [% IF reconciliation_note_required %]
490
                                        <span class="required">Required</span>
491
                                    [% END %]
492
                                [% ELSE %]
493
                                    <textarea id="reconciliation_note" name="reconciliation_note" rows="3" cols="40" maxlength="1000" placeholder="Enter a note explaining the surplus or deficit..."></textarea>
494
                                    [% IF reconciliation_note_required %]
495
                                        <span class="required">Required</span>
496
                                    [% END %]
497
                                    <div class="hint">Maximum 1000 characters</div>
498
                                [% END %]
499
                            </li>
500
                        </ol>
501
                    </fieldset>
502
                </div>
503
                <!-- /.modal-body -->
504
                <div class="modal-footer">
505
                    <input type="hidden" name="registerid" value="[% register.id | html %]" />
506
                    <input type="hidden" name="op" value="cud-cashup" />
507
                    <button type="submit" class="btn btn-primary" id="pos_cashup_confirm"> [% IF cashup_in_progress %]Complete cashup[% ELSE %]Confirm cashup[% END %] </button>
508
                    <button type="button" class="btn btn-default" data-bs-dismiss="modal">Cancel</button>
509
                </div>
510
                <!-- /.modal-footer -->
511
            </div>
512
            <!-- /.modal-content -->
513
        </div>
514
        <!-- /.modal-dialog -->
515
    </form>
516
</div>
517
<!-- /#confirmCashupModal -->
518
519
<!-- Issue refund modal -->
430
<!-- Issue refund modal -->
520
<div class="modal" id="issueRefundModal" tabindex="-1" role="dialog" aria-labelledby="issueRefundLabel">
431
<div class="modal" id="issueRefundModal" tabindex="-1" role="dialog" aria-labelledby="issueRefundLabel">
521
    <form id="refund_form" method="post" enctype="multipart/form-data" class="validated">
432
    <form id="refund_form" method="post" enctype="multipart/form-data" class="validated">
Lines 559-620 Link Here
559
</div>
470
</div>
560
<!-- /#issueRefundModal -->
471
<!-- /#issueRefundModal -->
561
472
562
<!-- Trigger cashup modal -->
563
<div class="modal" id="triggerCashupModal" tabindex="-1" role="dialog" aria-labelledby="triggerCashupLabel">
564
    <form method="post" class="validated">
565
        [% INCLUDE 'csrf-token.inc' %]
566
        <div class="modal-dialog">
567
            <div class="modal-content">
568
                <div class="modal-header">
569
                    <h1 class="modal-title" id="triggerCashupLabel"> Cashup for <em>[% register.description | html %]</em> </h1>
570
                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
571
                </div>
572
                <div class="modal-body">
573
                    <p><strong>Choose how to proceed with the cashup:</strong></p>
574
                    <p><strong>Start cashup</strong></p>
575
                    <ul id="start_cashup_instructions_individual">
576
                        <!-- JavaScript will populate this based on positive/negative amount -->
577
                    </ul>
578
                    <p><strong>Quick cashup</strong></p>
579
                    <ul id="quick_cashup_instructions_individual">
580
                        <!-- JavaScript will populate this based on positive/negative amount -->
581
                    </ul>
582
                    <p id="float_reminder_text_individual">
583
                        <!-- JavaScript will populate this based on positive/negative amount -->
584
                    </p>
585
                </div>
586
                <div class="modal-footer">
587
                    <input type="hidden" name="registerid" value="[% register.id | html %]" />
588
                    <input type="hidden" name="op" value="cud-cashup_start" />
589
                    <input type="hidden" name="amount" value="" />
590
                    <input type="hidden" id="register_bankable_amount" value="[% accountlines.total( payment_type => [ 'CASH', 'SIP00' ]) * -1 | html %]" />
591
                    <button type="submit" class="btn btn-primary">Start cashup</button>
592
                    <button type="button" class="btn btn-success" id="quick_cashup_btn_individual">Quick cashup</button>
593
                    <button type="button" class="btn btn-default" data-bs-dismiss="modal">Cancel</button>
594
                </div>
595
            </div>
596
        </div>
597
    </form>
598
</div>
599
<!-- /#triggerCashupModal -->
600
601
[% INCLUDE 'modals/cashup_summary.inc' %]
473
[% INCLUDE 'modals/cashup_summary.inc' %]
474
[%
475
    INCLUDE 'modals/trigger_cashup.inc'
476
    modal_id='triggerCashupModal'
477
    register_description=register.description
478
    register_id=register.id
479
%]
480
[%
481
    INCLUDE 'modals/confirm_cashup.inc'
482
    modal_id='confirmCashupModal'
483
    reconciliation_note_avs=reconciliation_note_avs
484
    reconciliation_note_required=reconciliation_note_required
485
    cashup_in_progress=cashup_in_progress
486
%]
602
487
603
[% MACRO jsinclude BLOCK %]
488
[% MACRO jsinclude BLOCK %]
604
    [% INCLUDE 'datatables.inc' %]
489
    [% INCLUDE 'datatables.inc' %]
605
    [% INCLUDE 'format_price.inc' %]
490
    [% INCLUDE 'format_price.inc' %]
606
    [% INCLUDE 'js-date-format.inc' %]
491
    [% INCLUDE 'js-date-format.inc' %]
607
    <script>
608
        // Map of authorized value codes to descriptions for reconciliation notes
609
        var reconciliation_note_avs = {
610
            [% IF reconciliation_note_avs %]
611
                [% FOREACH av IN reconciliation_note_avs %]
612
                    "[% av.authorised_value | html %]": "[% av.lib | html %]"[% UNLESS loop.last %],[% END %]
613
                [% END %]
614
            [% END %]
615
        };
616
    </script>
617
    [% Asset.js("js/cashup_modal.js") | $raw %]
492
    [% Asset.js("js/cashup_modal.js") | $raw %]
493
    [% Asset.js("js/modals/cashup_modals.js") | $raw %]
618
    [% Asset.js("js/modal_printer.js") | $raw %]
494
    [% Asset.js("js/modal_printer.js") | $raw %]
619
    [% INCLUDE 'calendar.inc' %]
495
    [% INCLUDE 'calendar.inc' %]
620
    <script>
496
    <script>
Lines 746-873 Link Here
746
            ]
622
            ]
747
        }, null, 1);
623
        }, null, 1);
748
624
749
        // Real-time reconciliation calculation for cashup modal
625
        // Initialize cashup modals
750
        $("#amount").on("input", function() {
626
        initTriggerCashupModal('#triggerCashupModal', {
751
            var actualAmount = parseFloat($(this).val()) || 0;
627
            bankableAmount: [% accountlines.total( payment_type => [ 'CASH', 'SIP00' ]) * -1 | html %],
752
            var expectedText = $("#expected_amount").text().replace(/[£$,]/g, '');
628
            registerFloat: [% register.starting_float | html %]
753
            var expectedAmount = parseFloat(expectedText) || 0;
754
            var difference = actualAmount - expectedAmount;
755
756
            if ($(this).val() && !isNaN(actualAmount)) {
757
                var reconciliationText = "";
758
                var reconciliationClass = "";
759
                var hasDiscrepancy = false;
760
761
                if (difference > 0) {
762
                    reconciliationText = "Surplus: " + difference.format_price();
763
                    reconciliationClass = "success";
764
                    hasDiscrepancy = true;
765
                } else if (difference < 0) {
766
                    reconciliationText = "Deficit: " + Math.abs(difference).format_price();
767
                    reconciliationClass = "warning";
768
                    hasDiscrepancy = true;
769
                } else {
770
                    reconciliationText = "Balanced - no surplus or deficit";
771
                    reconciliationClass = "success";
772
                    hasDiscrepancy = false;
773
                }
774
775
                $("#reconciliation_text").text(reconciliationText)
776
                    .removeClass("success warning")
777
                    .addClass(reconciliationClass);
778
                $("#reconciliation_display").show();
779
780
                // Show/hide note field based on whether there's a discrepancy
781
                if (hasDiscrepancy) {
782
                    $("#reconciliation_note_field").show();
783
                    // Update required attribute and label based on system preference
784
                    [% IF reconciliation_note_required %]
785
                        $("#reconciliation_note").attr('required', 'required');
786
                        $("#reconciliation_note_label").addClass("required");
787
                    [% ELSE %]
788
                        $("#reconciliation_note").removeAttr('required');
789
                        $("#reconciliation_note_label").removeClass("required");
790
                    [% END %]
791
                } else {
792
                    $("#reconciliation_note_field").hide();
793
                    $("#reconciliation_note").val(''); // Clear note when balanced
794
                    $("#reconciliation_note").removeAttr('required');
795
                    $("#reconciliation_note_label").removeClass("required");
796
                }
797
            } else {
798
                $("#reconciliation_display").hide();
799
                $("#reconciliation_note_field").hide();
800
            }
801
        });
802
803
        // Reset modal when opened
804
        $("#confirmCashupModal").on("shown.bs.modal", function() {
805
            // Start with empty actual amount field (user must enter amount)
806
            $("#amount").val('').focus();
807
            $("#reconciliation_display").hide();
808
            $("#reconciliation_note_field").hide();
809
            $("#reconciliation_note").val('');
810
        });
629
        });
811
630
812
        // Handle triggerCashupModal for individual register page
631
        initConfirmCashupModal('#confirmCashupModal', {
813
        $("#triggerCashupModal").on("shown.bs.modal", function(e){
632
            hasAuthorisedValues: [% reconciliation_note_avs ? 'true' : 'false' %],
814
           var bankableAmount = parseFloat($("#register_bankable_amount").val());
633
            noteRequired: [% reconciliation_note_required ? 'true' : 'false' %],
815
           var isNegative = bankableAmount < 0;
634
            isInProgress: [% cashup_in_progress ? 'true' : 'false' %]
816
           var formattedAmount = Math.abs(bankableAmount).format_price();
817
           var floatAmount = parseFloat([% register.starting_float | html %]).format_price();
818
819
           // Update Start cashup instructions
820
           if (isNegative) {
821
               $("#start_cashup_instructions_individual").html(
822
                   '<li>' + _("Count cash in the register") + '</li>' +
823
                   '<li>' + _("The register can continue operating during counting") + '</li>' +
824
                   '<li>' + _("Complete the cashup by adding cash to restore the float") + '</li>'
825
               );
826
           } else {
827
               $("#start_cashup_instructions_individual").html(
828
                   '<li>' + _("Remove cash from the register for counting") + '</li>' +
829
                   '<li>' + _("The register can continue operating during counting") + '</li>' +
830
                   '<li>' + _("Complete the cashup once counted") + '</li>'
831
               );
832
           }
833
834
           // Update Quick cashup instructions
835
           if (isNegative) {
836
               $("#quick_cashup_instructions_individual").html(
837
                   '<li>' + _("Top up the register with %s to restore the float").format(formattedAmount) + '</li>'
838
               );
839
           } else {
840
               $("#quick_cashup_instructions_individual").html(
841
                   '<li>' + _("Confirm you have removed %s cash from the register to bank immediately").format(formattedAmount) + '</li>'
842
               );
843
           }
844
845
           // Update float reminder
846
           if (isNegative) {
847
               $("#float_reminder_text_individual").html(
848
                   _("This will bring the register back to the expected float of <strong>%s</strong>").format(floatAmount)
849
               );
850
           } else {
851
               $("#float_reminder_text_individual").html(
852
                   _("Remember to leave the float amount of <strong>%s</strong> in the register.").format(floatAmount)
853
               );
854
           }
855
        });
856
857
        // Handle Quick cashup button click for individual register
858
        $("#quick_cashup_btn_individual").on("click", function(e){
859
           e.preventDefault();
860
           var form = $(this).closest('form');
861
           var bankableAmount = $("#register_bankable_amount").val();
862
863
           // Change operation to cud-cashup (quick cashup)
864
           form.find('input[name="op"]').val('cud-cashup');
865
866
           // Set the amount to the expected bankable amount
867
           form.find('input[name="amount"]').val(bankableAmount);
868
869
           // Submit the form
870
           form.submit();
871
        });
635
        });
872
    </script>
636
    </script>
873
[% END %]
637
[% END %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/pos/registers.tt (-245 / +20 lines)
Lines 257-302 Link Here
257
    [% END %]
257
    [% END %]
258
[% END %]
258
[% END %]
259
259
260
<!-- Trigger cashup modal for individual registers -->
261
<div class="modal" id="triggerCashupModalRegister" tabindex="-1" role="dialog" aria-labelledby="triggerCashupLabelRegister">
262
    <form method="post" class="validated">
263
        [% INCLUDE 'csrf-token.inc' %]
264
        <div class="modal-dialog">
265
            <div class="modal-content">
266
                <div class="modal-header">
267
                    <h1 class="modal-title" id="triggerCashupLabelRegister">
268
                        Cashup for <em><span id="register_desc"></span></em>
269
                    </h1>
270
                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
271
                </div>
272
                <div class="modal-body">
273
                    <p><strong>Choose how to proceed with the cashup:</strong></p>
274
                    <p><strong>Start cashup</strong></p>
275
                    <ul id="start_cashup_instructions">
276
                        <!-- JavaScript will populate this based on positive/negative amount -->
277
                    </ul>
278
                    <p><strong>Quick cashup</strong></p>
279
                    <ul id="quick_cashup_instructions">
280
                        <!-- JavaScript will populate this based on positive/negative amount -->
281
                    </ul>
282
                    <p id="float_reminder_text">
283
                        <!-- JavaScript will populate this based on positive/negative amount -->
284
                    </p>
285
                </div>
286
                <div class="modal-footer">
287
                    <input type="hidden" name="registerid" id="register_id_field" value="" />
288
                    <input type="hidden" name="op" value="cud-cashup_start" />
289
                    <input type="hidden" name="amount" value="" />
290
                    <button type="submit" class="btn btn-primary">Start cashup</button>
291
                    <button type="button" class="btn btn-success" id="quick_cashup_btn">Quick cashup</button>
292
                    <button type="button" class="btn btn-default" data-bs-dismiss="modal">Cancel</button>
293
                </div>
294
            </div>
295
        </div>
296
    </form>
297
</div>
298
<!-- /#triggerCashupModalRegister -->
299
300
<!-- Confirm cashup selected modal -->
260
<!-- Confirm cashup selected modal -->
301
<div class="modal" id="confirmCashupSelectedModal" tabindex="-1" role="dialog" aria-labelledby="confirmCashupSelectedLabel">
261
<div class="modal" id="confirmCashupSelectedModal" tabindex="-1" role="dialog" aria-labelledby="confirmCashupSelectedLabel">
302
    <form method="post" class="validated">
262
    <form method="post" class="validated">
Lines 359-416 Link Here
359
</div>
319
</div>
360
<!-- /#confirmCashupSelectedModal -->
320
<!-- /#confirmCashupSelectedModal -->
361
321
362
<!-- Confirm cashup modal -->
363
<div class="modal" id="confirmCashupModal" tabindex="-1" role="dialog" aria-labelledby="confirmCashupLabel">
364
    <form id="cashup_individual_form" method="post" enctype="multipart/form-data" class="validated">
365
        [% INCLUDE 'csrf-token.inc' %]
366
        <div class="modal-dialog">
367
            <div class="modal-content">
368
                <div class="modal-header">
369
                    <h1 class="modal-title" id="confirmCashupLabel">
370
                        Confirm cashup of <em><span id="registerc"></span></em>
371
                    </h1>
372
                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
373
                </div>
374
                <div class="modal-body">
375
                    <fieldset class="rows">
376
                        <ol>
377
                            <li>
378
                                <span class="label" id="expected_amount_label">Expected cashup amount:</span>
379
                                <span id="cashc" class="expected-amount"></span>
380
                            </li>
381
                            <li>
382
                                <label class="required" for="amount" id="actual_amount_label">Actual cashup amount counted:</label>
383
                                <input type="text" inputmode="decimal" pattern="^-?\d+(\.\d{2})?$" id="amount" name="amount" required="required" />
384
                                <span class="required">Required</span>
385
                            </li>
386
                            <li id="reconciliation_display" style="display: none;">
387
                                <span class="label">Reconciliation:</span>
388
                                <span id="reconciliation_text"></span>
389
                            </li>
390
                            <li id="reconciliation_note_field" style="display: none;">
391
                                <label for="reconciliation_note">Note (optional):</label>
392
                                <textarea id="reconciliation_note" name="reconciliation_note" rows="3" cols="40" maxlength="1000" placeholder="Enter a note explaining the surplus or deficit..."></textarea>
393
                                <div class="hint">Maximum 1000 characters</div>
394
                            </li>
395
                        </ol>
396
                    </fieldset>
397
                </div>
398
                <!-- /.modal-body -->
399
                <div class="modal-footer">
400
                    <input type="hidden" name="registerid" id="cashup_registerid" value="" />
401
                    <input type="hidden" name="op" value="cud-cashup" />
402
                    <button type="submit" class="btn btn-primary" id="cashup_confirm"> Complete cashup</button>
403
                    <button type="button" class="btn btn-default" data-bs-dismiss="modal">Cancel</button>
404
                </div>
405
                <!-- /.modal-footer -->
406
            </div>
407
            <!-- /.modal-content -->
408
        </div>
409
        <!-- /.modal-dialog -->
410
    </form>
411
</div>
412
<!-- /#confirmCashupModal -->
413
414
<!-- Confirm cashupall modal -->
322
<!-- Confirm cashupall modal -->
415
<div class="modal" id="confirmCashupAllModal" tabindex="-1" role="dialog" aria-labelledby="confirmCashupAllLabel">
323
<div class="modal" id="confirmCashupAllModal" tabindex="-1" role="dialog" aria-labelledby="confirmCashupAllLabel">
416
    <form id="cashup_all_form" method="post" enctype="multipart/form-data">
324
    <form id="cashup_all_form" method="post" enctype="multipart/form-data">
Lines 444-455 Link Here
444
<!-- /#confirmCashupAllModal -->
352
<!-- /#confirmCashupAllModal -->
445
353
446
[% INCLUDE 'modals/cashup_summary.inc' %]
354
[% INCLUDE 'modals/cashup_summary.inc' %]
355
[%
356
    INCLUDE 'modals/trigger_cashup.inc'
357
    modal_id='triggerCashupModalRegister'
358
    form_action='/cgi-bin/koha/pos/register.pl'
359
    redirect_to='registers'
360
%]
361
[%
362
    INCLUDE 'modals/confirm_cashup.inc'
363
    modal_id='confirmCashupModal'
364
    reconciliation_note_avs=reconciliation_note_avs
365
    reconciliation_note_required=reconciliation_note_required
366
    form_action='/cgi-bin/koha/pos/register.pl'
367
    redirect_to='registers'
368
%]
447
369
448
[% MACRO jsinclude BLOCK %]
370
[% MACRO jsinclude BLOCK %]
449
    [% INCLUDE 'datatables.inc' %]
371
    [% INCLUDE 'datatables.inc' %]
450
    [% INCLUDE 'format_price.inc' %]
372
    [% INCLUDE 'format_price.inc' %]
451
    [% INCLUDE 'js-date-format.inc' %]
373
    [% INCLUDE 'js-date-format.inc' %]
452
    [% Asset.js("js/cashup_modal.js") | $raw %]
374
    [% Asset.js("js/cashup_modal.js") | $raw %]
375
    [% Asset.js("js/modals/cashup_modals.js") | $raw %]
453
    [% Asset.js("js/modal_printer.js") | $raw %]
376
    [% Asset.js("js/modal_printer.js") | $raw %]
454
    <script>
377
    <script>
455
        $(document).ready(function () {
378
        $(document).ready(function () {
Lines 465-624 Link Here
465
                paginationType: "full",
388
                paginationType: "full",
466
            });
389
            });
467
390
468
            // Real-time reconciliation calculation for cashup modal
391
            // Initialize cashup modals
469
            $("#amount").on("input", function() {
392
            initTriggerCashupModal('#triggerCashupModalRegister');
470
                var actualAmount = parseFloat($(this).val()) || 0;
471
                var expectedText = $("#expected_amount").text().replace(/[£$,]/g, '');
472
                var expectedAmount = parseFloat(expectedText) || 0;
473
                var difference = actualAmount - expectedAmount;
474
475
                if ($(this).val() && !isNaN(actualAmount)) {
476
                    var reconciliationText = "";
477
                    var reconciliationClass = "";
478
                    var hasDiscrepancy = false;
479
480
                    if (difference > 0) {
481
                        reconciliationText = "Surplus: " + difference.format_price();
482
                        reconciliationClass = "success";
483
                        hasDiscrepancy = true;
484
                    } else if (difference < 0) {
485
                        reconciliationText = "Deficit: " + Math.abs(difference).format_price();
486
                        reconciliationClass = "warning";
487
                        hasDiscrepancy = true;
488
                    } else {
489
                        reconciliationText = "Balanced - no surplus or deficit";
490
                        reconciliationClass = "success";
491
                        hasDiscrepancy = false;
492
                    }
493
494
                    $("#reconciliation_text").text(reconciliationText)
495
                        .removeClass("success warning")
496
                        .addClass(reconciliationClass);
497
                    $("#reconciliation_display").show();
498
499
                    // Show/hide note field based on whether there's a discrepancy
500
                    if (hasDiscrepancy) {
501
                        $("#reconciliation_note_field").show();
502
                    } else {
503
                        $("#reconciliation_note_field").hide();
504
                        $("#reconciliation_note").val(''); // Clear note when balanced
505
                    }
506
                } else {
507
                    $("#reconciliation_display").hide();
508
                    $("#reconciliation_note_field").hide();
509
                }
510
            });
511
512
            // Reset modal when opened
513
            $("#confirmCashupModal").on("shown.bs.modal", function(e){
514
               var button = $(e.relatedTarget);
515
               var register = button.data('register');
516
               $("#registerc").text(register);
517
               var expected = button.data('expected');
518
               $("#cashc").text(expected);
519
520
               // Parse expected amount to check if negative
521
               var expectedAmount = expected.replace(/[^0-9.-]/g, '');
522
               var isNegative = parseFloat(expectedAmount) < 0;
523
524
               // Update labels based on sign
525
               if (isNegative) {
526
                   $("#expected_amount_label").text(_("Expected amount to add:"));
527
                   $("#actual_amount_label").text(_("Actual amount added to register:"));
528
               } else {
529
                   $("#expected_amount_label").text(_("Expected cashup amount:"));
530
                   $("#actual_amount_label").text(_("Actual cashup amount counted:"));
531
               }
532
533
               var rfloat = button.data('float');
534
               $('#floatc').text(rfloat);
535
               var rid = button.data('registerid');
536
               $('#cashup_registerid').val(rid);
537
               $("#amount").val('').focus();
538
               $("#reconciliation_display").hide();
539
               $("#reconciliation_note_field").hide();
540
               $("#reconciliation_note").val('');
541
            });
542
543
            // Handle the new trigger cashup modal for individual registers
544
            $("#triggerCashupModalRegister").on("shown.bs.modal", function(e){
545
               var button = $(e.relatedTarget);
546
               var register = button.data('register');
547
               $("#register_desc").text(register);
548
               var bankable = button.data('bankable');
549
               var rfloat = button.data('float');
550
               var rid = button.data('registerid');
551
               $('#register_id_field').val(rid);
552
553
               // Guard against undefined/null bankable value
554
               if (!bankable) {
555
                   console.error("Bankable amount is undefined");
556
                   return;
557
               }
558
559
               // Parse bankable amount (remove currency formatting, keep minus sign)
560
               var bankableAmount = String(bankable).replace(/[^0-9.-]/g, '');
561
               var numericAmount = parseFloat(bankableAmount);
562
               var isNegative = numericAmount < 0;
563
564
               // Format the absolute amount for display
565
               var absAmountFormatted = Math.abs(numericAmount).format_price();
566
567
               // Update Start cashup instructions
568
               if (isNegative) {
569
                   $("#start_cashup_instructions").html(
570
                       '<li>' + _("Count cash in the register") + '</li>' +
571
                       '<li>' + _("The register can continue operating during counting") + '</li>' +
572
                       '<li>' + _("Complete the cashup by adding cash to restore the float") + '</li>'
573
                   );
574
               } else {
575
                   $("#start_cashup_instructions").html(
576
                       '<li>' + _("Remove cash from the register for counting") + '</li>' +
577
                       '<li>' + _("The register can continue operating during counting") + '</li>' +
578
                       '<li>' + _("Complete the cashup once counted") + '</li>'
579
                   );
580
               }
581
582
               // Update Quick cashup instructions
583
               if (isNegative) {
584
                   $("#quick_cashup_instructions").html(
585
                       '<li>' + _("Top up the register with %s to restore the float").format(absAmountFormatted) + '</li>'
586
                   );
587
               } else {
588
                   $("#quick_cashup_instructions").html(
589
                       '<li>' + _("Confirm you have removed %s cash from the register to bank immediately").format(absAmountFormatted) + '</li>'
590
                   );
591
               }
592
593
               // Update float reminder
594
               if (isNegative) {
595
                   $("#float_reminder_text").html(
596
                       _("This will bring the register back to the expected float of <strong>%s</strong>").format(rfloat)
597
                   );
598
               } else {
599
                   $("#float_reminder_text").html(
600
                       _("Remember to leave the float amount of <strong>%s</strong> in the register.").format(rfloat)
601
                   );
602
               }
603
604
               // Store bankable amount for quick cashup (with sign)
605
               $('#triggerCashupModalRegister').data('bankable-amount', bankableAmount);
606
            });
607
608
            // Handle Quick cashup button click
609
            $("#quick_cashup_btn").on("click", function(e){
610
               e.preventDefault();
611
               var form = $(this).closest('form');
612
               var bankableAmount = $('#triggerCashupModalRegister').data('bankable-amount');
613
614
               // Change operation to cud-cashup (quick cashup)
615
               form.find('input[name="op"]').val('cud-cashup');
616
617
               // Set the amount to the expected bankable amount
618
               form.find('input[name="amount"]').val(bankableAmount);
619
393
620
               // Submit the form
394
            initConfirmCashupModal('#confirmCashupModal', {
621
               form.submit();
395
                hasAuthorisedValues: [% reconciliation_note_avs ? 'true' : 'false' %],
396
                noteRequired: [% reconciliation_note_required ? 'true' : 'false' %]
622
            });
397
            });
623
398
624
            // Select all registers functionality
399
            // Select all registers functionality
(-)a/koha-tmpl/intranet-tmpl/prog/js/modals/cashup_modals.js (+273 lines)
Line 0 Link Here
1
/**
2
 * Cashup Modal JavaScript Module
3
 * Shared initialization functions for cashup modals across POS register pages
4
 */
5
6
/**
7
 * Initialize trigger cashup modal behavior
8
 * @param {string} modalSelector - jQuery selector for the modal (e.g., '#triggerCashupModal')
9
 * @param {object} options - Configuration options
10
 * @param {number} options.registerFloat - Starting float amount (for register.tt)
11
 * @param {number} options.bankableAmount - Bankable amount (for register.tt)
12
 */
13
function initTriggerCashupModal(modalSelector, options) {
14
    options = options || {};
15
16
    $(modalSelector).on("shown.bs.modal", function (e) {
17
        var button = $(e.relatedTarget);
18
        var modal = $(this);
19
20
        // Get data from button (for registers.tt) or options (for register.tt)
21
        var register = button.data("register");
22
        var bankable = button.data("bankable");
23
        var rfloat = button.data("float");
24
        var rid = button.data("registerid");
25
26
        // For register.tt, use options if provided
27
        if (options.bankableAmount !== undefined) {
28
            bankable = options.bankableAmount;
29
        }
30
        if (options.registerFloat !== undefined) {
31
            rfloat = options.registerFloat;
32
        }
33
34
        // Populate register description if available
35
        if (register) {
36
            modal.find(".register-description").text(register);
37
        }
38
39
        // Set register ID if available
40
        if (rid) {
41
            modal.find(".register-id-field").val(rid);
42
        }
43
44
        // Guard against undefined/null bankable value
45
        if (bankable === undefined || bankable === null) {
46
            console.error("Bankable amount is undefined");
47
            return;
48
        }
49
50
        // Parse bankable amount (remove currency formatting, keep minus sign)
51
        var bankableAmount = String(bankable).replace(/[^0-9.-]/g, "");
52
        var numericAmount = parseFloat(bankableAmount);
53
        var isNegative = numericAmount < 0;
54
55
        // Format amounts for display
56
        var absAmountFormatted = Math.abs(numericAmount).format_price();
57
        var floatFormatted = rfloat;
58
        if (typeof rfloat === "number") {
59
            floatFormatted = rfloat.format_price();
60
        }
61
62
        // Update Start cashup instructions
63
        var startInstructions;
64
        if (isNegative) {
65
            startInstructions =
66
                "<li>" +
67
                _("Count cash in the register") +
68
                "</li>" +
69
                "<li>" +
70
                _("The register can continue operating during counting") +
71
                "</li>" +
72
                "<li>" +
73
                _("Complete the cashup by adding cash to restore the float") +
74
                "</li>";
75
        } else {
76
            startInstructions =
77
                "<li>" +
78
                _("Remove cash from the register for counting") +
79
                "</li>" +
80
                "<li>" +
81
                _("The register can continue operating during counting") +
82
                "</li>" +
83
                "<li>" +
84
                _("Complete the cashup once counted") +
85
                "</li>";
86
        }
87
        modal.find(".start-cashup-instructions").html(startInstructions);
88
89
        // Update Quick cashup instructions
90
        var quickInstructions;
91
        if (isNegative) {
92
            quickInstructions =
93
                "<li>" +
94
                _("Top up the register with %s to restore the float").format(
95
                    absAmountFormatted
96
                ) +
97
                "</li>";
98
        } else {
99
            quickInstructions =
100
                "<li>" +
101
                _(
102
                    "Confirm you have removed %s cash from the register to bank immediately"
103
                ).format(absAmountFormatted) +
104
                "</li>";
105
        }
106
        modal.find(".quick-cashup-instructions").html(quickInstructions);
107
108
        // Update float reminder
109
        var floatReminder;
110
        if (isNegative) {
111
            floatReminder = _(
112
                "This will bring the register back to the expected float of <strong>%s</strong>"
113
            ).format(floatFormatted);
114
        } else {
115
            floatReminder = _(
116
                "Remember to leave the float amount of <strong>%s</strong> in the register."
117
            ).format(floatFormatted);
118
        }
119
        modal.find(".float-reminder-text").html(floatReminder);
120
121
        // Store bankable amount for quick cashup (with sign)
122
        modal.data("bankable-amount", bankableAmount);
123
    });
124
125
    // Handle Quick cashup button click
126
    $(modalSelector + " .quick-cashup-btn").on("click", function (e) {
127
        e.preventDefault();
128
        var form = $(this).closest("form");
129
        var modal = $(this).closest(".modal");
130
        var bankableAmount = modal.data("bankable-amount");
131
132
        // Change operation to cud-cashup (quick cashup)
133
        form.find('input[name="op"]').val("cud-cashup");
134
135
        // Set the amount to the expected bankable amount
136
        form.find('input[name="amount"]').val(bankableAmount);
137
138
        // Submit the form
139
        form.submit();
140
    });
141
}
142
143
/**
144
 * Initialize confirm cashup modal behavior with reconciliation calculation
145
 * @param {string} modalSelector - jQuery selector for the modal (e.g., '#confirmCashupModal')
146
 * @param {object} options - Configuration options
147
 * @param {boolean} options.noteRequired - Whether reconciliation note is required when there's a discrepancy
148
 * @param {boolean} options.hasAuthorisedValues - Whether authorized values are configured for notes
149
 * @param {boolean} options.isInProgress - Whether this is completing an in-progress cashup (for register.tt)
150
 */
151
function initConfirmCashupModal(modalSelector, options) {
152
    options = options || {};
153
    var noteRequired = options.noteRequired || false;
154
    var hasAuthorisedValues = options.hasAuthorisedValues || false;
155
    var isInProgress = options.isInProgress || false;
156
157
    // Real-time reconciliation calculation
158
    $(modalSelector + " .cashup-amount-input").on("input", function () {
159
        var modal = $(this).closest(".modal");
160
        var actualAmount = parseFloat($(this).val()) || 0;
161
        var expectedText = modal
162
            .find(".expected-amount")
163
            .text()
164
            .replace(/[£$,]/g, "");
165
        var expectedAmount = parseFloat(expectedText) || 0;
166
        var difference = actualAmount - expectedAmount;
167
168
        if ($(this).val() && !isNaN(actualAmount)) {
169
            var reconciliationText = "";
170
            var reconciliationClass = "";
171
            var hasDiscrepancy = false;
172
173
            if (difference > 0) {
174
                reconciliationText = "Surplus: " + difference.format_price();
175
                reconciliationClass = "success";
176
                hasDiscrepancy = true;
177
            } else if (difference < 0) {
178
                reconciliationText =
179
                    "Deficit: " + Math.abs(difference).format_price();
180
                reconciliationClass = "warning";
181
                hasDiscrepancy = true;
182
            } else {
183
                reconciliationText = "Balanced - no surplus or deficit";
184
                reconciliationClass = "success";
185
                hasDiscrepancy = false;
186
            }
187
188
            modal
189
                .find(".reconciliation-text")
190
                .text(reconciliationText)
191
                .removeClass("success warning")
192
                .addClass(reconciliationClass);
193
            modal.find(".reconciliation-display").show();
194
195
            // Show/hide note field based on whether there's a discrepancy
196
            if (hasDiscrepancy) {
197
                modal.find(".reconciliation-note-field").show();
198
199
                // Update required attribute and label based on system preference
200
                if (noteRequired) {
201
                    modal
202
                        .find(".reconciliation-note-input")
203
                        .attr("required", "required");
204
                    modal
205
                        .find(".reconciliation-note-label")
206
                        .addClass("required");
207
                } else {
208
                    modal
209
                        .find(".reconciliation-note-input")
210
                        .removeAttr("required");
211
                    modal
212
                        .find(".reconciliation-note-label")
213
                        .removeClass("required");
214
                }
215
            } else {
216
                modal.find(".reconciliation-note-field").hide();
217
                modal.find(".reconciliation-note-input").val(""); // Clear note when balanced
218
                modal.find(".reconciliation-note-input").removeAttr("required");
219
                modal
220
                    .find(".reconciliation-note-label")
221
                    .removeClass("required");
222
            }
223
        } else {
224
            modal.find(".reconciliation-display").hide();
225
            modal.find(".reconciliation-note-field").hide();
226
        }
227
    });
228
229
    // Reset/populate modal when opened
230
    $(modalSelector).on("shown.bs.modal", function (e) {
231
        var button = $(e.relatedTarget);
232
        var modal = $(this);
233
234
        // For registers.tt: populate from button data
235
        if (button.length && button.data("register")) {
236
            var register = button.data("register");
237
            modal.find(".register-name").text(register);
238
239
            var expected = button.data("expected");
240
            modal.find(".expected-amount").text(expected);
241
242
            var rid = button.data("registerid");
243
            modal.find(".register-id-field").val(rid);
244
245
            // Parse expected amount to check if negative
246
            var expectedAmount = expected.replace(/[^0-9.-]/g, "");
247
            var isNegative = parseFloat(expectedAmount) < 0;
248
249
            // Update labels based on sign
250
            if (isNegative) {
251
                modal
252
                    .find(".expected-amount-label")
253
                    .text(_("Expected amount to add:"));
254
                modal
255
                    .find(".actual-amount-label")
256
                    .text(_("Actual amount added to register:"));
257
            } else {
258
                modal
259
                    .find(".expected-amount-label")
260
                    .text(_("Expected cashup amount:"));
261
                modal
262
                    .find(".actual-amount-label")
263
                    .text(_("Actual cashup amount counted:"));
264
            }
265
        }
266
267
        // Reset fields
268
        modal.find(".cashup-amount-input").val("").focus();
269
        modal.find(".reconciliation-display").hide();
270
        modal.find(".reconciliation-note-field").hide();
271
        modal.find(".reconciliation-note-input").val("");
272
    });
273
}
(-)a/pos/register.pl (-3 / +15 lines)
Lines 138-144 if ( !$registers->count ) { Link Here
138
            } else {
138
            } else {
139
139
140
                # Redirect to prevent duplicate submissions (POST/REDIRECT/GET pattern)
140
                # Redirect to prevent duplicate submissions (POST/REDIRECT/GET pattern)
141
                print $input->redirect( "/cgi-bin/koha/pos/register.pl?registerid=" . $registerid );
141
                my $redirect_to = $input->param('redirect_to') || 'register';
142
143
                if ( $redirect_to eq 'registers' ) {
144
                    print $input->redirect("/cgi-bin/koha/pos/registers.pl?cashup_start_success=1");
145
                } else {
146
                    print $input->redirect( "/cgi-bin/koha/pos/register.pl?registerid=" . $registerid );
147
                }
142
                exit;
148
                exit;
143
            }
149
            }
144
        } else {
150
        } else {
Lines 149-155 if ( !$registers->count ) { Link Here
149
            my $amount              = $input->param('amount');
155
            my $amount              = $input->param('amount');
150
            my $reconciliation_note = $input->param('reconciliation_note');
156
            my $reconciliation_note = $input->param('reconciliation_note');
151
157
152
            if ( defined $amount && $amount =~ /^\d+(?:\.\d{1,2})?$/ ) {
158
            if ( defined $amount && $amount =~ /^-?\d+(?:\.\d{1,2})?$/ ) {
153
159
154
                # Sanitize and limit note length
160
                # Sanitize and limit note length
155
                if ( defined $reconciliation_note ) {
161
                if ( defined $reconciliation_note ) {
Lines 190-196 if ( !$registers->count ) { Link Here
190
                } else {
196
                } else {
191
197
192
                    # Redirect to prevent duplicate submissions (POST/REDIRECT/GET pattern)
198
                    # Redirect to prevent duplicate submissions (POST/REDIRECT/GET pattern)
193
                    print $input->redirect( "/cgi-bin/koha/pos/register.pl?registerid=" . $registerid );
199
                    my $redirect_to = $input->param('redirect_to') || 'register';
200
201
                    if ( $redirect_to eq 'registers' ) {
202
                        print $input->redirect("/cgi-bin/koha/pos/registers.pl?cashup_complete_success=1");
203
                    } else {
204
                        print $input->redirect( "/cgi-bin/koha/pos/register.pl?registerid=" . $registerid );
205
                    }
194
                    exit;
206
                    exit;
195
                }
207
                }
196
            } else {
208
            } else {
(-)a/pos/registers.pl (-2 / +16 lines)
Lines 42-47 my $logged_in_user = Koha::Patrons->find($loggedinuser) or die "Not logged in"; Link Here
42
my $library = Koha::Libraries->find( C4::Context->userenv->{'branch'} );
42
my $library = Koha::Libraries->find( C4::Context->userenv->{'branch'} );
43
$template->param( library => $library );
43
$template->param( library => $library );
44
44
45
# Get authorized values for reconciliation notes if configured
46
my $note_av_category = C4::Context->preference('CashupReconciliationNoteAuthorisedValue');
47
my $reconciliation_note_avs;
48
if ($note_av_category) {
49
    require Koha::AuthorisedValues;
50
    $reconciliation_note_avs = Koha::AuthorisedValues->search(
51
        { category => $note_av_category },
52
        { order_by => { '-asc' => 'lib' } }
53
    );
54
}
55
45
my $registers = Koha::Cash::Registers->search(
56
my $registers = Koha::Cash::Registers->search(
46
    { branch   => $library->id, archived => 0 },
57
    { branch   => $library->id, archived => 0 },
47
    { order_by => { '-asc' => 'name' } }
58
    { order_by => { '-asc' => 'name' } }
Lines 50-56 my $registers = Koha::Cash::Registers->search( Link Here
50
if ( !$registers->count ) {
61
if ( !$registers->count ) {
51
    $template->param( error_registers => 1 );
62
    $template->param( error_registers => 1 );
52
} else {
63
} else {
53
    $template->param( registers => $registers );
64
    $template->param(
65
        registers                    => $registers,
66
        reconciliation_note_avs      => $reconciliation_note_avs,
67
        reconciliation_note_required => C4::Context->preference('CashupReconciliationNoteRequired'),
68
    );
54
}
69
}
55
70
56
# Handle success/error messages from redirects
71
# Handle success/error messages from redirects
57
- 

Return to bug 40445