PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.4.3
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.4.3
2.4.3 2.4.4 2.4.2 2.4.0 2.4.1 2.3.8 2.3.7 2.3.6 2.3.5 2.3.4 2.3.3 2.3.2 2.3.1 2.2.0 2.1.21 2.1.20 2.1.19 2.1.18 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 All 60 releases
easy-invoice / assets / js / settings.js

settings.js in Easy Invoice – Invoice Generator, PDF Quotes & Payments 2.4.3, at assets/js/settings.js

691 lines 29.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 jQuery(function($) {
2 // Add CSS for drag and drop functionality
3 $('<style>')
4 .prop('type', 'text/css')
5 .html(`
6 .gateway-handle {
7 cursor: move;
8 transition: color 0.2s ease;
9 }
10 .gateway-handle:hover {
11 color: #6b7280 !important;
12 }
13 .gateway-item.dragging {
14 opacity: 0.8;
15 transform: rotate(2deg);
16 box-shadow: 0 10px 25px rgba(0,0,0,0.15);
17 z-index: 1000;
18 }
19 .gateway-item-placeholder {
20 background: #f3f4f6;
21 border: 2px dashed #d1d5db;
22 border-radius: 0.5rem;
23 height: 6rem;
24 margin: 1.25rem 0;
25 }
26 body.dragging-gateway .gateway-item:not(.dragging) {
27 transition: transform 0.2s ease;
28 }
29 body.dragging-gateway .gateway-item:not(.dragging):hover {
30 transform: translateY(-2px);
31 }
32 `)
33 .appendTo('head');
34
35
36 // Initialize Select2 for multiselect fields if Select2 is available
37 function initializeSelect2() {
38 if ($.fn.select2) {
39 $('select[multiple="multiple"]').each(function() {
40 if (!$(this).hasClass('select2-hidden-accessible')) {
41
42 var $field = $(this);
43 var fieldLabel = $.trim($('label[for="' + $field.attr('id') + '"]').first().text())
44 || $field.attr('placeholder')
45 || 'Select options';
46
47 $field.select2({
48 width: '100%',
49 placeholder: $field.attr('placeholder') || 'Select options',
50 closeOnSelect: false,
51 allowClear: true
52 });
53
54 // Name the search box Select2 builds for itself. Select2 4.0.13
55 // renders `.select2-search__field` with no label and no aria-label,
56 // so a screen reader announces it as an unnamed text field. On a
57 // multiselect that box lives inside the selection control and is
58 // present from the moment Select2 initialises — not only while the
59 // dropdown is open — so it is labelled here, and again on open to
60 // cover the search field Select2 renders into the dropdown itself.
61 var labelSearchFields = function ($root) {
62 $root.find('.select2-search__field')
63 .filter(function () { return !this.getAttribute('aria-label'); })
64 .attr('aria-label', fieldLabel);
65 };
66
67 labelSearchFields($field.next('.select2-container'));
68 $field.on('select2:open', function () {
69 labelSearchFields($('.select2-container--open'));
70 });
71 }
72 });
73 }
74 }
75
76 // Initialize Select2 on page load
77 initializeSelect2();
78
79 // Reinitialize Select2 after settings save
80 $(document).on('settingsSaved', function() {
81 setTimeout(function() {
82 initializeSelect2();
83 }, 500);
84 });
85
86 // Make gateways sortable
87 if ($("#sortable-gateways").length) {
88 $("#sortable-gateways").sortable({
89 handle: ".gateway-handle",
90 axis: "y",
91 placeholder: "gateway-item-placeholder bg-gray-100 border-2 border-dashed border-gray-300 rounded-lg h-24",
92 tolerance: "pointer",
93 opacity: 0.8,
94 update: function(event, ui) {
95 // Update the order inputs when items are reordered
96 $('#sortable-gateways .gateway-item').each(function(index) {
97 $(this).find('input.gateway-order-input').val($(this).data('gateway-id'));
98 });
99
100 // Show a subtle indication that order was updated
101 if (typeof EasyInvoiceToast !== 'undefined') {
102 EasyInvoiceToast.info('Payment method order updated. Save settings to apply changes.', { duration: 3000 });
103 }
104 },
105 start: function(event, ui) {
106 ui.item.addClass('dragging');
107 $('body').addClass('dragging-gateway');
108 },
109 stop: function(event, ui) {
110 ui.item.removeClass('dragging');
111 $('body').removeClass('dragging-gateway');
112 }
113 }).disableSelection();
114 }
115
116 // Toggle gateway settings visibility based on checkbox
117 $('#settings-form').on('change', '.gateway-enable-checkbox', function() {
118 var settingsDiv = $(this).closest('.gateway-item').find('.gateway-settings');
119 if ($(this).is(':checked')) {
120 settingsDiv.removeClass('hidden');
121 } else {
122 settingsDiv.addClass('hidden');
123 }
124 });
125
126 // Offline payment methods the merchant adds (ACH, wire, …). Adding or
127 // removing one registers a gateway, so the page reloads to show it.
128 var offlineNonce = (typeof easyInvoiceSettings !== 'undefined' && easyInvoiceSettings.nonce) ? easyInvoiceSettings.nonce : '';
129 var offlineAjax = (typeof easyInvoiceSettings !== 'undefined' && easyInvoiceSettings.ajaxurl) ? easyInvoiceSettings.ajaxurl : (window.ajaxurl || '');
130 var toast = function (type, message) {
131 if (typeof EasyInvoiceToast !== 'undefined' && EasyInvoiceToast[type]) { EasyInvoiceToast[type](message); } else if (message) { window.alert(message); }
132 };
133 $('#ei-offline-add-btn').on('click', function () {
134 var $btn = $(this), title = $.trim($('#ei-offline-add-title').val() || '');
135 if (!title) { toast('error', 'Give the payment method a name.'); $('#ei-offline-add-title').focus(); return; }
136 $btn.prop('disabled', true);
137 $.post(offlineAjax, { action: 'easy_invoice_add_offline_method', nonce: offlineNonce, title: title })
138 .done(function (r) {
139 if (r && r.success) {
140 toast('success', r.data && r.data.message ? r.data.message : 'Payment method added.');
141 var url = new URL(window.location.href); url.searchParams.set('section', 'payment'); url.hash = '';
142 setTimeout(function () { window.location.href = url.toString() + '#gateway-enable-' + (r.data && r.data.id ? r.data.id : ''); }, 600);
143 } else {
144 toast('error', r && r.data && r.data.message ? r.data.message : 'The payment method could not be added.');
145 $btn.prop('disabled', false);
146 }
147 })
148 .fail(function () { toast('error', 'The payment method could not be added.'); $btn.prop('disabled', false); });
149 });
150 $('#ei-offline-add-title').on('keydown', function (e) { if (e.key === 'Enter') { e.preventDefault(); $('#ei-offline-add-btn').trigger('click'); } });
151 $('#settings-form').on('click', '.ei-offline-remove', function () {
152 var $btn = $(this), id = $btn.data('method-id'), name = $btn.data('method-title') || id;
153 if (!window.confirm('Remove "' + name + '" from the payment methods you offer? Payments already recorded keep it.')) { return; }
154 $btn.prop('disabled', true);
155 $.post(offlineAjax, { action: 'easy_invoice_remove_offline_method', nonce: offlineNonce, id: id })
156 .done(function (r) {
157 if (r && r.success) { toast('success', r.data && r.data.message ? r.data.message : 'Payment method removed.'); setTimeout(function () { window.location.reload(); }, 600); }
158 else { toast('error', r && r.data && r.data.message ? r.data.message : 'The payment method could not be removed.'); $btn.prop('disabled', false); }
159 })
160 .fail(function () { toast('error', 'The payment method could not be removed.'); $btn.prop('disabled', false); });
161 });
162
163 // Initialize gateway settings visibility on page load
164 $('.gateway-enable-checkbox').each(function() {
165 var settingsDiv = $(this).closest('.gateway-item').find('.gateway-settings');
166 if ($(this).is(':checked')) {
167 settingsDiv.removeClass('hidden');
168 } else {
169 settingsDiv.addClass('hidden');
170 }
171 });
172
173 // Generic function to handle all conditional fields
174 function updateConditionalFields() {
175 $('.conditional-field').each(function() {
176 const field = $(this);
177 let shouldShow = true;
178
179 // Check all dependencies
180 field.find('.depends-on-*').each(function() {
181 const className = $(this).attr('class');
182 const matches = className.match(/depends-on-([^-]+)-([^-]+)/);
183 if (matches) {
184 const dependsKey = matches[1];
185 const dependsValue = matches[2];
186 const dependsField = $('select[name="settings[' + dependsKey + ']"], input[name="settings[' + dependsKey + ']"]');
187
188 if (dependsField.length && dependsField.val() !== dependsValue) {
189 shouldShow = false;
190 }
191 }
192 });
193
194 if (shouldShow) {
195 field.removeClass('hidden');
196 } else {
197 field.addClass('hidden');
198 }
199 });
200 }
201
202 // Initialize conditional field visibility on page load
203 updateConditionalFields();
204
205 // Update fields when any dependency field changes
206 $(document).on('change', 'select[name*="settings["], input[name*="settings["]', function() {
207 updateConditionalFields();
208 });
209
210 // Handle logo upload
211 $('.upload-logo-button').on('click', function(e) {
212 e.preventDefault();
213
214
215 const button = $(this);
216 const fieldId = button.attr('id').replace('upload_image_button_', '');
217 const previewId = '#' + fieldId + '-preview';
218 const inputId = '#' + fieldId;
219
220
221
222 if (typeof wp === 'undefined' || typeof wp.media === 'undefined') {
223 console.error('WordPress media library not loaded');
224 if (typeof EasyInvoiceToast !== 'undefined') {
225 EasyInvoiceToast.show('error', 'Media library not available. Please refresh the page and try again.');
226 }
227 return;
228 }
229
230 const frame = wp.media({
231 title: 'Select Company Logo',
232 button: { text: 'Use this logo' },
233 multiple: false
234 });
235
236 frame.on('select', function() {
237 const attachment = frame.state().get('selection').first().toJSON();
238 $(previewId).attr('src', attachment.url).removeClass('hidden');
239 $(inputId).val(attachment.url);
240 });
241
242 frame.open();
243 });
244
245 // Handle form submission
246 $('#save-settings, #save-settings-bottom').on('click', function(e) {
247 e.preventDefault();
248 const button = $(this);
249 const originalText = button.html();
250
251 // Sync wp_editor content before submission
252 if (typeof tinyMCE !== 'undefined') {
253 tinyMCE.triggerSave();
254 }
255
256 button.prop('disabled', true)
257 .html('<i class="fas fa-spinner fa-spin mr-2"></i>Saving...');
258 const formData = new FormData($('#settings-form')[0]);
259 formData.append('action', 'easy_invoice_save_settings');
260 formData.append('nonce', easyInvoiceSettings.nonce);
261 if (!$('input[name="settings[easy_invoice_payment_methods][]"]:checked').length) {
262 formData.append('settings[easy_invoice_payment_methods]', '');
263 }
264
265
266 $.ajax({
267 url: easyInvoiceSettings.ajaxurl,
268 type: 'POST',
269 data: formData,
270 processData: false,
271 contentType: false,
272 success: function(response) {
273
274 if (response.success) {
275 if (typeof EasyInvoiceToast !== 'undefined') {
276 EasyInvoiceToast.success('Settings saved successfully');
277 } else {
278 // Simple fallback if toast system is not available
279 alert('Settings saved successfully');
280 }
281
282 // Trigger settingsSaved event to reinitialize Select2
283 $(document).trigger('settingsSaved');
284 } else {
285 // Check for error message in different possible locations
286 let errorMessage = 'Error saving settings';
287 if (response.message) {
288 errorMessage = response.message;
289 } else if (response.data && response.data.message) {
290 errorMessage = response.data.message;
291 }
292
293 if (typeof EasyInvoiceToast !== 'undefined') {
294 EasyInvoiceToast.error(errorMessage);
295 } else {
296 alert(errorMessage);
297 }
298 }
299 button.prop('disabled', false).html(originalText);
300 },
301 error: function(xhr, status, error) {
302 console.error('Settings save AJAX error:', {xhr, status, error});
303
304 let errorMessage = 'Error saving settings';
305 if (xhr.responseJSON && xhr.responseJSON.message) {
306 errorMessage = xhr.responseJSON.message;
307 } else if (xhr.responseText) {
308 try {
309 const response = JSON.parse(xhr.responseText);
310 if (response.message) {
311 errorMessage = response.message;
312 }
313 } catch (e) {
314 console.error('Failed to parse error response:', e);
315 }
316 }
317
318 if (typeof EasyInvoiceToast !== 'undefined') {
319 EasyInvoiceToast.error(errorMessage);
320 } else {
321 alert(errorMessage);
322 }
323 button.prop('disabled', false).html(originalText);
324 }
325 });
326 });
327
328 // Handle regenerate quote numbers functionality
329 $(document).on('click', '.regenerate-quote-numbers-button', function(e) {
330 e.preventDefault();
331 const button = $(this);
332 const originalText = button.html();
333
334 // Check if EasyInvoiceConfirmation is available
335 if (typeof EasyInvoiceConfirmation !== 'undefined' && EasyInvoiceConfirmation.confirmAction) {
336
337 try {
338 EasyInvoiceConfirmation.confirmAction(
339 'regenerate',
340 'all quote numbers',
341 function() {
342 // User confirmed, proceed with regeneration
343 performQuoteRegeneration(button, originalText);
344 }
345 );
346 } catch (error) {
347 console.error('Error calling EasyInvoiceConfirmation.confirmAction:', error);
348 }
349 } else {
350 // Fallback to browser confirm
351 if (confirm('This will regenerate all quote numbers starting from the Next Quote Number. This action cannot be undone. Are you sure you want to continue?')) {
352 performQuoteRegeneration(button, originalText);
353 }
354 }
355 });
356
357 function performQuoteRegeneration(button, originalText) {
358 button.prop('disabled', true).html('Regenerating...');
359
360 $.ajax({
361 url: easyInvoiceSettings.ajaxurl,
362 type: 'POST',
363 data: {
364 action: 'regenerate_quote_numbers',
365 nonce: easyInvoiceSettings.nonce,
366 easy_invoice_settings_nonce: $('input[name="easy_invoice_settings_nonce"]').val()
367 },
368 success: function(response) {
369 if (response.success) {
370 if (typeof EasyInvoiceToast !== 'undefined') {
371 EasyInvoiceToast.success(response.data.message || 'Quote numbers regenerated successfully');
372 } else {
373 alert(response.data.message || 'Quote numbers regenerated successfully');
374 }
375
376 // Update the Next Quote Number field if provided
377 if (response.data.next_number) {
378 $('input[name="settings[easy_invoice_next_quote_number]"]').val(response.data.next_number);
379 }
380
381 // Reload the page after a short delay to show the updated numbers
382 setTimeout(function() {
383 location.reload();
384 }, 2000);
385 } else {
386 if (typeof EasyInvoiceToast !== 'undefined') {
387 EasyInvoiceToast.error(response.data.message || 'Failed to regenerate quote numbers');
388 } else {
389 alert(response.data.message || 'Failed to regenerate quote numbers');
390 }
391 }
392 button.prop('disabled', false).html(originalText);
393 },
394 error: function(xhr, status, error) {
395 console.error('AJAX Error:', error);
396 console.error('Status:', status);
397 console.error('Response:', xhr.responseText);
398 if (typeof EasyInvoiceToast !== 'undefined') {
399 EasyInvoiceToast.error('Failed to regenerate quote numbers. Please try again.');
400 } else {
401 alert('Failed to regenerate quote numbers. Please try again.');
402 }
403 button.prop('disabled', false).html(originalText);
404 }
405 });
406 }
407
408 // Handle test email functionality
409 $(document).on('click', '.test-email-button', function(e) {
410 e.preventDefault();
411 const button = $(this);
412 const originalText = button.html();
413 const emailInput = button.closest('.mt-1').find('input[type="email"]');
414 const resultDiv = button.closest('.mt-1').find('[id$="_result"]');
415 const testEmail = emailInput.val().trim();
416
417 if (!testEmail) {
418 if (typeof EasyInvoiceToast !== 'undefined') {
419 EasyInvoiceToast.show('error', 'Please enter a valid email address');
420 }
421 return;
422 }
423
424 if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(testEmail)) {
425 if (typeof EasyInvoiceToast !== 'undefined') {
426 EasyInvoiceToast.show('error', 'Please enter a valid email address');
427 }
428 return;
429 }
430
431 button.prop('disabled', true)
432 .html('<i class="fas fa-spinner fa-spin mr-2"></i>Sending...');
433
434 $.ajax({
435 url: easyInvoiceSettings.ajaxurl,
436 type: 'POST',
437 data: {
438 action: 'easy_invoice_test_email',
439 test_email: testEmail,
440 nonce: easyInvoiceSettings.nonce
441 },
442 success: function(response) {
443 if (response.success) {
444 resultDiv.html('<div class="text-green-600 text-sm"><i class="fas fa-check-circle mr-1"></i>' + response.data.message + '</div>')
445 .show();
446 } else {
447 resultDiv.html('<div class="text-red-600 text-sm"><i class="fas fa-exclamation-circle mr-1"></i>' + (response.data && response.data.message ? response.data.message : 'Failed to send test email') + '</div>')
448 .show();
449 }
450 button.prop('disabled', false).html(originalText);
451
452 // Hide result after 5 seconds
453 setTimeout(function() {
454 resultDiv.fadeOut();
455 }, 5000);
456 },
457 error: function() {
458 resultDiv.html('<div class="text-red-600 text-sm"><i class="fas fa-exclamation-circle mr-1"></i>Error sending test email</div>')
459 .show();
460 button.prop('disabled', false).html(originalText);
461
462 // Hide result after 5 seconds
463 setTimeout(function() {
464 resultDiv.fadeOut();
465 }, 5000);
466 }
467 });
468 });
469
470 // Handle test template email functionality
471 $(document).on('click', '.test-template-email-button', function(e) {
472 e.preventDefault();
473 const button = $(this);
474 const originalText = button.html();
475 const emailInput = button.closest('.mt-1').find('input[type="email"]');
476 const resultDiv = button.closest('.mt-1').find('[id$="_result"]');
477 const testEmail = emailInput.val().trim();
478 const templateType = button.data('template-type');
479
480 if (!testEmail) {
481 if (typeof EasyInvoiceToast !== 'undefined') {
482 EasyInvoiceToast.show('error', 'Please enter a valid email address');
483 }
484 return;
485 }
486
487 if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(testEmail)) {
488 if (typeof EasyInvoiceToast !== 'undefined') {
489 EasyInvoiceToast.show('error', 'Please enter a valid email address');
490 }
491 return;
492 }
493
494 button.prop('disabled', true)
495 .html('<i class="fas fa-spinner fa-spin mr-2"></i>Testing...');
496
497 $.ajax({
498 url: easyInvoiceSettings.ajaxurl,
499 type: 'POST',
500 data: {
501 action: 'easy_invoice_test_template_email',
502 test_email: testEmail,
503 template_type: templateType,
504 nonce: easyInvoiceSettings.nonce
505 },
506 success: function(response) {
507 if (response.success) {
508 resultDiv.html('<div class="text-green-600 text-sm"><i class="fas fa-check-circle mr-1"></i>' + response.data.message + '</div>')
509 .show();
510 } else {
511 resultDiv.html('<div class="text-red-600 text-sm"><i class="fas fa-exclamation-circle mr-1"></i>' + (response.data && response.data.message ? response.data.message : 'Failed to send template test email') + '</div>')
512 .show();
513 }
514 button.prop('disabled', false).html(originalText);
515
516 // Hide result after 5 seconds
517 setTimeout(function() {
518 resultDiv.fadeOut();
519 }, 5000);
520 },
521 error: function() {
522 resultDiv.html('<div class="text-red-600 text-sm"><i class="fas fa-exclamation-circle mr-1"></i>Error sending template test email</div>')
523 .show();
524 button.prop('disabled', false).html(originalText);
525
526 // Hide result after 5 seconds
527 setTimeout(function() {
528 resultDiv.fadeOut();
529 }, 5000);
530 }
531 });
532 });
533
534 // Handle test payment reminder email functionality
535 $(document).on('click', '.test-payment-reminder-email-button', function(e) {
536 e.preventDefault();
537 const button = $(this);
538 const originalText = button.html();
539 const emailInput = button.closest('.mt-1').find('input[type="email"]');
540 const resultDiv = button.closest('.mt-1').find('[id$="_result"]');
541 const testEmail = emailInput.val().trim();
542
543 if (!testEmail) {
544 if (typeof EasyInvoiceToast !== 'undefined') {
545 EasyInvoiceToast.show('error', 'Please enter a valid email address');
546 }
547 return;
548 }
549
550 if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(testEmail)) {
551 if (typeof EasyInvoiceToast !== 'undefined') {
552 EasyInvoiceToast.show('error', 'Please enter a valid email address');
553 }
554 return;
555 }
556
557 button.prop('disabled', true)
558 .html('<i class="fas fa-spinner fa-spin mr-2"></i>Testing...');
559
560 $.ajax({
561 url: easyInvoiceSettings.ajaxurl,
562 type: 'POST',
563 data: {
564 action: 'easy_invoice_test_payment_reminder_email',
565 test_email: testEmail,
566 nonce: easyInvoiceSettings.nonce
567 },
568 success: function(response) {
569 if (response.success) {
570 resultDiv.html('<div class="text-green-600 text-sm"><i class="fas fa-check-circle mr-1"></i>' + response.data.message + '</div>')
571 .show();
572 } else {
573 resultDiv.html('<div class="text-red-600 text-sm"><i class="fas fa-exclamation-circle mr-1"></i>' + (response.data && response.data.message ? response.data.message : 'Failed to send test payment reminder email') + '</div>')
574 .show();
575 }
576 button.prop('disabled', false).html(originalText);
577
578 // Hide result after 5 seconds
579 setTimeout(function() {
580 resultDiv.fadeOut();
581 }, 5000);
582 },
583 error: function() {
584 resultDiv.html('<div class="text-red-600 text-sm"><i class="fas fa-exclamation-circle mr-1"></i>Error sending test payment reminder email</div>')
585 .show();
586 button.prop('disabled', false).html(originalText);
587
588 // Hide result after 5 seconds
589 setTimeout(function() {
590 resultDiv.fadeOut();
591 }, 5000);
592 }
593 });
594 });
595
596 // Handle regenerate invoice numbers functionality
597 $(document).on('click', '.regenerate-invoice-numbers-button', function(e) {
598 e.preventDefault();
599 const button = $(this);
600 const originalText = button.html();
601
602
603 // Show confirmation dialog
604 if (typeof EasyInvoiceConfirmation !== 'undefined') {
605 try {
606 EasyInvoiceConfirmation.confirmAction(
607 'regenerate',
608 'all invoice numbers',
609 function() {
610 // User confirmed, proceed with regeneration
611 performInvoiceRegeneration(button, originalText);
612 }
613 );
614 } catch (error) {
615 console.error('Error calling EasyInvoiceConfirmation.confirmAction:', error);
616 }
617 } else {
618 // Fallback to browser confirm
619 if (confirm('This will regenerate all invoice numbers starting from the Last Invoice Number. This action cannot be undone. Are you sure you want to continue?')) {
620 performInvoiceRegeneration(button, originalText);
621 }
622 }
623 });
624
625 // Function to perform the actual regeneration
626 function performInvoiceRegeneration(button, originalText) {
627 button.prop('disabled', true)
628 .html('<i class="fas fa-spinner fa-spin mr-2"></i>Regenerating...');
629
630
631 $.ajax({
632 url: easyInvoiceSettings.ajaxurl,
633 type: 'POST',
634 data: {
635 action: 'regenerate_invoice_numbers',
636 nonce: easyInvoiceSettings.nonce,
637 easy_invoice_settings_nonce: $('input[name="easy_invoice_settings_nonce"]').val()
638 },
639 success: function(response) {
640 if (response.success) {
641 if (typeof EasyInvoiceToast !== 'undefined') {
642 EasyInvoiceToast.success(response.data.message);
643 } else {
644 alert(response.data.message);
645 }
646
647 // Update the Next Invoice Number field if provided
648 if (response.data.next_number) {
649 $('input[name="settings[easy_invoice_next_invoice_number]"]').val(response.data.next_number);
650 }
651
652 // Refresh the page to show updated values
653 setTimeout(function() {
654 location.reload();
655 }, 2000);
656 } else {
657 if (typeof EasyInvoiceToast !== 'undefined') {
658 EasyInvoiceToast.error(response.data.message || 'Failed to regenerate invoice numbers');
659 } else {
660 alert(response.data.message || 'Failed to regenerate invoice numbers');
661 }
662 }
663 button.prop('disabled', false).html(originalText);
664 },
665 error: function(xhr, status, error) {
666 console.error('Invoice regeneration AJAX error:', {xhr, status, error});
667
668 let errorMessage = 'Error regenerating invoice numbers';
669 if (xhr.responseJSON && xhr.responseJSON.data && xhr.responseJSON.data.message) {
670 errorMessage = xhr.responseJSON.data.message;
671 } else if (xhr.responseText) {
672 try {
673 const response = JSON.parse(xhr.responseText);
674 if (response.data && response.data.message) {
675 errorMessage = response.data.message;
676 }
677 } catch (e) {
678 console.error('Failed to parse error response:', e);
679 }
680 }
681
682 if (typeof EasyInvoiceToast !== 'undefined') {
683 EasyInvoiceToast.error(errorMessage);
684 } else {
685 alert(errorMessage);
686 }
687 button.prop('disabled', false).html(originalText);
688 }
689 });
690 }
691 });