mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2026-03-03 03:07:01 +00:00
Fixes for required fields copying across
This commit is contained in:
@@ -186,8 +186,6 @@ class InvoiceCheckOverdue implements ShouldQueue
|
||||
continue;
|
||||
}
|
||||
|
||||
nlog($company_user->permissions);
|
||||
|
||||
$overdue_invoices_collection = $overdue_invoices;
|
||||
|
||||
$invoice = Invoice::withTrashed()->find($overdue_invoices[0]['id']);
|
||||
@@ -205,7 +203,7 @@ class InvoiceCheckOverdue implements ShouldQueue
|
||||
$overdue_invoices_collection = collect($overdue_invoices)
|
||||
->filter(function ($overdue_invoice) use ($user) {
|
||||
$invoice = Invoice::withTrashed()->find($overdue_invoice['id']);
|
||||
nlog([$invoice->user_id, $user->id, $invoice->assigned_user_id, $user->id]);
|
||||
// nlog([$invoice->user_id, $user->id, $invoice->assigned_user_id, $user->id]);
|
||||
return $invoice->user_id == $user->id || $invoice->assigned_user_id == $user->id;
|
||||
})
|
||||
->toArray();
|
||||
@@ -243,6 +241,7 @@ class InvoiceCheckOverdue implements ShouldQueue
|
||||
|
||||
/**
|
||||
* Send notifications for an overdue invoice to all relevant company users.
|
||||
* @deprecated in favour of sendOverdueNotifications to send a summary email to all users
|
||||
*/
|
||||
/** @phpstan-ignore-next-line */
|
||||
private function notifyOverdueInvoice(Invoice $invoice): void
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
|
||||
@component('portal.ninja2020.components.general.card-element-single')
|
||||
<div class="flex justify-end">
|
||||
<button type="button" class="bg-gray-100 px-2 py-1 text-sm rounded" wire:click="handleCopyBilling">
|
||||
<button type="button" id="copy-billing-button" class="bg-gray-100 px-2 py-1 text-sm rounded">
|
||||
{{ ctrans('texts.copy_billing') }}
|
||||
</button>
|
||||
</div>
|
||||
@@ -131,3 +131,106 @@
|
||||
@endif
|
||||
|
||||
</div>
|
||||
|
||||
@script
|
||||
<script>
|
||||
(function() {
|
||||
function copyBillingToShipping() {
|
||||
const form = document.getElementById('required-client-info-form');
|
||||
if (!form) return;
|
||||
|
||||
// Pure vanilla JavaScript - read directly from DOM and update DOM
|
||||
// Mapping: billing field => shipping field
|
||||
const fieldMappings = [
|
||||
{ from: 'client_address_line_1', to: 'client_shipping_address_line_1' },
|
||||
{ from: 'client_address_line_2', to: 'client_shipping_address_line_2' },
|
||||
{ from: 'client_city', to: 'client_shipping_city' },
|
||||
{ from: 'client_state', to: 'client_shipping_state' },
|
||||
{ from: 'client_postal_code', to: 'client_shipping_postal_code' },
|
||||
{ from: 'client_country_id', to: 'client_shipping_country_id' }
|
||||
];
|
||||
|
||||
fieldMappings.forEach(function(mapping) {
|
||||
var from = mapping.from;
|
||||
var to = mapping.to;
|
||||
|
||||
// Find the billing input field
|
||||
var billingField = form.querySelector('[name="' + from + '"]');
|
||||
// Find the shipping input field
|
||||
var shippingField = form.querySelector('[name="' + to + '"]');
|
||||
|
||||
if (!billingField || !shippingField) return;
|
||||
|
||||
// Try multiple methods to get the current value
|
||||
var currentValue = '';
|
||||
|
||||
// Method 1: Direct .value property
|
||||
var directValue = billingField.value || '';
|
||||
|
||||
// Method 2: Try getting from Livewire if available (for wire:model fields)
|
||||
var livewireValue = null;
|
||||
try {
|
||||
// Check if Livewire is available and has the property
|
||||
if (typeof window.Livewire !== 'undefined') {
|
||||
var component = window.Livewire.find(billingField.closest('[wire\\:id]')?.getAttribute('wire:id'));
|
||||
if (component) {
|
||||
livewireValue = component.get(from);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Livewire not available or error reading
|
||||
}
|
||||
|
||||
// Method 3: Use FormData to get form values
|
||||
var formData = new FormData(form);
|
||||
var formDataValue = formData.get(from);
|
||||
|
||||
// Choose the best value - prioritize what user sees/types
|
||||
// If direct value exists and is not empty, use it
|
||||
// Otherwise try Livewire, then FormData
|
||||
if (directValue !== '' && directValue !== null && directValue !== undefined) {
|
||||
currentValue = directValue;
|
||||
} else if (livewireValue !== null && livewireValue !== undefined && livewireValue !== '') {
|
||||
currentValue = String(livewireValue);
|
||||
} else if (formDataValue !== null && formDataValue !== undefined) {
|
||||
currentValue = String(formDataValue);
|
||||
} else {
|
||||
currentValue = '';
|
||||
}
|
||||
|
||||
// Directly set the shipping field's DOM .value property
|
||||
shippingField.value = currentValue;
|
||||
|
||||
// Trigger the appropriate event so Livewire's wire:model can sync
|
||||
if (shippingField.tagName === 'SELECT') {
|
||||
// For select elements, trigger 'change' event
|
||||
var changeEvent = new Event('change', { bubbles: true, cancelable: true });
|
||||
shippingField.dispatchEvent(changeEvent);
|
||||
} else {
|
||||
// For input elements, trigger 'input' event
|
||||
var inputEvent = new Event('input', { bubbles: true, cancelable: true });
|
||||
shippingField.dispatchEvent(inputEvent);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Wait for DOM to be ready, then attach event listener
|
||||
function attachListener() {
|
||||
var button = document.getElementById('copy-billing-button');
|
||||
if (button) {
|
||||
button.addEventListener('click', copyBillingToShipping);
|
||||
} else {
|
||||
// Try again after a short delay in case the button hasn't rendered yet
|
||||
setTimeout(attachListener, 100);
|
||||
}
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', attachListener);
|
||||
} else {
|
||||
// DOM is already ready
|
||||
attachListener();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
@endscript
|
||||
|
||||
@@ -1,58 +1,4 @@
|
||||
<div x-data="{
|
||||
fields: @entangle('fields'),
|
||||
copyBillingToShipping() {
|
||||
// Mapping: billing field => shipping field
|
||||
const mappings = {
|
||||
'client_address_line_1': 'client_shipping_address_line_1',
|
||||
'client_address_line_2': 'client_shipping_address_line_2',
|
||||
'client_city': 'client_shipping_city',
|
||||
'client_state': 'client_shipping_state',
|
||||
'client_postal_code': 'client_shipping_postal_code',
|
||||
'client_country_id': 'client_shipping_country_id'
|
||||
};
|
||||
|
||||
// Copy values from billing fields to shipping fields
|
||||
Object.entries(mappings).forEach(([billingField, shippingField]) => {
|
||||
const billingInput = document.querySelector(`input[name='${billingField}'], select[name='${billingField}']`);
|
||||
const shippingInput = document.querySelector(`input[name='${shippingField}'], select[name='${shippingField}']`);
|
||||
|
||||
if (billingInput && shippingInput) {
|
||||
// Get the value from billing field (read from form, not database)
|
||||
let value = billingInput.value;
|
||||
|
||||
// Handle country_id specially - ensure it's a valid number or null
|
||||
if (billingField === 'client_country_id') {
|
||||
if (value === 'none' || value === '' || value === null) {
|
||||
value = null;
|
||||
} else {
|
||||
value = parseInt(value, 10);
|
||||
}
|
||||
}
|
||||
|
||||
// Update Livewire model property first
|
||||
if (shippingInput.hasAttribute('wire:model')) {
|
||||
const modelName = shippingInput.getAttribute('wire:model');
|
||||
$wire.set(modelName, value);
|
||||
}
|
||||
|
||||
// Update the DOM input/select value for immediate visual feedback
|
||||
// For select, convert back to string for the option value
|
||||
if (shippingInput.tagName === 'SELECT') {
|
||||
shippingInput.value = value !== null ? String(value) : 'none';
|
||||
// Trigger change event for select - this is important for Livewire
|
||||
shippingInput.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
// Also trigger input event
|
||||
shippingInput.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
} else {
|
||||
shippingInput.value = value !== null ? value : '';
|
||||
// Trigger Livewire's input event to ensure sync
|
||||
shippingInput.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}"
|
||||
class="rounded-lg border bg-card text-card-foreground shadow-sm overflow-hidden px-4 py-5 bg-white sm:gap-4 sm:px-6">
|
||||
<div class="rounded-lg border bg-card text-card-foreground shadow-sm overflow-hidden px-4 py-5 bg-white sm:gap-4 sm:px-6">
|
||||
|
||||
<p class="font-semibold tracking-tight group flex items-center gap-2 text-lg mb-3">
|
||||
{{ ctrans('texts.required_fields') }}
|
||||
@@ -72,7 +18,7 @@
|
||||
@foreach($fields as $field)
|
||||
@component('portal.ninja2020.components.general.card-element', ['title' => $field['label']])
|
||||
@if($field['name'] == 'client_country_id' || $field['name'] == 'client_shipping_country_id')
|
||||
<select id="client_country" class="input w-full form-select bg-white" name="{{ $field['name'] }}"
|
||||
<select id="{{ $field['name'] }}" class="input w-full form-select bg-white" name="{{ $field['name'] }}"
|
||||
wire:model="{{ $field['name'] }}">
|
||||
<option value="none"></option>
|
||||
|
||||
@@ -98,7 +44,7 @@
|
||||
|
||||
<div class="bg-white px-4 py-5 flex items-center w-full justify-end">
|
||||
<button type="button"
|
||||
@click="copyBillingToShipping()"
|
||||
id="copy-billing-button"
|
||||
class="bg-gray-100 hover:bg-gray-200 px-4 py-2 text-sm rounded transition-colors">
|
||||
{{ ctrans('texts.copy_billing') }}
|
||||
</button>
|
||||
@@ -120,3 +66,106 @@
|
||||
</form>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@script
|
||||
<script>
|
||||
(function() {
|
||||
function copyBillingToShipping() {
|
||||
const form = document.getElementById('required-client-info-form');
|
||||
if (!form) return;
|
||||
|
||||
// Pure vanilla JavaScript - read directly from DOM and update DOM
|
||||
// Mapping: billing field => shipping field
|
||||
const fieldMappings = [
|
||||
{ from: 'client_address_line_1', to: 'client_shipping_address_line_1' },
|
||||
{ from: 'client_address_line_2', to: 'client_shipping_address_line_2' },
|
||||
{ from: 'client_city', to: 'client_shipping_city' },
|
||||
{ from: 'client_state', to: 'client_shipping_state' },
|
||||
{ from: 'client_postal_code', to: 'client_shipping_postal_code' },
|
||||
{ from: 'client_country_id', to: 'client_shipping_country_id' }
|
||||
];
|
||||
|
||||
fieldMappings.forEach(function(mapping) {
|
||||
var from = mapping.from;
|
||||
var to = mapping.to;
|
||||
|
||||
// Find the billing input field
|
||||
var billingField = form.querySelector('[name="' + from + '"]');
|
||||
// Find the shipping input field
|
||||
var shippingField = form.querySelector('[name="' + to + '"]');
|
||||
|
||||
if (!billingField || !shippingField) return;
|
||||
|
||||
// Try multiple methods to get the current value
|
||||
var currentValue = '';
|
||||
|
||||
// Method 1: Direct .value property
|
||||
var directValue = billingField.value || '';
|
||||
|
||||
// Method 2: Try getting from Livewire if available (for wire:model fields)
|
||||
var livewireValue = null;
|
||||
try {
|
||||
// Check if Livewire is available and has the property
|
||||
if (typeof window.Livewire !== 'undefined') {
|
||||
var component = window.Livewire.find(billingField.closest('[wire\\:id]')?.getAttribute('wire:id'));
|
||||
if (component) {
|
||||
livewireValue = component.get(from);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Livewire not available or error reading
|
||||
}
|
||||
|
||||
// Method 3: Use FormData to get form values
|
||||
var formData = new FormData(form);
|
||||
var formDataValue = formData.get(from);
|
||||
|
||||
// Choose the best value - prioritize what user sees/types
|
||||
// If direct value exists and is not empty, use it
|
||||
// Otherwise try Livewire, then FormData
|
||||
if (directValue !== '' && directValue !== null && directValue !== undefined) {
|
||||
currentValue = directValue;
|
||||
} else if (livewireValue !== null && livewireValue !== undefined && livewireValue !== '') {
|
||||
currentValue = String(livewireValue);
|
||||
} else if (formDataValue !== null && formDataValue !== undefined) {
|
||||
currentValue = String(formDataValue);
|
||||
} else {
|
||||
currentValue = '';
|
||||
}
|
||||
|
||||
// Directly set the shipping field's DOM .value property
|
||||
shippingField.value = currentValue;
|
||||
|
||||
// Trigger the appropriate event so Livewire's wire:model can sync
|
||||
if (shippingField.tagName === 'SELECT') {
|
||||
// For select elements, trigger 'change' event
|
||||
var changeEvent = new Event('change', { bubbles: true, cancelable: true });
|
||||
shippingField.dispatchEvent(changeEvent);
|
||||
} else {
|
||||
// For input elements, trigger 'input' event
|
||||
var inputEvent = new Event('input', { bubbles: true, cancelable: true });
|
||||
shippingField.dispatchEvent(inputEvent);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Wait for DOM to be ready, then attach event listener
|
||||
function attachListener() {
|
||||
var button = document.getElementById('copy-billing-button');
|
||||
if (button) {
|
||||
button.addEventListener('click', copyBillingToShipping);
|
||||
} else {
|
||||
// Try again after a short delay in case the button hasn't rendered yet
|
||||
setTimeout(attachListener, 100);
|
||||
}
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', attachListener);
|
||||
} else {
|
||||
// DOM is already ready
|
||||
attachListener();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
@endscript
|
||||
Reference in New Issue
Block a user