When a single-register cashup is completed from the Cash summary page
(registers.pl), pos/register.pl redirected back to registers.pl without
the '#cashup-<id>' hash, so the hash-watcher in registers.tt never
triggered the cashup summary modal. Completing the same cashup from the
Transaction history page (register.pl) worked because that branch
already appended the hash.
Append '#cashup-<id>' to the registers.pl redirect URL too, so both the
'Record cashup' (quick) and 'Complete cashup' (two-phase) actions on the
Cash summary page open the summary modal on return.
Test plan:
1. Have at least one register with cash transactions.
2. From Cash summary (registers.pl), click 'Record cashup' on a register
with no cashup in progress; complete it.
=> On return, cashup summary modal opens automatically.
3. Start a two-phase cashup; from Cash summary click 'Complete cashup'
and confirm.
=> On return, cashup summary modal opens automatically.
4. From Transaction history (register.pl), complete a cashup as before.
=> Modal still opens (regression check).
Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Jackie Usher <jackie.usher@westsussex.gov.uk>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
This patch implements an optional two-phase cashup workflow for point
of sale operations, along with improved error handling and validation.
Two-Phase Cashup Workflow:
- Staff can initiate cashup (CASHUP_START) and complete later
- Prevents new transactions on registers with cashups in progress
- Tracks cashup sessions with start/end timestamps
- Supports both quick cashup (immediate) and staged cashup workflows
- Updates batch cashup workflow operations across multiple registers
Error Handling & Validation:
- Centralized exception handling for cashup operations
- Clear validation messages for missing parameters
- Prevents cashup when no cash transactions exist
- Improved handling of zero/negative amounts
- Informative error messages for user guidance
Negative Amount Support:
- Supports negative cashup amounts for cashup deficits
- Automatic detection when actual cash is less than expected amount
- Creates appropriate CASHUP_DEFICIT records
- UI handles both positive and negative reconciliation amounts
- Updated calculations and display logic throughout
Modal Refactoring:
- Eliminates code duplication between register.tt and registers.tt
- Centralized cashup modal functionality in cashup_modals.js
- Shared confirm_cashup.inc and trigger_cashup.inc templates
- Consistent user experience across all cashup workflows
- Improved maintainability and code organization
Backend changes (Koha::Cash::Register):
- start_cashup(): Creates CASHUP_START action to begin session
- cashup_in_progress(): Detects active cashup sessions
- add_cashup(): Enhanced with two-phase completion support
- outstanding_accountlines(): Respects cashup session boundaries
Frontend changes:
- Interactive modals for starting and completing cashups
- Real-time calculation displays
- Support for reconciliation with actual amounts
- Batch operations UI for multiple registers
- Responsive error messaging and validation feedback
Test plan:
1. Apply patches and restart services
2. Run prove t/db_dependent/Koha/Cash/Register.t
3. Run prove t/db_dependent/Koha/Cash/Register/Cashup.t
4. Test single register workflows:
- Start cashup, add transactions, verify blocked
- Complete cashup with reconciliation amounts
- Test with positive, negative, and zero amounts
5. Test multi-register workflows:
- Select multiple registers on registers page
- Perform batch cashup operations
- Verify each register processes correctly
6. Test error conditions:
- Attempt cashup with no transactions
- Attempt cashup with invalid amounts
- Verify clear error messages displayed
Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Jackie Usher <jackie.usher@westsussex.gov.uk>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
The previous patch hides the 'Account credit' option in the refund modal
when the accountline has no borrowernumber, but that is a UI convenience
only. A crafted POST (or a stale page) could still submit
refund_type=AC against an anonymous accountline, which would create a
REFUND credit with no patron to attach it to and leave the register
unbalanced because the payout branch is skipped.
Add a server-side guard in pos/register.pl that refuses the operation
and surfaces a new error_refund_anonymous_ac flag, rendered in
pos/register.tt in the same style as the other refund errors.
Test plan:
1. Apply the patch
2. Log in as a user with cash_management/anonymous_refund permission
3. Record an anonymous sale against a register
4. Using the browser devtools (or curl), POST to pos/register.pl with
op=cud-refund, refund_type=AC and the accountline id of the sale
5. => Confirm the request is rejected and the
"Account credit refunds are not available for anonymous transactions"
message is shown
6. => Confirm no REFUND accountline was created and the register
balance is unchanged
7. Repeat the POST with refund_type=CASH (or any non-AC type) and
confirm the refund + payout are created normally
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
When processing a refund through the POS register with refund type 'AC'
(Account Credit), the system was incorrectly creating both a REFUND credit
and a PAYOUT debit with payment_type = 'AC'.
When 'AC' is selected, the refund should remain as a credit on the patron's
account without creating a payout. This behavior was already correct in the
member account refund workflow (members/boraccount.pl) but was missing
from the POS refund workflow.
This patch:
1. Adds the same check to pos/register.pl that exists in members/boraccount.pl
to skip payout creation when refund_type is 'AC', making both workflows
consistent.
2. Fixes the JavaScript in register.tt to properly hide the 'AC' option for
anonymous transactions. The original check only tested for empty string,
but borrowernumber can be undefined/null, which needs to be handled.
Test plan:
Part 1: Test patron transaction refund with Account Credit (AC)
1. Find or create a test patron
2. On the patron's Accounting tab, manually add a charge (e.g., Manual fee)
3. Pay the fee off from the patron's Accounting tab
4. Navigate to Point of Sale > Transaction History
5. Find the payment transaction you just created
6. Click "Issue refund" on the payment line item
7. Verify that "Account credit" appears as an option in the "Transaction type"
dropdown
8. Select "Account credit" as the transaction type
9. Enter the refund amount and click "Confirm"
10. Navigate back to the patron's Accounting tab
11. Verify that a REFUND credit appears on their account
12. Verify that NO PAYOUT debit was created
13. Verify the credit remains on the patron's account (positive balance)
Part 2: Test patron transaction refund with Cash payout
16. Using the same patron, create another manual fee and payment
17. Navigate to Point of Sale > Transaction History
18. Click "Issue refund" on the new payment
19. Select "Cash" (or another payment type) as the transaction type
20. Enter the refund amount and click "Confirm"
21. Navigate back to the patron's Accounting tab
22. Verify both a REFUND credit and a PAYOUT debit appear
23. Verify the PAYOUT has the correct payment_type (e.g., CASH)
Part 3: Test anonymous transaction (no Account Credit option)
24. Navigate to Point of Sale
25. Add an item to sell and complete the payment
27. Navigate to Point of Sale > Transaction History
28. Find the anonymous payment you just created
29. Click "Issue refund" on the payment line item
30. Verify that "Account credit" does NOT appear in the "Transaction type"
dropdown (only payment types like Cash, Check, etc. should appear)
31. This confirms anonymous transactions cannot have account credits since
there's no patron account to credit
Expected results:
- Patron transactions show "Account credit" option in refund dropdown
- Anonymous transactions do NOT show "Account credit" option
- AC refunds create only REFUND credit (no PAYOUT debit)
- Non-AC refunds create both REFUND credit and PAYOUT debit
- Run: prove t/db_dependent/Koha/Cash/Register/Cashup.t
Signed-off-by: Laura Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
This patch fixes two issues with cash refunds on the register page:
1. Payouts were not appearing in the transactions table after being
created because accountlines were fetched before the refund
operation completed.
2. Account credit (AC) refunds were incorrectly creating payout
transactions when no cash should leave the register.
Changes:
- Only create payout for non-AC refund types
- Use explicit registerid from page context for payout
- Add POST/REDIRECT/GET pattern after refund to refresh page data
- Add client-side validation for cash register requirement on patron
account page
Test plan:
1. Navigate to POS register page with transactions
2. Issue a cash refund on a transaction
3. Verify the payout appears in the transactions table
4. Issue an account credit (AC) refund
5. Verify no payout is created (only the refund credit)
6. On patron account page, attempt cash refund without register
7. Verify validation message appears
Signed-off-by: Jackie Usher <jackie.usher@westsussex.gov.uk>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
The upstream implementation of POST/REDIRECT/GET pattern for cashup operations
introduced a regression where the cashup modal would no longer auto-trigger
after successful cashup submission. The original approach passed cashup_id as
a query parameter in the redirect, but the template variable was not being
populated from the URL parameter, causing the JavaScript to receive an empty
value.
This follow-up implements a more modern and reliable solution using URL hash
fragments instead of query parameters:
1. Changed redirect URLs to use hash fragments (e.g., #cashup-123)
2. Updated JavaScript to detect and parse hash fragments on page load
3. Auto-triggers the cashup modal when hash is present
4. Cleans up the URL using history.replaceState() after modal opens
Benefits:
- Maintains proper POST/REDIRECT/GET pattern
- Hash fragments don't trigger server requests or require server-side handling
- More reliable than template variable approach
- Follows modern web development patterns
- Works consistently across both register.pl and registers.pl pages
Test plan:
1. Navigate to POS register page (pos/register.pl)
2. Perform a cashup operation
3. Verify the cashup summary modal automatically appears after redirect
4. Verify the URL is clean (no hash fragment visible)
5. Repeat for registers summary page (pos/registers.pl)
6. Perform individual register cashup
7. Verify modal auto-triggers correctly
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
This patch automatically triggers the cashup display modal after a
cashup is performed from the individual Register details display page.
Test plan:
1. Enable the point of sale system preference
2. Ensure you have permission for cash_management > cashup
3. Create at least one cash register for your current branch
4. Navigate to More > Point of Sale > Cash registers
5. Click on a register name to access the individual register page
6. Perform some transactions to create outstanding amounts
7. Click 'Record cashup' button on the register details page
8. Confirm the cashup in the modal dialog
9. Verify that after page reload, the cashup summary modal
automatically opens showing the details of the cashup just performed
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Sally Healey <sally.healey@cheshiresharedservices.gov.uk>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
The cashup functionality on both /pos/register.pl and /pos/registers.pl
pages was using GET requests via anchor tags, which could cause
duplicate cashup records when users refreshed the page after performing
a cashup.
This patch implements proper POST/REDIRECT/GET pattern with CSRF
protection:
1. Convert anchor tag buttons to proper HTML forms with submit buttons
2. Add CSRF token protection to all cashup operations
3. Change operation from 'cashup' to 'cud-cashup' for CSRF compliance
4. Implement redirects after successful cashup operations to prevent
resubmission
5. Update JavaScript to populate form fields instead of href attributes
This ensures cashup operations follow HTTP semantics properly (POST for
state changes) and prevents duplicate submissions while maintaining the
existing user experience.
Test plan:
1. Apply patch and restart services
2. Go to Point of Sale > Cash registers
3. Click "Record cashup" on any register - should open modal
4. Click "Confirm" - should perform cashup and reload page
5. Refresh the page - should NOT create duplicate cashup record
6. Go to Point of Sale > Register for individual register
7. Click "Record cashup" - should open modal
8. Click "Confirm" - should perform cashup and reload page
9. Refresh the page - should NOT create duplicate cashup record
10. On registers page, try "Cashup all" button - should work correctly
11. Verify all existing functionality (modals, summaries) still works
12. Check browser developer tools - all requests should be POST for
cashup operations
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
This commit is generated using:
% perl misc/devel/tidy.pl
*within* ktd, to get the same version of perltidy than what will be used
by our CI (currently v20230309).
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
To recreate:
1. Enable the "EnablePointofSale" syspref.
2. Set the "UseCashRegisters" syspref to use.
3. Create 3 new registers (Register A, B and C) with a float amount of $25.
4. Under POS, configure 3 new debit types (i.e. Copies, Earbuds, Friends Donation)
-- You can leave the default amounts blank, or add an amount if desired.
-- Set "Can be manually invoiced?" and "Can be sold?"
5. Under point of sale make a $5 sale of copies under Register A.
6. Under point of sale make a $10 sale of earbuds under Register B.
7. Under point of sale make a $100 sale of Friends Donation under Register C.
8. At circ/set-library.pl set your register to Register A.
9. Go back to POS and view the "Transaction history for Register A" - it shows the correct amount of $5 sold in copies.
10. Go back to circ/set-library.pl and change your register to Register B.
11. Go back to POS and view the "Transaction history for Register B" - it shows the incorrect transaction history. It is showing the transaction history for Register A.
12. Go back to circ/set-library.pl and change your register to Register C.
13. Go back to POS and view the "Transaction history for Register C" - it shows the incorrect transaction history. It is showing the transaction history for Register A.
14. Apply patch, repeat. The correct transactions should now show.
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
The idea rely on the KohaDates TT plugin for the date formatting. We
should not have any output_pref calls in pl or pm (there are some
exceptions, for ILSDI for instance).
Also flatpickr will deal with the places where dates are inputed. We
will pass the raw SQL value (what we call 'iso' in Koha::DateUtils), and
the controller will receive the same value, no need to additional
conversion.
Note that DBIC has the capability to auto-deflate DateTime objects,
which makes things way easier. We can either pass the value we receive
from the controller, or pass a DT object to our methods.
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
This patch changes the fix from adding a day to setting the time to
23:59:59.
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Fridolin Somers <fridolin.somers@biblibre.com>
Test Plan:
1) Sell an item
2) Cash up to make the sale historical
3) Search "Older transactions" with both dates being the current date,
click "Display" to search
4) Note you get no results
5) Apply the patch
6) You should now see your transaction
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Fridolin Somers <fridolin.somers@biblibre.com>
On bug 17591 we discovered that there was something weird going on with
the way we export and use subroutines/modules.
This patch tries to standardize our EXPORT to use EXPORT_OK only.
That way we will need to explicitely define the subroutine we want to
use from a module.
This patch is a squashed version of:
Bug 17600: After export.pl
Bug 17600: After perlimport
Bug 17600: Manual changes
Bug 17600: Other manual changes after second perlimports run
Bug 17600: Fix tests
And a lot of other manual changes.
export.pl is a dirty script that can be found on bug 17600.
"perlimport" is:
git clone https://github.com/oalders/App-perlimports.git
cd App-perlimports/
cpanm --installdeps .
export PERL5LIB="$PERL5LIB:/kohadevbox/koha/App-perlimports/lib"
find . \( -name "*.pl" -o -name "*.pm" \) -exec perl App-perlimports/script/perlimports --inplace-edit --no-preserve-unused --filename {} \;
The ideas of this patch are to:
* use EXPORT_OK instead of EXPORT
* perltidy the EXPORT_OK list
* remove '&' before the subroutine names
* remove some uneeded use statements
* explicitely import the subroutines we need within the controllers or
modules
Note that the private subroutines (starting with _) should not be
exported (and not used from outside of the module except from tests).
EXPORT vs EXPORT_OK (from
https://www.thegeekstuff.com/2010/06/perl-exporter-examples/)
"""
Export allows to export the functions and variables of modules to user’s namespace using the standard import method. This way, we don’t need to create the objects for the modules to access it’s members.
@EXPORT and @EXPORT_OK are the two main variables used during export operation.
@EXPORT contains list of symbols (subroutines and variables) of the module to be exported into the caller namespace.
@EXPORT_OK does export of symbols on demand basis.
"""
If this patch caused a conflict with a patch you wrote prior to its
push:
* Make sure you are not reintroducing a "use" statement that has been
removed
* "$subroutine" is not exported by the C4::$MODULE module
means that you need to add the subroutine to the @EXPORT_OK list
* Bareword "$subroutine" not allowed while "strict subs"
means that you didn't imported the subroutine from the module:
- use $MODULE qw( $subroutine list );
You can also use the fully qualified namespace: C4::$MODULE::$subroutine
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
This patch centralises the payment/transaction type select options
handling so the SIP types are properly in all cases.
Test plan
1) Check that SIP payment types are properly hidden on the following
modals.
1a) Refund modal on the borrower account page (The 'Account credit'
option should appear here)
1b) Payout modal on borrower account page
1c) Payment via paycollect
1d) Payment via point of sale
1e) Refund via point of sale, register details page (The 'Account
credit' option should only appear for debts associated to a patron and
not for payments accepted via point of sale)
2) Signoff
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
The description of REFUND type accountline credits introduced with the "issue refund" feature is not translatable.
To test:
- Make sure a language with a complete translation is installed
- Switch to the language
- Go to any user account
- Add a manual invoice
- Pay it off fully or partially
- Click on "issue refund"
- Confirm the refund
- Check the description of the line in the patron account is not translated.
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de>
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
The cash register summary page for cash management is available for users
with the 'anonymous_refund' or 'cashup' permission and the actions available
are appropriately displayed.
However, the actions are not yet correctly tested for at the server and
so a user may force submit to accomplish the action.
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de>
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
It would appear one has to format the dates in the controller rather
than in the template for input fields.. I wasn't aware of this caveat
until now.
Test plan
1/ Prior to applying patch attempt to look at previous transactions
ensuring you enter a 'to' date.
2/ Note a catastrophic failure
3/ Apply patch
4/ Look at the same date range and note you no longer get a failure but
are instead faced with the requested transactions.
5/ Signoff
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
This patch adds the new `anonymous_refund` permission to the
`cash_management` flag and binds it to the visability of the register
details page.
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
This patch makes the parameter naming more consistent and also fixes the
tests (there was a missmatch on the parameter naming, and tests count
was incorrect).
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
This patch adds a new page which can be accessed via the menu to the
left of the point of sale page. This new page displays a list of
transactions since the last 'cashup' action, a summary of transaction
amounts and a button to allow the recording of a new 'cashup action.
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>