|
Lines 1-5
Link Here
|
| 1 |
[% USE raw %] |
1 |
[% USE raw %] |
| 2 |
[% USE AdditionalContents %] |
2 |
[% USE AdditionalContents %] |
|
|
3 |
[% USE AuthorisedValues %] |
| 3 |
[% USE Asset %] |
4 |
[% USE Asset %] |
| 4 |
[% USE Branches %] |
5 |
[% USE Branches %] |
| 5 |
[% USE Koha %] |
6 |
[% USE Koha %] |
|
Lines 7-12
Link Here
|
| 7 |
[% USE TablesSettings %] |
8 |
[% USE TablesSettings %] |
| 8 |
[% USE Registers %] |
9 |
[% USE Registers %] |
| 9 |
[% PROCESS 'i18n.inc' %] |
10 |
[% PROCESS 'i18n.inc' %] |
|
|
11 |
[% PROCESS 'html_helpers.inc' %] |
| 10 |
[% SET footerjs = 1 %] |
12 |
[% SET footerjs = 1 %] |
| 11 |
[% INCLUDE 'doc-head-open.inc' %] |
13 |
[% INCLUDE 'doc-head-open.inc' %] |
| 12 |
[% SET registers = Registers.all( filters => { current_branch => 1 } ) %] |
14 |
[% SET registers = Registers.all( filters => { current_branch => 1 } ) %] |
|
Lines 33-38
Link Here
|
| 33 |
[% END #/ WRAPPER sub-header.inc %] |
35 |
[% END #/ WRAPPER sub-header.inc %] |
| 34 |
|
36 |
|
| 35 |
[% WRAPPER 'main-container.inc' aside='pos-menu' %] |
37 |
[% WRAPPER 'main-container.inc' aside='pos-menu' %] |
|
|
38 |
|
| 39 |
[% IF ( CAN_user_cash_management_anonymous_refund ) %] |
| 40 |
<div id="toolbar" class="btn-toolbar"> |
| 41 |
<button id="process_refund" type="button" class="btn btn-default" data-bs-toggle="modal" data-bs-target="#refundSearchModal"><i class="fa-solid fa-undo"></i> Process refund</button> |
| 42 |
</div> |
| 43 |
[% END %] |
| 44 |
|
| 36 |
<h1>Point of sale</h1> |
45 |
<h1>Point of sale</h1> |
| 37 |
|
46 |
|
| 38 |
[% IF ( registers.size == 0 ) %] |
47 |
[% IF ( registers.size == 0 ) %] |
|
Lines 240-245
Link Here
|
| 240 |
</div> |
249 |
</div> |
| 241 |
</div> |
250 |
</div> |
| 242 |
|
251 |
|
|
|
252 |
<!-- Refund search modal --> |
| 253 |
<div class="modal" id="refundSearchModal" tabindex="-1" role="dialog" aria-labelledby="refundSearchLabel"> |
| 254 |
<div class="modal-dialog modal-lg"> |
| 255 |
<div class="modal-content"> |
| 256 |
<div class="modal-header"> |
| 257 |
<h1 class="modal-title" id="refundSearchLabel">Search transaction for refund</h1> |
| 258 |
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> |
| 259 |
</div> |
| 260 |
<div class="modal-body"> |
| 261 |
<!-- Messages block for errors/warnings --> |
| 262 |
<div id="refund_search_messages"></div> |
| 263 |
|
| 264 |
<!-- Search form --> |
| 265 |
<div id="refund_search_form"> |
| 266 |
<div class="input-group"> |
| 267 |
<input type="text" id="receipt_id" name="receipt_id" class="form-control" placeholder="Enter receipt number or transaction ID" /> |
| 268 |
<button type="button" class="btn btn-primary" id="search_transaction"><i class="fa fa-search"></i> Search</button> |
| 269 |
</div> |
| 270 |
</div> |
| 271 |
|
| 272 |
<!-- Search results --> |
| 273 |
<div id="refund_search_results" style="display: none; margin-top: 20px;"> |
| 274 |
<div class="page-section"> |
| 275 |
<div id="transaction_info"></div> |
| 276 |
<div id="transaction_debits" style="margin-top: 15px;"> |
| 277 |
<h5>Items in this transaction:</h5> |
| 278 |
<table id="search_results_table" class="table table-bordered table-striped"> |
| 279 |
<thead> |
| 280 |
<tr> |
| 281 |
<th>Description</th> |
| 282 |
<th>Amount</th> |
| 283 |
<th>Status</th> |
| 284 |
<th class="NoSort">Action</th> |
| 285 |
</tr> |
| 286 |
</thead> |
| 287 |
<tbody></tbody> |
| 288 |
</table> |
| 289 |
</div> |
| 290 |
</div> |
| 291 |
</div> |
| 292 |
</div> |
| 293 |
<div class="modal-footer"> |
| 294 |
<button type="button" class="btn btn-default" data-bs-dismiss="modal">Close</button> |
| 295 |
</div> |
| 296 |
</div> |
| 297 |
</div> |
| 298 |
</div> |
| 299 |
<!-- /#refundSearchModal --> |
| 300 |
|
| 301 |
[% INCLUDE 'modals/refund_modal.inc' refund_form_action = '/cgi-bin/koha/pos/register.pl' registers = registers %] |
| 302 |
|
| 243 |
[% IF payment_id && Koha.Preference('FinePaymentAutoPopup') %] |
303 |
[% IF payment_id && Koha.Preference('FinePaymentAutoPopup') %] |
| 244 |
<!-- Automatic Print Receipt --> |
304 |
<!-- Automatic Print Receipt --> |
| 245 |
<a id="printReceipt" style="display: none" href="#"></a> |
305 |
<a id="printReceipt" style="display: none" href="#"></a> |
|
Lines 247-254
Link Here
|
| 247 |
|
307 |
|
| 248 |
[% MACRO jsinclude BLOCK %] |
308 |
[% MACRO jsinclude BLOCK %] |
| 249 |
[% INCLUDE 'format_price.inc' %] |
309 |
[% INCLUDE 'format_price.inc' %] |
|
|
310 |
[% INCLUDE 'js-date-format.inc' %] |
| 250 |
[% INCLUDE 'datatables.inc' %] |
311 |
[% INCLUDE 'datatables.inc' %] |
|
|
312 |
[% Asset.js("js/refund_modal.js") | $raw %] |
| 251 |
<script> |
313 |
<script> |
|
|
314 |
// Create lookup objects for descriptions |
| 315 |
var debitTypeDescriptions = { |
| 316 |
[% FOREACH invoice_type IN invoice_types %] |
| 317 |
'[% invoice_type.code | html %]': '[% invoice_type.description | html %]', |
| 318 |
[% END %] |
| 319 |
}; |
| 320 |
|
| 321 |
// Create lookup for payment type descriptions |
| 322 |
var paymentTypeDescriptions = { |
| 323 |
[% FOREACH pt IN AuthorisedValues.GetAuthValueDropbox('PAYMENT_TYPE') %] |
| 324 |
'[% pt.authorised_value | html %]': '[% pt.lib | html %]', |
| 325 |
[% END %] |
| 326 |
}; |
| 327 |
|
| 252 |
function moneyFormat(textObj) { |
328 |
function moneyFormat(textObj) { |
| 253 |
var newValue = textObj.value; |
329 |
var newValue = textObj.value; |
| 254 |
var decAmount = ""; |
330 |
var decAmount = ""; |
|
Lines 527-532
Link Here
|
| 527 |
}); |
603 |
}); |
| 528 |
$("#printReceipt").click(); |
604 |
$("#printReceipt").click(); |
| 529 |
[% END %] |
605 |
[% END %] |
|
|
606 |
|
| 607 |
// Refund search functionality |
| 608 |
var current_registerid = $('select[name="registerid"]').val(); |
| 609 |
|
| 610 |
// Reset search modal when opened |
| 611 |
$('#refundSearchModal').on('show.bs.modal', function() { |
| 612 |
$('#receipt_id').val(''); |
| 613 |
$('#refund_search_results').hide(); |
| 614 |
$('#refund_search_messages').empty(); |
| 615 |
$('#search_results_table tbody').empty(); |
| 616 |
$('#transaction_info').empty(); |
| 617 |
}); |
| 618 |
|
| 619 |
// Auto-focus search input when modal is fully shown |
| 620 |
$('#refundSearchModal').on('shown.bs.modal', function() { |
| 621 |
$('#receipt_id').focus(); |
| 622 |
}); |
| 623 |
|
| 624 |
// Search transaction button click |
| 625 |
$('#search_transaction').on('click', function() { |
| 626 |
var receipt_id = $('#receipt_id').val().trim(); |
| 627 |
|
| 628 |
if (!receipt_id) { |
| 629 |
showSearchMessage('Please enter a receipt number or transaction ID.', 'warning'); |
| 630 |
return; |
| 631 |
} |
| 632 |
|
| 633 |
$('#refund_search_messages').empty(); |
| 634 |
$('#refund_search_results').hide(); |
| 635 |
|
| 636 |
// Build query - search by both credit_number and accountlines_id |
| 637 |
var query = JSON.stringify({ |
| 638 |
"-or": [ |
| 639 |
{ "credit_number": receipt_id }, |
| 640 |
{ "accountlines_id": receipt_id } |
| 641 |
] |
| 642 |
}); |
| 643 |
|
| 644 |
// Call API to search for credit |
| 645 |
$.ajax({ |
| 646 |
url: '/api/v1/account/credits', |
| 647 |
method: 'GET', |
| 648 |
data: { |
| 649 |
q: query, |
| 650 |
}, |
| 651 |
headers: { |
| 652 |
'x-koha-embed': 'debits' |
| 653 |
}, |
| 654 |
success: function(data) { |
| 655 |
if (data && data.length > 0) { |
| 656 |
displayTransactionResults(data[0]); |
| 657 |
} else { |
| 658 |
showSearchMessage('No transaction found with that receipt number or ID.', 'warning'); |
| 659 |
} |
| 660 |
}, |
| 661 |
error: function(xhr) { |
| 662 |
var errorMsg = 'Error searching for transaction.'; |
| 663 |
if (xhr.responseJSON && xhr.responseJSON.error) { |
| 664 |
errorMsg = xhr.responseJSON.error; |
| 665 |
} |
| 666 |
showSearchMessage(errorMsg, 'danger'); |
| 667 |
} |
| 668 |
}); |
| 669 |
}); |
| 670 |
|
| 671 |
// Allow Enter key to trigger search |
| 672 |
$('#receipt_id').on('keypress', function(e) { |
| 673 |
if (e.which === 13) { |
| 674 |
e.preventDefault(); |
| 675 |
$('#search_transaction').click(); |
| 676 |
} |
| 677 |
}); |
| 678 |
|
| 679 |
function showSearchMessage(message, type) { |
| 680 |
var alertClass = 'alert-' + type; |
| 681 |
var iconClass = type === 'danger' ? 'fa-exclamation-circle' : 'fa-exclamation-triangle'; |
| 682 |
var html = '<div class="alert ' + alertClass + '" role="alert">'; |
| 683 |
html += '<i class="fa ' + iconClass + '"></i> '; |
| 684 |
html += message; |
| 685 |
html += '</div>'; |
| 686 |
$('#refund_search_messages').html(html); |
| 687 |
} |
| 688 |
|
| 689 |
function displayTransactionResults(credit) { |
| 690 |
// Get payment type description for display |
| 691 |
var paymentTypeDesc = paymentTypeDescriptions[credit.payment_type] || credit.payment_type || ''; |
| 692 |
var info_html = '<h4>Transaction details</h4>'; |
| 693 |
info_html += '<table class="table table-sm">'; |
| 694 |
info_html += '<tr><th>Receipt #:</th><td>' + (credit.credit_number || credit.account_line_id) + '</td></tr>'; |
| 695 |
info_html += '<tr><th>Date:</th><td>' + $date(credit.date) + '</td></tr>'; |
| 696 |
info_html += '<tr><th>Total:</th><td>' + Math.abs(parseFloat(credit.amount)).format_price() + '</td></tr>'; |
| 697 |
info_html += '<tr><th>Payment type:</th><td>' + paymentTypeDesc + '</td></tr>'; |
| 698 |
info_html += '</table>'; |
| 699 |
|
| 700 |
// Show warning if from different register |
| 701 |
if (credit.cash_register_id && credit.cash_register_id != current_registerid) { |
| 702 |
showSearchMessage('Note: This transaction was made at a different cash register.', 'warning'); |
| 703 |
} |
| 704 |
|
| 705 |
$('#transaction_info').html(info_html); |
| 706 |
|
| 707 |
// Display debits |
| 708 |
var tbody = $('#search_results_table tbody'); |
| 709 |
tbody.empty(); |
| 710 |
|
| 711 |
if (credit.debits && credit.debits.length > 0) { |
| 712 |
credit.debits.forEach(function(debit) { |
| 713 |
// Don't show PAYOUT debits or already REFUNDED debits |
| 714 |
if (debit.type === 'PAYOUT' || debit.status === 'REFUNDED') { |
| 715 |
return; |
| 716 |
} |
| 717 |
|
| 718 |
// Get description from lookup, fallback to debit.description, then type code |
| 719 |
var debitDesc = debitTypeDescriptions[debit.type] || debit.description || debit.type || 'Item'; |
| 720 |
|
| 721 |
var row = '<tr>'; |
| 722 |
row += '<td>' + debitDesc + '</td>'; |
| 723 |
row += '<td>' + parseFloat(debit.amount).format_price() + '</td>'; |
| 724 |
row += '<td>' + (debit.status || 'Paid') + '</td>'; |
| 725 |
row += '<td>'; |
| 726 |
|
| 727 |
if (debit.status !== 'REFUNDED') { |
| 728 |
|
| 729 |
row += '<button type="button" class="btn btn-sm btn-default refund_debit_btn" '; |
| 730 |
row += 'data-bs-toggle="modal" '; |
| 731 |
row += 'data-bs-target="#issueRefundModal" '; |
| 732 |
row += 'data-accountline="' + debit.account_line_id + '" '; |
| 733 |
row += 'data-amount="' + debit.amount + '" '; |
| 734 |
row += 'data-amountoutstanding="' + (debit.amount_outstanding || 0) + '" '; |
| 735 |
row += 'data-item="' + debitDesc + '" '; |
| 736 |
row += 'data-register-id="' + (credit.cash_register_id || '') + '" '; |
| 737 |
row += 'data-payment-type="' + paymentTypeDesc + '" '; |
| 738 |
row += 'data-member="">'; |
| 739 |
row += '<i class="fa-solid fa-money-bill-1"></i> Issue refund</button>'; |
| 740 |
} else { |
| 741 |
row += '<span class="badge badge-secondary">Already refunded</span>'; |
| 742 |
} |
| 743 |
|
| 744 |
row += '</td>'; |
| 745 |
row += '</tr>'; |
| 746 |
|
| 747 |
tbody.append(row); |
| 748 |
}); |
| 749 |
|
| 750 |
$('#refund_search_results').show(); |
| 751 |
} else { |
| 752 |
showSearchMessage('No refundable items found in this transaction.', 'info'); |
| 753 |
} |
| 754 |
} |
| 755 |
|
| 756 |
// Handle "Issue refund" button click in search results |
| 757 |
// Close search modal when refund button is clicked |
| 758 |
$(document).on('click', '.refund_debit_btn', function() { |
| 759 |
$('#refundSearchModal').modal('hide'); |
| 760 |
}); |
| 761 |
|
| 762 |
// Initialize refund modal with current register context |
| 763 |
initRefundModal({ |
| 764 |
currentRegisterId: current_registerid |
| 765 |
}); |
| 530 |
}); |
766 |
}); |
| 531 |
</script> |
767 |
</script> |
| 532 |
[% END %] |
768 |
[% END %] |