This commit is contained in:
gsinghpal
2026-03-11 12:15:53 -04:00
parent f81e0cd918
commit db4b9aa278
1210 changed files with 173089 additions and 4044 deletions

View File

@@ -1048,331 +1048,7 @@ async function setupSimpleAddressFields(el, orm) {
}
}
/**
* Setup autocomplete for LTC Facility form.
* Attaches establishment search on the name field and address search on street.
*/
async function setupFacilityAutocomplete(el, model, orm) {
globalOrm = orm;
const apiKey = await getGoogleMapsApiKey(orm);
if (!apiKey) return;
try { await loadGoogleMapsApi(apiKey); } catch (e) { return; }
// --- Name field: establishment autocomplete ---
const nameSelectors = [
'.oe_title [name="name"] input',
'div[name="name"] input',
'.o_field_widget[name="name"] input',
'[name="name"] input',
];
let nameInput = null;
for (const sel of nameSelectors) {
nameInput = el.querySelector(sel);
if (nameInput) break;
}
if (nameInput && !autocompleteInstances.has('facility_name_' + (nameInput.id || 'default'))) {
_attachFacilityNameAutocomplete(nameInput, el, model);
}
// --- Street field: address autocomplete ---
const streetSelectors = [
'div[name="street"] input',
'.o_field_widget[name="street"] input',
'[name="street"] input',
];
let streetInput = null;
for (const sel of streetSelectors) {
streetInput = el.querySelector(sel);
if (streetInput) break;
}
if (streetInput && !autocompleteInstances.has(streetInput)) {
_attachFacilityAddressAutocomplete(streetInput, el, model);
}
}
/**
* Attach establishment (business) autocomplete on facility name field.
* Selecting a business fills name, address, phone, email, and website.
*/
function _attachFacilityNameAutocomplete(input, el, model) {
if (!input || !window.google?.maps?.places) return;
const instanceKey = 'facility_name_' + (input.id || 'default');
if (autocompleteInstances.has(instanceKey)) return;
const autocomplete = new google.maps.places.Autocomplete(input, {
componentRestrictions: { country: 'ca' },
types: ['establishment'],
fields: [
'place_id', 'name', 'address_components', 'formatted_address',
'formatted_phone_number', 'international_phone_number', 'website',
],
});
autocomplete.addListener('place_changed', async () => {
let place = autocomplete.getPlace();
if (!place.name && !place.place_id) return;
if (place.place_id && !place.formatted_phone_number && !place.website) {
try {
const service = new google.maps.places.PlacesService(document.createElement('div'));
const details = await new Promise((resolve, reject) => {
service.getDetails(
{
placeId: place.place_id,
fields: ['formatted_phone_number', 'international_phone_number', 'website'],
},
(result, status) => {
if (status === google.maps.places.PlacesServiceStatus.OK) resolve(result);
else reject(new Error(status));
}
);
});
if (details.formatted_phone_number) place.formatted_phone_number = details.formatted_phone_number;
if (details.international_phone_number) place.international_phone_number = details.international_phone_number;
if (details.website) place.website = details.website;
} catch (_) { /* ignore */ }
}
let streetNumber = '', streetName = '', unitNumber = '';
let city = '', province = '', postalCode = '', countryCode = '';
if (place.address_components) {
for (const c of place.address_components) {
const t = c.types;
if (t.includes('street_number')) streetNumber = c.long_name;
else if (t.includes('route')) streetName = c.long_name;
else if (t.includes('subpremise')) unitNumber = c.long_name;
else if (t.includes('floor') && !unitNumber) unitNumber = 'Floor ' + c.long_name;
else if (t.includes('locality')) city = c.long_name;
else if (t.includes('sublocality_level_1') && !city) city = c.long_name;
else if (t.includes('administrative_area_level_1')) province = c.short_name;
else if (t.includes('postal_code')) postalCode = c.long_name;
else if (t.includes('country')) countryCode = c.short_name;
}
}
const street = streetNumber ? `${streetNumber} ${streetName}` : streetName;
const phone = place.formatted_phone_number || place.international_phone_number || '';
if (!model?.root) return;
const record = model.root;
let countryId = null, stateId = null;
if (globalOrm && countryCode) {
try {
const [countries, states] = await Promise.all([
globalOrm.searchRead('res.country', [['code', '=', countryCode]], ['id'], { limit: 1 }),
province
? globalOrm.searchRead('res.country.state', [['code', '=', province], ['country_id.code', '=', countryCode]], ['id'], { limit: 1 })
: Promise.resolve([]),
]);
if (countries.length) countryId = countries[0].id;
if (states.length) stateId = states[0].id;
} catch (_) { /* ignore */ }
}
if (record.resId && globalOrm) {
try {
const firstWrite = {};
if (place.name) firstWrite.name = place.name;
if (street) firstWrite.street = street;
if (unitNumber) firstWrite.street2 = unitNumber;
if (city) firstWrite.city = city;
if (postalCode) firstWrite.zip = postalCode;
if (phone) firstWrite.phone = phone;
if (place.website) firstWrite.website = place.website;
if (countryId) firstWrite.country_id = countryId;
await globalOrm.write('fusion.ltc.facility', [record.resId], firstWrite);
if (stateId) {
await new Promise(r => setTimeout(r, 100));
await globalOrm.write('fusion.ltc.facility', [record.resId], { state_id: stateId });
}
await record.load();
} catch (err) {
console.error('[GooglePlaces Facility] Name autocomplete ORM write failed:', err);
}
} else {
try {
const textUpdate = {};
if (place.name) textUpdate.name = place.name;
if (street) textUpdate.street = street;
if (unitNumber) textUpdate.street2 = unitNumber;
if (city) textUpdate.city = city;
if (postalCode) textUpdate.zip = postalCode;
if (phone) textUpdate.phone = phone;
if (place.website) textUpdate.website = place.website;
await record.update(textUpdate);
if (countryId && globalOrm) {
const formEl = input.closest('.o_form_view') || input.closest('.o_content') || document.body;
const [countryData, stateData] = await Promise.all([
globalOrm.read('res.country', [countryId], ['display_name']),
stateId ? globalOrm.read('res.country.state', [stateId], ['display_name']) : Promise.resolve([]),
]);
await simulateMany2OneSelection(formEl, 'country_id', countryId, countryData[0]?.display_name || 'Canada');
await new Promise(r => setTimeout(r, 300));
if (stateId && stateData.length) {
await simulateMany2OneSelection(formEl, 'state_id', stateId, stateData[0]?.display_name || province);
}
}
} catch (err) {
console.error('[GooglePlaces Facility] Name autocomplete update failed:', err);
}
}
});
autocompleteInstances.set(instanceKey, autocomplete);
input.style.backgroundImage = 'url("data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 24 24\' fill=\'%232196F3\'%3E%3Cpath d=\'M12 7V3H2v18h20V7H12zM6 19H4v-2h2v2zm0-4H4v-2h2v2zm0-4H4V9h2v2zm0-4H4V5h2v2zm4 12H8v-2h2v2zm0-4H8v-2h2v2zm0-4H8V9h2v2zm0-4H8V5h2v2zm10 12h-8v-2h2v-2h-2v-2h2v-2h-2V9h8v10zm-2-8h-2v2h2v-2zm0 4h-2v2h2v-2z\'/%3E%3C/svg%3E")';
input.style.backgroundRepeat = 'no-repeat';
input.style.backgroundPosition = 'right 8px center';
input.style.backgroundSize = '20px';
input.style.paddingRight = '35px';
}
/**
* Attach address autocomplete on facility street field.
* Fills street, street2, city, state, zip, and country.
*/
function _attachFacilityAddressAutocomplete(input, el, model) {
if (!input || !window.google?.maps?.places) return;
if (autocompleteInstances.has(input)) return;
const autocomplete = new google.maps.places.Autocomplete(input, {
componentRestrictions: { country: 'ca' },
types: ['address'],
fields: ['address_components', 'formatted_address'],
});
autocomplete.addListener('place_changed', async () => {
const place = autocomplete.getPlace();
if (!place.address_components) return;
let streetNumber = '', streetName = '', unitNumber = '';
let city = '', province = '', postalCode = '', countryCode = '';
for (const c of place.address_components) {
const t = c.types;
if (t.includes('street_number')) streetNumber = c.long_name;
else if (t.includes('route')) streetName = c.long_name;
else if (t.includes('subpremise')) unitNumber = c.long_name;
else if (t.includes('floor') && !unitNumber) unitNumber = 'Floor ' + c.long_name;
else if (t.includes('locality')) city = c.long_name;
else if (t.includes('sublocality_level_1') && !city) city = c.long_name;
else if (t.includes('administrative_area_level_1')) province = c.short_name;
else if (t.includes('postal_code')) postalCode = c.long_name;
else if (t.includes('country')) countryCode = c.short_name;
}
const street = streetNumber ? `${streetNumber} ${streetName}` : streetName;
if (!model?.root) return;
const record = model.root;
let countryId = null, stateId = null;
if (globalOrm && countryCode) {
try {
const [countries, states] = await Promise.all([
globalOrm.searchRead('res.country', [['code', '=', countryCode]], ['id'], { limit: 1 }),
province
? globalOrm.searchRead('res.country.state', [['code', '=', province], ['country_id.code', '=', countryCode]], ['id'], { limit: 1 })
: Promise.resolve([]),
]);
if (countries.length) countryId = countries[0].id;
if (states.length) stateId = states[0].id;
} catch (_) { /* ignore */ }
}
if (record.resId && globalOrm) {
try {
const firstWrite = {};
if (street) firstWrite.street = street;
if (unitNumber) firstWrite.street2 = unitNumber;
if (city) firstWrite.city = city;
if (postalCode) firstWrite.zip = postalCode;
if (countryId) firstWrite.country_id = countryId;
await globalOrm.write('fusion.ltc.facility', [record.resId], firstWrite);
if (stateId) {
await new Promise(r => setTimeout(r, 100));
await globalOrm.write('fusion.ltc.facility', [record.resId], { state_id: stateId });
}
await record.load();
} catch (err) {
console.error('[GooglePlaces Facility] Address ORM write failed:', err);
}
} else {
try {
const textUpdate = {};
if (street) textUpdate.street = street;
if (unitNumber) textUpdate.street2 = unitNumber;
if (city) textUpdate.city = city;
if (postalCode) textUpdate.zip = postalCode;
await record.update(textUpdate);
if (countryId && globalOrm) {
const formEl = input.closest('.o_form_view') || input.closest('.o_content') || document.body;
const [countryData, stateData] = await Promise.all([
globalOrm.read('res.country', [countryId], ['display_name']),
stateId ? globalOrm.read('res.country.state', [stateId], ['display_name']) : Promise.resolve([]),
]);
await simulateMany2OneSelection(formEl, 'country_id', countryId, countryData[0]?.display_name || 'Canada');
await new Promise(r => setTimeout(r, 300));
if (stateId && stateData.length) {
await simulateMany2OneSelection(formEl, 'state_id', stateId, stateData[0]?.display_name || province);
}
}
} catch (err) {
console.error('[GooglePlaces Facility] Address autocomplete update failed:', err);
}
}
setTimeout(() => { _reattachFacilityAutocomplete(el, model); }, 400);
});
autocompleteInstances.set(input, autocomplete);
input.style.backgroundImage = 'url("data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 24 24\' fill=\'%234CAF50\'%3E%3Cpath d=\'M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z\'/%3E%3C/svg%3E")';
input.style.backgroundRepeat = 'no-repeat';
input.style.backgroundPosition = 'right 8px center';
input.style.backgroundSize = '20px';
input.style.paddingRight = '35px';
}
/**
* Re-attach facility autocomplete after OWL re-renders inputs.
*/
function _reattachFacilityAutocomplete(el, model) {
const streetSelectors = [
'div[name="street"] input',
'.o_field_widget[name="street"] input',
'[name="street"] input',
];
for (const sel of streetSelectors) {
const inp = el.querySelector(sel);
if (inp && !autocompleteInstances.has(inp)) {
_attachFacilityAddressAutocomplete(inp, el, model);
break;
}
}
}
/** REMOVED: LTC Facility autocomplete functions moved to fusion_ltc_management */
/**
* Patch FormController to add Google autocomplete for partner forms and dialog detection
@@ -1490,35 +1166,6 @@ patch(FormController.prototype, {
}
}
// LTC Facility form
if (this.props.resModel === 'fusion.ltc.facility') {
setTimeout(() => {
if (this.rootRef && this.rootRef.el) {
setupFacilityAutocomplete(this.rootRef.el, this.model, this.orm);
}
}, 800);
if (this.rootRef && this.rootRef.el) {
this._facilityAddrObserver = new MutationObserver((mutations) => {
const hasNewInputs = mutations.some(m =>
m.addedNodes.length > 0 &&
Array.from(m.addedNodes).some(n =>
n.nodeType === 1 && (n.tagName === 'INPUT' || n.querySelector?.('input'))
)
);
if (hasNewInputs) {
setTimeout(() => {
setupFacilityAutocomplete(this.rootRef.el, this.model, this.orm);
}, 300);
}
});
this._facilityAddrObserver.observe(this.rootRef.el, {
childList: true,
subtree: true,
});
}
}
// Simple address autocomplete: res.partner, res.users, res.config.settings
if (this.props.resModel === 'res.partner' || this.props.resModel === 'res.users' || this.props.resModel === 'res.config.settings') {
setTimeout(() => {
@@ -1556,9 +1203,6 @@ patch(FormController.prototype, {
if (this._taskAddressObserver) {
this._taskAddressObserver.disconnect();
}
if (this._facilityAddrObserver) {
this._facilityAddrObserver.disconnect();
}
if (this._simpleAddrObserver) {
this._simpleAddrObserver.disconnect();
}