(function () { 'use strict'; var App = { state: { currentStep: 1, totalSteps: 8, isBusy: false, leadId: null, leadRef: null, formCacheKey: 'agpal_registration_form', leadIdKey: 'agpal_registration_lead_id', leadRefKey: 'agpal_registration_lead_ref', practitionerRowCount: 0 }, ctx: window.portalContext || {}, /* Config */ config: { fee: parseFloat((window.portalContext || {}).fee) || 1200, ccSurchargeRate: parseFloat((window.portalContext || {}).ccSurchargeRate) || 0.01927, gstRate: parseFloat((window.portalContext || {}).gstRate) || 0.10, contactTypeId: (window.portalContext || {}).contactTypeId || '', businessId: (window.portalContext || {}).businessId || '', operationalTatDays: parseInt((window.portalContext || {}).operationalTatDays, 10) || 14, practiceTypeGuids: { gp: (window.portalContext || {}).practiceTypeGP || '', ahs: (window.portalContext || {}).practiceTypeAHS || '', mds: (window.portalContext || {}).practiceTypeMDS || '', aboriginal: (window.portalContext || {}).practiceTypeAboriginal || '' }, practitionerTypeMap: { registrar: 1, vocational: 2, other: 10 } }, STATE_MAP: { 'Australian Capital Territory': 107150000, 'New South Wales': 107150001, 'Northern Territory': 107150002, 'Queensland': 107150003, 'South Australia': 107150004, 'Tasmania': 107150005, 'Victoria': 107150006, 'Western Australia': 107150007, /* short-code fallbacks */ 'ACT': 107150000, 'NSW': 107150001, 'NT': 107150002, 'QLD': 107150003, 'SA': 107150004, 'TAS': 107150005, 'VIC': 107150006, 'WA': 107150007 }, mapState: function (v) { if (!v) return undefined; var n = this.STATE_MAP[v]; if (n !== undefined) return n; n = this.STATE_MAP[(v + '').trim()]; return n !== undefined ? n : undefined; }, /* Pending Registration — 14-day threshold Returns true when: - accred-status is "no" (new practice, not yet accredited) - commencement date is more than 14 days from today */ isPendingRegistration: function () { var accred = this.val('accred-status'); if (accred !== 'no') return false; var dateStr = this.val('commencement-date'); if (!dateStr) return false; var commenceDate = new Date(dateStr); if (isNaN(commenceDate.getTime())) return false; var today = new Date(); today.setHours(0, 0, 0, 0); var diffMs = commenceDate.getTime() - today.getTime(); var diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); return diffDays > this.config.operationalTatDays; }, dom: {}, /* Init */ init: function () { this.injectSpinnerOverlay(); this.cacheDom(); this.clearSession(); this.bindEvents(); this.handleConditionalUI(); this.updateFeeTable(); this.updateSubmitEnabled(); console.log('[AGPAL] App initialized'); }, cacheDom: function () { var ids = [ 'stepTitle', 'stepCount', 'progressFill', 'btn-submit', 'btn-submit-agpal', 'lead-status-msg', 'lead-card', 'lead-card-content', 'ref-number', 'toast', 'toast-msg', 'payment-method', 'practice-name', 'practice-email', 'group-accred', 'corporate-entity-name', 'practitioners', 'practitioner-dynamic-section', 'practitioner-over10-section', 'practitioner-rows-container', 'surcharge-row', 'gst-row', 'total-row' ]; for (var i = 0; i < ids.length; i++) { this.dom[ids[i]] = document.getElementById(ids[i]); } }, byId: function (id) { return document.getElementById(id); }, val: function (id) { var el = this.byId(id); return el && typeof el.value === 'string' ? el.value.trim() : ''; }, /* ── Toast ──────────────────────────────────────────────── */ showToast: function (message, isError) { if (!this.dom.toast || !this.dom['toast-msg']) return; this.dom['toast-msg'].textContent = message; this.dom.toast.classList.toggle('toast-error', !!isError); this.dom.toast.classList.add('show'); setTimeout(function () { if (App.dom.toast) App.dom.toast.classList.remove('show'); }, 4000); }, injectSpinnerOverlay: function () { if (document.getElementById('agpal-spinner-overlay')) return; var style = document.createElement('style'); style.textContent = '@keyframes agpal-spin{to{transform:rotate(360deg)}}' + '#agpal-spinner-overlay{display:none;position:fixed;inset:0;background:rgba(26,46,74,0.60);' + 'z-index:99999;align-items:center;justify-content:center;flex-direction:column;gap:18px;}'; document.head.appendChild(style); var overlay = document.createElement('div'); overlay.id = 'agpal-spinner-overlay'; overlay.setAttribute('aria-live', 'assertive'); overlay.innerHTML = '
' + '
Please wait…
'; document.body.appendChild(overlay); }, /* Busy */ setBusy: function (busy, buttonText) { this.state.isBusy = busy; var overlay = document.getElementById('agpal-spinner-overlay'); var msgEl = document.getElementById('agpal-spinner-msg'); if (overlay) overlay.style.display = busy ? 'flex' : 'none'; if (msgEl && buttonText) msgEl.textContent = buttonText; if (msgEl && !busy) msgEl.textContent = 'Please wait…'; var buttons = document.querySelectorAll('button'); buttons.forEach(function (btn) { if (busy) { btn.setAttribute('data-was-disabled', btn.disabled ? '1' : '0'); btn.disabled = true; } else { btn.disabled = btn.getAttribute('data-was-disabled') === '1'; } }); if (!busy && this.dom['btn-submit']) { this.dom['btn-submit'].disabled = !this.areDeclarationsChecked(); } }, /* CSRF */ waitForToken: function () { return new Promise(function (resolve, reject) { var cached = (window.portalContext || {}).csrfToken || (window.portalContext || {}).requestVerificationToken; if (cached && cached.length > 20) { resolve(cached); return; } var domEl = document.querySelector('input[name="__RequestVerificationToken"]'); if (domEl && domEl.value) { resolve(domEl.value); return; } var csrfEl = document.getElementById('csrf-token-value'); if (csrfEl && csrfEl.value && csrfEl.value.length > 20) { resolve(csrfEl.value); return; } var attempts = 0; var poll = setInterval(function () { attempts++; var pc = (window.portalContext || {}).csrfToken; if (pc && pc.length > 20) { clearInterval(poll); resolve(pc); return; } var el = document.getElementById('csrf-token-value'); if (el && el.value && el.value.length > 20) { clearInterval(poll); resolve(el.value); return; } if (attempts >= 30) { clearInterval(poll); fetch('/Account/Login', { credentials: 'same-origin', headers: { 'Accept': 'text/html' } }) .then(function (r) { return r.text(); }) .then(function (html) { var m = html.match(/name="__RequestVerificationToken"[^>]*value="([^"]+)"/); if (!m) m = html.match(/value="([^"]{40,})"[^>]*name="__RequestVerificationToken"/); if (m && m[1]) { resolve(m[1]); } else { reject(new Error('CSRF token not available.')); } }) .catch(function (e) { reject(new Error('CSRF: ' + e.message)); }); } }, 150); }); }, /* API helpers */ parseApiError: function (xhr) { try { if (xhr && xhr.responseJSON && xhr.responseJSON.error) return xhr.responseJSON.error.message || 'Unknown error'; } catch (e1) { } try { if (xhr && xhr.responseText) { var p = JSON.parse(xhr.responseText); return (p && p.error && p.error.message) || 'Unknown error'; } } catch (e2) { } return 'Unknown error'; }, extractGuidFromOdata: function (data) { var id = (data && data['@odata.id']) || ''; var m = id.match(/\(([0-9a-f-]{36})\)/i); return m ? m[1] : null; }, extractEntityId: function (data, response) { if (data && typeof data === 'object') { var fromBody = data.leadid || data.accountid || data.contactid || data.ongc_practitionerid || data.ongc_paymentevidenceid || this.extractGuidFromOdata(data); if (fromBody) return fromBody; } var hv = ''; if (response) { if (typeof response.headers === 'object' && typeof response.headers.get === 'function') { hv = response.headers.get('OData-EntityId') || ''; } if (!hv && typeof response.getResponseHeader === 'function') { hv = response.getResponseHeader('OData-EntityId') || response.getResponseHeader('entityid') || ''; } } if (hv) { var m = hv.match(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i); if (m) return m[0]; } return null; }, /* POST */ dataversePost: async function (entitySet, payload) { var url = '/_api/' + entitySet; if (window.webapi && typeof window.webapi.safeAjax === 'function') { return new Promise(function (resolve, reject) { window.webapi.safeAjax({ type: 'POST', url: url, contentType: 'application/json', data: JSON.stringify(payload), success: function (data, status, xhr) { resolve({ id: App.extractEntityId(data, xhr), data: data || {} }); }, error: function (xhr) { reject(new Error(App.parseApiError(xhr))); } }); }); } var token = await this.waitForToken(); var resp = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', '__RequestVerificationToken': token, 'OData-MaxVersion': '4.0', 'OData-Version': '4.0' }, body: JSON.stringify(payload) }); if (!resp.ok) { var em = 'API error ' + resp.status; try { var ej = await resp.json(); em = (ej && ej.error && ej.error.message) || em; } catch (e) { } throw new Error(em); } var data = {}; try { data = await resp.json(); } catch (e) { } return { id: this.extractEntityId(data, resp), data: data }; }, /* GET — used for existence lookups */ dataverseGet: async function (url) { try { var token = await this.waitForToken(); var resp = await fetch(url, { method: 'GET', headers: { '__RequestVerificationToken': token, 'OData-MaxVersion': '4.0', 'OData-Version': '4.0', 'Accept': 'application/json' } }); if (!resp.ok) return null; return await resp.json(); } catch (e) { return null; } }, /* Existence checks */ /* Contact: check emailaddress1 OR mobilephone OR telephone1 */ /* Contact lookup — OR across 2 fields: emailaddress1 eq, mobilephone eq */ findExistingContact: async function (email, mobile) { var filters = []; function esc(v) { return (v || '').replace(/'/g, "''"); } if (email) filters.push("emailaddress1 eq '" + esc(email) + "'"); if (mobile) filters.push("mobilephone eq '" + esc(mobile) + "'"); if (!filters.length) return null; var data = await this.dataverseGet( '/_api/contacts?$filter=' + encodeURIComponent(filters.join(' or ')) + '&$top=1&$select=contactid' ); var id = (data && data.value && data.value[0]) ? data.value[0].contactid : null; console.log('[AGPAL] findExistingContact:', id || 'not found'); return id; }, /* Account — OR across 5 fields: emailaddress1 eq, name eq, emailaddress2 eq, ongc_clientid contains, ongc_practiceabn eq */ findExistingAccount: async function (email, name, email2, clientId, abn) { var filters = []; function esc(v) { return (v || '').replace(/'/g, "''"); } if (email) filters.push("emailaddress1 eq '" + esc(email) + "'"); if (name) filters.push("name eq '" + esc(name) + "'"); if (clientId) filters.push("contains(ongc_clientid,'" + esc(clientId) + "')"); if (abn) filters.push("ongc_abn eq '" + esc(abn) + "'"); if (!filters.length) return null; var data = await this.dataverseGet( '/_api/accounts?$filter=' + encodeURIComponent(filters.join(' or ')) + '&$top=1&$select=accountid' ); var id = (data && data.value && data.value[0]) ? data.value[0].accountid : null; console.log('[AGPAL] findExistingAccount:', id || 'not found'); return id; }, /* Misc */ round2: function (n) { return Math.round(n * 100) / 100; }, clearSession: function () { try { sessionStorage.removeItem(this.state.formCacheKey); sessionStorage.removeItem(this.state.leadIdKey); sessionStorage.removeItem(this.state.leadRefKey); } catch (e) { } this.state.leadId = null; this.state.leadRef = null; }, saveState: function () { try { sessionStorage.setItem(this.state.formCacheKey, JSON.stringify(this.collectFormData())); if (this.state.leadId) sessionStorage.setItem(this.state.leadIdKey, this.state.leadId); if (this.state.leadRef) sessionStorage.setItem(this.state.leadRefKey, this.state.leadRef); } catch (e) { } }, resetForm: function () { try { sessionStorage.removeItem(this.state.formCacheKey); sessionStorage.removeItem(this.state.leadIdKey); sessionStorage.removeItem(this.state.leadRefKey); } catch (e) { } document.querySelectorAll('input, select, textarea').forEach(function (el) { if (el.type === 'checkbox' || el.type === 'radio') { el.checked = el.defaultChecked; } else { el.value = el.defaultValue || ''; } }); var c = document.getElementById('practitioner-rows-container'); if (c) c.innerHTML = ''; document.querySelectorAll('.error-msg').forEach(function (el) { el.classList.remove('visible'); }); if (this.dom['lead-card']) this.dom['lead-card'].style.display = 'none'; if (this.dom['lead-status-msg']) this.dom['lead-status-msg'].style.display = 'none'; if (this.dom['ref-number']) this.dom['ref-number'].textContent = '\u2014'; if (this.dom['btn-submit']) { this.dom['btn-submit'].disabled = true; this.dom['btn-submit'].textContent = 'Proceed to Payment'; } this.state.currentStep = 1; this.state.isBusy = false; this.state.leadId = null; this.state.leadRef = null; this.handleConditionalUI(); this.updateFeeTable(); this.goTo(1); }, calcFees: function (isCC) { var base = this.config.fee; var sur = isCC ? this.round2(base * this.config.ccSurchargeRate) : 0; var gst = this.round2((base + sur) * this.config.gstRate); return { base: base, surcharge: sur, gst: gst, total: this.round2(base + sur + gst) }; }, updateFeeTable: function () { var fees = this.calcFees(this.val('payment-method') === 'cc'); if (this.dom['surcharge-row']) this.dom['surcharge-row'].textContent = '$' + fees.surcharge.toFixed(2); if (this.dom['gst-row']) this.dom['gst-row'].textContent = '$' + fees.gst.toFixed(2); if (this.dom['total-row']) this.dom['total-row'].textContent = '$' + fees.total.toFixed(2); }, showError: function (id) { var el = this.byId(id); if (el) el.classList.add('visible'); }, hideError: function (id) { var el = this.byId(id); if (el) el.classList.remove('visible'); }, focusFirstError: function () { var err = document.querySelector('.error-msg.visible'); if (!err) return; var grp = err.closest('.field-group'); if (!grp) return; var inp = grp.querySelector('input, select, textarea'); if (inp) inp.focus(); }, /* Validation */ validateStep: function (step) { var ok = true; function req(id, errId) { if (!App.val(id)) { App.showError(errId); ok = false; } else { App.hideError(errId); } } if (step === 1) { req('accred-status', 'err-accred'); var accred = this.val('accred-status'); if (accred === 'yes-other') { req('other-org-name', 'err-other-org-name'); req('other-expiry-date', 'err-other-expiry-date'); } if (accred === 'no') { req('commencement-date', 'err-commencement-date'); } } if (step === 2) { req('practice-name', 'err-practice-name'); req('practice-abn', 'err-practice-abn'); req('practice-phone', 'err-practice-phone'); req('practice-email', 'err-practice-email'); req('standards', 'err-standards'); } if (step === 3) { req('contact-first', 'err-contact-name'); req('contact-last', 'err-contact-name'); req('contact-position', 'err-contact-position'); req('contact-phone', 'err-contact-phone'); req('contact-email', 'err-contact-email'); req('gp-first', 'err-gp-name'); req('gp-last', 'err-gp-name'); } if (step === 4) { req('addr1', 'err-address'); req('city', 'err-address'); req('state', 'err-address'); req('postcode', 'err-address'); var postalYes = document.querySelector('input[name="postal"][value="yes"]:checked'); if (postalYes) { req('postal-addr1', 'err-postal-address'); req('postal-city', 'err-postal-address'); req('postal-state', 'err-postal-address'); req('postal-postcode', 'err-postal-address'); } else { this.hideError('err-postal-address'); } } if (step === 5) { req('phn', 'err-phn'); req('practice-type', 'err-practice-type'); req('group-accred', 'err-group-accred'); var ga = this.val('group-accred'); if (ga === 'yes-corporate' || ga === 'yes-group') { req('corporate-entity-name', 'err-corporate-entity'); } else { this.hideError('err-corporate-entity'); } } if (step === 6) { var pCount = this.val('practitioners'); if (!pCount) { this.showError('err-practitioners'); ok = false; } else { this.hideError('err-practitioners'); } if (pCount && pCount !== 'over-10') { var n = parseInt(pCount, 10); for (var i = 1; i <= n; i++) { if (!this.val('prac-first-' + i) || !this.val('prac-last-' + i) || !this.val('prac-hours-' + i) || !this.val('prac-type-' + i)) { this.showError('err-practitioner-rows'); ok = false; break; } else { this.hideError('err-practitioner-rows'); } } } } /* Step 7 — payment method only required for non-pending registrations */ if (step === 7) { if (!this.isPendingRegistration()) { req('payment-method', 'err-payment'); } else { this.hideError('err-payment'); } } if (!ok) this.focusFirstError(); return ok; }, areDeclarationsChecked: function () { var basicChecked = !!( this.byId('cb-auth') && this.byId('cb-auth').checked && this.byId('cb-terms') && this.byId('cb-terms').checked && this.byId('cb-prof') && this.byId('cb-prof').checked ); if (!basicChecked) return false; /* For pending registrations the additional acknowledgment checkbox is mandatory */ if (this.isPendingRegistration()) { return !!(this.byId('cb-pending-ack') && this.byId('cb-pending-ack').checked); } return true; }, updateSubmitEnabled: function () { if (!this.dom['btn-submit']) return; /* Pending path: enabled purely by checkbox state — lead created on submit. Normal path: also requires leadId to exist (set after page 7 creates the lead). */ var declarationsOk = this.areDeclarationsChecked(); var canSubmit = this.isPendingRegistration() ? declarationsOk && !this.state.isBusy : declarationsOk && !this.state.isBusy && !!this.state.leadId; this.dom['btn-submit'].disabled = !canSubmit; }, /* Data collection */ collectPractitioners: function () { var result = []; var v = this.val('practitioners'); if (!v || v === 'over-10') return result; var count = parseInt(v, 10); for (var i = 1; i <= count; i++) { var firstName = this.val('prac-first-' + i); var lastName = this.val('prac-last-' + i); result.push({ title: this.val('prac-title-' + i), firstName: firstName, lastName: lastName, name: (firstName + ' ' + lastName).trim(), hours: this.val('prac-hours-' + i), type: this.val('prac-type-' + i) }); } return result; }, collectFormData: function () { var accredVal = this.val('accred-status'); var postalSameAsStreet = !document.querySelector('input[name="postal"][value="yes"]:checked'); return { accredStatus: accredVal, otherOrgName: accredVal === 'yes-other' ? this.val('other-org-name') : '', otherExpiry: accredVal === 'yes-other' ? this.val('other-expiry-date') : '', commenceDate: accredVal === 'no' ? this.val('commencement-date') : '', isPending: this.isPendingRegistration(), practiceName: this.val('practice-name'), practiceAbn: this.val('practice-abn'), practicePhone: this.val('practice-phone'), practiceFax: this.val('practice-fax'), practiceEmail: this.val('practice-email'), standards: this.val('standards'), contactPrefix: this.val('contact-prefix'), contactFirst: this.val('contact-first'), contactLast: this.val('contact-last'), contactPosition: this.val('contact-position'), contactPhone: this.val('contact-phone'), contactEmail: this.val('contact-email'), contactMethod: this.val('contact-method'), gpPrefix: this.val('gp-prefix'), gpFirst: this.val('gp-first'), gpLast: this.val('gp-last'), addr1: this.val('addr1'), addr2: this.val('addr2'), city: this.val('city'), state: this.val('state'), postcode: this.val('postcode'), postalSameAsStreet: postalSameAsStreet, postalAddr1: postalSameAsStreet ? '' : this.val('postal-addr1'), postalAddr2: postalSameAsStreet ? '' : this.val('postal-addr2'), postalCity: postalSameAsStreet ? '' : this.val('postal-city'), postalState: postalSameAsStreet ? '' : this.val('postal-state'), postalPostcode: postalSameAsStreet ? '' : this.val('postal-postcode'), phn: this.val('phn'), pipId: this.val('pip-id'), practiceType: this.val('practice-type'), groupAccred: this.val('group-accred'), corporateEntity: this.val('corporate-entity-name'), practitionerCount: this.val('practitioners'), practitioners: this.collectPractitioners(), paymentMethod: this.val('payment-method') }; }, /* Payloads */ buildLeadPayload: function (d) { var isPracticeAccredited = d.accredStatus === 'yes-agpal' ? 107150001 : d.accredStatus === 'yes-other' ? 107150002 : 107150000; var groupAccredValue = d.groupAccred === 'yes-corporate' ? 107150001 : d.groupAccred === 'yes-group' ? 107150002 : 107150000; var practiceTypeGuid = this.config.practiceTypeGuids[d.practiceType] || null; /* For pending registrations, flag the subject and status so can route the notification to adminbd@agpal.com.au (BCC marketingteam@agpal.com.au) and NOT progress into downstream systems Internally. */ var subjectLine = d.isPending ? 'AGPAL Registration (ON HOLD \u2013 Pending Commencement) \u2013 ' + d.contactFirst + ' ' + d.contactLast : 'AGPAL Registration \u2013 ' + d.contactFirst + ' ' + d.contactLast; var payload = { subject: subjectLine, firstname: d.contactFirst, lastname: d.contactLast || d.practiceName, jobtitle: d.contactPosition, emailaddress1: d.contactEmail || d.practiceEmail, telephone1: d.contactPhone, mobilephone: d.practicePhone, fax: d.practiceFax, companyname: d.practiceName, leadsourcecode: 8, address1_line1: d.addr1, address1_line2: d.addr2, address1_city: d.city, address1_stateorprovince: d.state, address1_postalcode: d.postcode, address1_country: 'Australia', ongc_state: App.mapState(d.state), /* integer choice */ ongc_country: 107150000, /* Australia */ address2_line1: d.postalAddr1, address2_line2: d.postalAddr2, address2_city: d.postalCity, address2_stateorprovince: d.postalState, address2_postalcode: d.postalPostcode, address2_country: d.postalAddr1 ? 'Australia' : '', ongc_enquirytype: 8, ongc_practicename: d.practiceName, ongc_practiceabn: d.practiceAbn, ongc_practicephone: d.practicePhone, ongc_pipid: d.pipId, ongc_corporateentity: d.corporateEntity, ongc_groupname: d.corporateEntity, ongc_postalsameasstreet: d.postalSameAsStreet, ongc_ispracticeaccredited: isPracticeAccredited, ongc_grouppracticesalreadyaccredited: groupAccredValue, ongc_accreditationexpirydate: d.otherExpiry || null, ongc_duedate: d.commenceDate || null, ongc_operationalcommencementdate: d.commenceDate || null, ongc_accreditationorganisationname: d.otherOrgName, statuscode: d.isPending ? 107150002 : undefined, ongc_webresponse: App.buildAgpalWebResponseHtml(d) }; if (practiceTypeGuid) { payload['ongc_PracticeType@odata.bind'] = '/ongc_practicetypes(' + practiceTypeGuid + ')'; } if (App.config.businessId) { payload['ongc_Business@odata.bind'] = '/ongc_businesses(' + App.config.businessId + ')'; } Object.keys(payload).forEach(function (k) { if (payload[k] === '' || payload[k] === null || payload[k] === undefined) delete payload[k]; }); return payload; }, buildAccountPayload: function (d) { var payload = { name: d.practiceName, telephone1: d.practicePhone, emailaddress1: d.practiceEmail, fax: d.practiceFax, address1_line1: d.addr1, address1_line2: d.addr2, address1_city: d.city, address1_stateorprovince: d.state, address1_postalcode: d.postcode, address1_country: 'Australia', ongc_state: App.mapState(d.state), ongc_country: 107150000, ongc_practiceabn: d.practiceAbn }; Object.keys(payload).forEach(function (k) { if (payload[k] === '' || payload[k] === null || payload[k] === undefined) delete payload[k]; }); return payload; }, buildContactPayload: function (d) { var payload = { firstname: d.contactFirst, lastname: d.contactLast, jobtitle: d.contactPosition, emailaddress1: d.contactEmail, telephone1: d.contactPhone, mobilephone: d.practicePhone, address1_line1: d.addr1, address1_line2: d.addr2, address1_city: d.city, address1_stateorprovince: d.state, address1_postalcode: d.postcode, address1_country: 'Australia', ongc_state: App.mapState(d.state), ongc_country: 107150000 }; if (this.state.leadId) { payload['originatingleadid@odata.bind'] = '/leads(' + this.state.leadId + ')'; } if (this.config.contactTypeId) { payload['ongc_ContactType@odata.bind'] = '/ongc_contacttypes(' + this.config.contactTypeId + ')'; } Object.keys(payload).forEach(function (k) { if (payload[k] === '' || payload[k] === null || payload[k] === undefined) delete payload[k]; }); return payload; }, /* Practitioners: ongc_Lead@odata.bind is the lookup the subgrid on the lead form. do not change to N:N intersect approach. */ buildPractitionerPayloads: function (d) { var rows = d.practitioners || []; return rows.map(function (prac) { var hours = parseFloat(prac.hours); var fullName = (prac.firstName + ' ' + prac.lastName).trim(); var payload = { ongc_title: prac.title || '', ongc_firstname: prac.firstName || '', ongc_lastname: prac.lastName || '', ongc_name: fullName || prac.name || '', ongc_type: App.config.practitionerTypeMap[prac.type] !== undefined ? App.config.practitionerTypeMap[prac.type] : 10, ongc_hoursperweek: isNaN(hours) ? null : hours }; /* No lookup bind here — N:N association created separately via $ref */ Object.keys(payload).forEach(function (k) { if (k === 'ongc_type') return; if (payload[k] === null || payload[k] === undefined || payload[k] === '') delete payload[k]; }); return payload; }); }, /* Lead card */ renderLeadCard: function (d) { if (!this.dom['lead-card-content']) return; function row(label, value) { return '
' + label + '' + (value || '\u2014') + '
'; } this.dom['lead-card-content'].innerHTML = row('Reference', App.state.leadRef || App.state.leadId || '\u2014') + row('Practice', d.practiceName) + row('ABN', d.practiceAbn) + row('Contact', (d.contactFirst + ' ' + d.contactLast).trim()) + row('Email', d.contactEmail || d.practiceEmail) + row('Phone', d.contactPhone) + row('Payment', 'Credit Card'); if (this.dom['lead-card']) this.dom['lead-card'].style.display = 'block'; if (this.dom['ref-number']) this.dom['ref-number'].textContent = App.state.leadRef || App.state.leadId || '\u2014'; }, /* Create lead + children */ /* Associate practitioner to lead via N:N $ref endpoint Relationship name: ongc_Lead_ongc_Practitioner_ongc_Practitioner Intersect table: ongc_lead_ongc_practitioner */ associatePractitionerToLead: async function (leadId, practitionerId) { var token = await this.waitForToken(); var baseUrl = window.location.origin; var resp = await fetch( '/_api/leads(' + leadId + ')/ongc_Lead_ongc_Practitioner_ongc_Practitioner/$ref', { method: 'POST', headers: { 'Content-Type': 'application/json', '__RequestVerificationToken': token, 'OData-MaxVersion': '4.0', 'OData-Version': '4.0' }, body: JSON.stringify({ '@odata.id': baseUrl + '/_api/ongc_practitioners(' + practitionerId + ')' }) } ); if (!resp.ok && resp.status !== 204) { var em = 'N:N associate HTTP ' + resp.status; try { var ej = await resp.json(); em = (ej && ej.error && ej.error.message) || em; } catch (e) { } throw new Error(em); } console.log('[AGPAL] N:N associated practitioner', practitionerId, '→ lead', leadId); }, /* Fetch ongc_leadrefid from the created lead record */ fetchLeadRef: async function (leadId) { try { var token = await this.waitForToken(); var r = await fetch( '/_api/leads(' + leadId + ')?$select=ongc_leadrefid,ongc_clientid', { method: 'GET', headers: { '__RequestVerificationToken': token, 'OData-MaxVersion': '4.0', 'OData-Version': '4.0', 'Accept': 'application/json' } } ); if (!r.ok) { console.warn('[AGPAL] fetchLeadRef HTTP', r.status); return null; } var d = await r.json(); var ref = (d && (d.ongc_leadrefid || d.ongc_clientid)) || null; console.log('[AGPAL] fetchLeadRef:', ref); return ref; } catch (e) { console.warn('[AGPAL] fetchLeadRef error:', e.message); return null; } }, createLeadAndChildren: async function () { var d = this.collectFormData(); this.setBusy(true, 'Submitting registration...10% \u2026'); this.saveState(); try { /* 1 — Lead */ var lead = await this.dataversePost('leads', this.buildLeadPayload(d)); this.state.leadId = lead.id; /* Fetch ongc_leadrefid — the human-readable reference number */ this.state.leadRef = await this.fetchLeadRef(this.state.leadId) || this.state.leadId; if (!this.state.leadId) throw new Error('Lead GUID not returned. Check API permissions.'); console.log('[AGPAL] Lead:', this.state.leadId); this.saveState(); /* 2 — Account: find existing or create */ this.setBusy(true, 'Submitting registration...30% \u2026'); var accountId = null; try { accountId = await this.findExistingAccount(d.practiceEmail, d.practiceName, null, d.corporateEntity, d.practiceAbn); if (accountId) { console.log('[AGPAL] Existing account:', accountId); /* Link existing account to lead */ var at = await this.waitForToken(); await fetch('/_api/leads(' + this.state.leadId + ')', { method: 'PATCH', headers: { 'Content-Type': 'application/json', '__RequestVerificationToken': at, 'OData-MaxVersion': '4.0', 'OData-Version': '4.0', 'If-Match': '*' }, body: JSON.stringify({ 'parentaccountid@odata.bind': '/accounts(' + accountId + ')' }) }); } else { var acct = await this.dataversePost('accounts', this.buildAccountPayload(d)); accountId = acct.id; console.log('[AGPAL] New account:', accountId); } } catch (e) { console.warn('[AGPAL] Account step failed:', e.message); } /* 3 — Contact: find existing or create */ this.setBusy(true, 'Submitting registration...55% \u2026'); try { var existingContactId = await this.findExistingContact(d.contactEmail, d.practicePhone); if (existingContactId) { console.log('[AGPAL] Existing contact:', existingContactId); /* Link existing contact to lead via both directions */ var ct = await this.waitForToken(); await fetch('/_api/leads(' + this.state.leadId + ')', { method: 'PATCH', headers: { 'Content-Type': 'application/json', '__RequestVerificationToken': ct, 'OData-MaxVersion': '4.0', 'OData-Version': '4.0', 'If-Match': '*' }, body: JSON.stringify({ 'parentcontactid@odata.bind': '/contacts(' + existingContactId + ')' }) }); /* Also set originatingleadid on the contact */ var ct2 = await this.waitForToken(); await fetch('/_api/contacts(' + existingContactId + ')', { method: 'PATCH', headers: { 'Content-Type': 'application/json', '__RequestVerificationToken': ct2, 'OData-MaxVersion': '4.0', 'OData-Version': '4.0', 'If-Match': '*' }, body: JSON.stringify({ 'originatingleadid@odata.bind': '/leads(' + this.state.leadId + ')' }) }); } else { var contactPayload = this.buildContactPayload(d); if (accountId) { contactPayload['parentcustomerid_account@odata.bind'] = '/accounts(' + accountId + ')'; } var cr = await this.dataversePost('contacts', contactPayload); console.log('[AGPAL] New contact:', cr.id); } } catch (e) { console.warn('[AGPAL] Contact step failed:', e.message); } /* 4 — Practitioners: create each, then N:N associate to lead via ongc_Lead_ongc_Practitioner_ongc_Practitioner relationship */ var pracPayloads = this.buildPractitionerPayloads(d); if (pracPayloads.length) { this.setBusy(true, 'Submitting registration...80% \u2026'); for (var pi = 0; pi < pracPayloads.length; pi++) { try { var pracResult = await App.dataversePost('ongc_practitioners', pracPayloads[pi]); var pracId = pracResult && pracResult.id; console.log('[AGPAL] Practitioner ' + (pi + 1) + ' created:', pracId); if (pracId && App.state.leadId) { await App.associatePractitionerToLead(App.state.leadId, pracId); } } catch (e) { console.warn('[AGPAL] Practitioner ' + (pi + 1) + ' failed:', e.message); } } } /* Done */ if (d.isPending) { /* Pending path: navigate to success page, render lead card there */ this.goToSuccess(true, d); this.showToast('Registration submitted successfully.'); } else { /* Normal path: show lead card on page 8, user proceeds to payment */ this.renderLeadCard(d); if (this.dom['lead-status-msg']) { this.dom['lead-status-msg'].style.display = 'block'; this.dom['lead-status-msg'].className = 'lead-status-ready'; this.dom['lead-status-msg'].textContent = 'Registration record submitted. You may now proceed to payment.'; } this.goTo(8); /* Re-apply pending UI now that we are on page 8 */ this.handleConditionalUI(); this.showToast('Registration submitted successfully.'); } } catch (err) { console.error('[AGPAL] Lead creation failed', err); if (this.dom['lead-status-msg']) { this.dom['lead-status-msg'].style.display = 'block'; this.dom['lead-status-msg'].className = 'lead-status-error'; this.dom['lead-status-msg'].textContent = 'Registration could not be submitted. Please try again.'; } this.showToast(err.message || 'Registration creation failed.', true); } finally { this.setBusy(false); this.updateSubmitEnabled(); } }, /* Payment evidence */ createPaymentEvidence: async function (stripeResult) { stripeResult = stripeResult || {}; var isCC = this.val('payment-method') === 'cc'; var fees = this.calcFees(isCC); var now = new Date().toISOString(); var payload = { ongc_payername: stripeResult.fullName || (this.val('contact-first') + ' ' + this.val('contact-last')).trim(), ongc_paymentamount: fees.base, ongc_surchargefee: fees.surcharge, ongc_gst: fees.gst, ongc_totalamount: fees.total, ongc_paymentreference: stripeResult.paymentIntentId || stripeResult.chargeId || '', ongc_paymentstatus: stripeResult.status || (isCC ? 'succeeded' : 'pending-eft'), ongc_paymentdate: stripeResult.paymentDate || now, ongc_responsemessage: JSON.stringify(stripeResult.rawResponse || stripeResult || {}, null, 2) }; if (this.state.leadId) { payload['ongc_Lead@odata.bind'] = '/leads(' + this.state.leadId + ')'; } Object.keys(payload).forEach(function (k) { if (payload[k] === '' || payload[k] === null || payload[k] === undefined) delete payload[k]; }); return this.dataversePost('ongc_paymentevidences', payload); }, /* Submit */ handleSubmit: async function () { if (!this.areDeclarationsChecked()) { this.showError('err-submit'); this.focusFirstError(); return; } this.hideError('err-submit'); this.hideError('err-pending-ack'); /* Pending path: create lead record NOW (on page 8 "Confirm and Submit"). createLeadAndChildren detects isPending and routes to goToSuccess(true). */ if (this.isPendingRegistration()) { await this.createLeadAndChildren(); return; } /* Normal path: lead already created on page 7, proceed to Stripe payment */ this.setBusy(true, 'Processing\u2026'); this.saveState(); try { if (!this.state.leadId) { throw new Error('Registration record not found. Please go back and recreate the registration.'); } await this.initiateStripePayment(); } catch (err) { console.error('[AGPAL] Submit failed', err); this.showToast(err.message || 'Submission failed.', true); this.setBusy(false); this.updateSubmitEnabled(); } }, initiateStripePayment: function () { var fees = this.calcFees(true); var params = new URLSearchParams({ leadId: this.state.leadId || '', leadRef: this.state.leadRef || '', amount: fees.total.toFixed(2), base: fees.base.toFixed(2), surcharge: fees.surcharge.toFixed(2), gst: fees.gst.toFixed(2), email: this.val('contact-email') || this.val('practice-email'), name: (this.val('contact-first') + ' ' + this.val('contact-last')).trim(), ref: 'AGPAL Registration \u2013 ' + this.val('practice-name'), returnUrl: window.location.href.split('?')[0] }); window.location.href = '/agpal-stripe-checkout?' + params.toString(); }, /* Navigation */ goTo: function (step) { document.querySelectorAll('.step-page').forEach(function (p) { p.classList.remove('active'); }); var page = this.byId('page-' + step); if (page) page.classList.add('active'); document.querySelectorAll('.step-dot').forEach(function (d, i) { d.classList.toggle('active', i + 1 === step); d.classList.toggle('completed', i + 1 < step); }); var titles = [ 'Step 1: Accreditation Status', 'Step 2: Practice Information', 'Step 3: Practice Contacts', 'Step 4: Practice Address', 'Step 5: Further Practice Details', 'Step 6: Practitioner Details', 'Step 7: Self Assessment Fee', 'Step 8: Authorisation & Submission' ]; if (step <= this.state.totalSteps) { if (this.dom.stepTitle) this.dom.stepTitle.textContent = titles[step - 1]; if (this.dom.stepCount) this.dom.stepCount.textContent = 'Page ' + step + ' of ' + this.state.totalSteps; if (this.dom.progressFill) this.dom.progressFill.style.width = ((step / this.state.totalSteps) * 100) + '%'; } this.state.currentStep = step; window.scrollTo(0, 0); }, goToSuccess: function (isPending, d) { document.querySelectorAll('.step-page').forEach(function (p) { p.classList.remove('active'); }); var s = this.byId('page-success'); if (s) s.classList.add('active'); /* Show the appropriate success message depending on registration type */ var msgNormal = this.byId('success-msg-normal'); var msgPending = this.byId('success-msg-pending'); if (msgNormal) msgNormal.style.display = isPending ? 'none' : 'block'; if (msgPending) msgPending.style.display = isPending ? 'block' : 'none'; /* Reference number */ if (this.dom['ref-number']) { this.dom['ref-number'].textContent = App.state.leadRef || App.state.leadId || '\u2014'; } /* Pending path: render lead status + card inside the success page */ if (isPending && d) { var statusEl = this.byId('pending-lead-status-msg'); if (statusEl) { statusEl.textContent = 'Your details have been recorded. A member of the AGPAL Client Liaison team will contact you closer to your opening date to progress your registration.'; statusEl.style.display = 'block'; } this.renderLeadCardPending(d); } this.setBusy(false); window.scrollTo(0, 0); }, /* Render lead card on the success page */ renderLeadCardPending: function (d) { var container = this.byId('pending-lead-card-content'); if (!container) return; function row(label, value) { return '
' + label + '' + (value || '\u2014') + '
'; } container.innerHTML = row('REFERENCE', App.state.leadRef || App.state.leadId) + row('PRACTICE', d.practiceName) + row('ABN', d.practiceAbn) + row('CONTACT', (d.contactFirst + ' ' + d.contactLast).trim()) + row('EMAIL', d.contactEmail) + row('PHONE', d.contactPhone); var card = this.byId('pending-lead-card'); if (card) card.style.display = 'block'; }, /* Conditional UI */ handleConditionalUI: function () { var accred = this.val('accred-status'); var yesAgpal = this.byId('group-yes-agpal'); var yesOther = this.byId('group-yes-other'); var noGroup = this.byId('group-no'); var btnNext1 = this.byId('btn-next-1'); var btnSubmitAg = this.byId('btn-submit-agpal'); if (yesAgpal) yesAgpal.style.display = accred === 'yes-agpal' ? 'block' : 'none'; if (yesOther) yesOther.style.display = accred === 'yes-other' ? 'block' : 'none'; if (noGroup) noGroup.style.display = accred === 'no' ? 'block' : 'none'; if (btnNext1) btnNext1.style.display = accred === 'yes-agpal' ? 'none' : 'inline-block'; if (btnSubmitAg) btnSubmitAg.style.display = accred === 'yes-agpal' ? 'inline-block' : 'none'; var ga = this.val('group-accred'); var corpWrap = this.byId('group-corporate-entity'); if (corpWrap) corpWrap.style.display = (ga === 'yes-corporate' || ga === 'yes-group') ? 'block' : 'none'; var postalYes = document.querySelector('input[name="postal"][value="yes"]:checked'); var postalWrap = this.byId('postal-address-group'); if (postalWrap) postalWrap.style.display = postalYes ? 'block' : 'none'; var v = this.val('practitioners'); if (this.dom['practitioner-dynamic-section']) this.dom['practitioner-dynamic-section'].style.display = 'none'; if (this.dom['practitioner-over10-section']) this.dom['practitioner-over10-section'].style.display = 'none'; if (v === 'over-10') { if (this.dom['practitioner-over10-section']) this.dom['practitioner-over10-section'].style.display = 'block'; this.state.practitionerRowCount = 0; if (this.dom['practitioner-rows-container']) this.dom['practitioner-rows-container'].innerHTML = ''; } else if (v) { var count = parseInt(v, 10); if (!isNaN(count) && count >= 1 && count <= 10) { this.buildPractitionerRows(count); if (this.dom['practitioner-dynamic-section']) this.dom['practitioner-dynamic-section'].style.display = 'block'; } } /* Pending Registration UI — driven by isPendingRegistration() */ var isPending = this.isPendingRegistration(); /* Page 1 — pending notice */ var pendingNoticeP1 = this.byId('pending-notice-p1'); if (pendingNoticeP1) pendingNoticeP1.style.display = isPending ? 'block' : 'none'; /* Page 7 — fee info box (two versions) */ var feeInfoNormal = this.byId('fee-info-normal'); var feeInfoPending = this.byId('fee-info-pending'); if (feeInfoNormal) feeInfoNormal.style.display = isPending ? 'none' : 'block'; if (feeInfoPending) feeInfoPending.style.display = isPending ? 'block' : 'none'; /* Page 7 — payment method dropdown (hidden for pending) */ var paymentMethodGroup = this.byId('payment-method-group'); if (paymentMethodGroup) paymentMethodGroup.style.display = isPending ? 'none' : 'block'; /* Page 7 — "What happens next?" box (two versions) */ var nextStepsNormal = this.byId('next-steps-normal'); var nextStepsPending = this.byId('next-steps-pending'); if (nextStepsNormal) nextStepsNormal.style.display = isPending ? 'none' : 'block'; if (nextStepsPending) nextStepsPending.style.display = isPending ? 'block' : 'none'; /* Page 7 — Next button label */ var btnNext7 = this.byId('btn-next-7'); if (btnNext7) btnNext7.textContent = isPending ? 'Confirm and Next' : 'Next'; /* Page 8 — "Save and return" notice (only relevant if payment is happening) */ var saveReturnNotice = this.byId('save-return-notice'); if (saveReturnNotice) saveReturnNotice.style.display = isPending ? 'none' : 'block'; /* Page 8 — pending notice at top */ var pendingNoticeP8 = this.byId('pending-notice-p8'); if (pendingNoticeP8) pendingNoticeP8.style.display = isPending ? 'block' : 'none'; /* Page 8 — pending acknowledgment checkbox group */ var pendingAckGroup = this.byId('pending-ack-group'); if (pendingAckGroup) pendingAckGroup.style.display = isPending ? 'block' : 'none'; /* Page 8 — fee table (hidden for pending — no payment being made) */ var feeTableWrap = this.byId('fee-table-wrap'); if (feeTableWrap) feeTableWrap.style.display = isPending ? 'none' : 'block'; /* Page 8 — lead-card/status-msg wrap: Normal path → visible (populated after page 7 Next creates the lead). Pending path → hidden here; shown on the success page instead after Confirm and Submit. */ var p8LeadWrap = this.byId('p8-lead-wrap'); if (p8LeadWrap) p8LeadWrap.style.display = isPending ? 'none' : 'block'; /* Page 8 — submit button label and enabled state for pending Pending: enabled by declarations alone (no lead pre-creation required). Normal: enabled only after lead GUID confirmed (updateSubmitEnabled handles this). */ var submitBtn = this.dom['btn-submit']; if (submitBtn) { submitBtn.textContent = isPending ? 'Confirm and Submit' : 'Proceed to Payment'; if (isPending) { submitBtn.disabled = !this.areDeclarationsChecked(); } } }, buildPractitionerRows: function (count) { if (!this.dom['practitioner-rows-container']) return; if (this.state.practitionerRowCount === count) return; this.state.practitionerRowCount = count; this.dom['practitioner-rows-container'].innerHTML = ''; for (var i = 1; i <= count; i++) { var row = document.createElement('div'); row.className = 'practitioner-row'; row.style.cssText = 'display:grid;grid-template-columns:0.6fr 1fr 1fr 1fr 0.7fr;gap:12px;margin-bottom:20px;align-items:end;'; row.innerHTML = '
' + '' + '' + '
' + '
' + '' + '' + '
' + '
' + '' + '' + '
' + '
' + '' + '' + '
' + '
' + '' + '' + '
'; this.dom['practitioner-rows-container'].appendChild(row); } }, /* Event binding */ bindEvents: function () { var self = this; ['accred-status', 'group-accred', 'practitioners', 'payment-method'].forEach(function (id) { var el = self.byId(id); if (!el) return; el.addEventListener('change', function () { self.handleConditionalUI(); self.updateFeeTable(); self.saveState(); }); }); /* Commencement date drives the pending-registration logic — re-evaluate conditional UI whenever it changes */ var commenceDateEl = self.byId('commencement-date'); if (commenceDateEl) { commenceDateEl.addEventListener('change', function () { self.handleConditionalUI(); self.updateFeeTable(); self.saveState(); }); } document.querySelectorAll('input[name="postal"]').forEach(function (el) { el.addEventListener('change', function () { self.handleConditionalUI(); self.saveState(); }); }); [1, 2, 3, 4, 5, 6].forEach(function (step) { var btn = self.byId('btn-next-' + step); if (!btn) return; btn.addEventListener('click', function () { if (self.validateStep(step)) { self.saveState(); self.goTo(step + 1); } }); }); var btnNext7 = this.byId('btn-next-7'); if (btnNext7) { btnNext7.addEventListener('click', function () { if (!self.validateStep(7)) return; if (self.isPendingRegistration()) { /* Pending path: page 7 just navigates to page 8. Lead is created later on page 8 "Confirm and Submit". */ self.saveState(); self.goTo(8); self.handleConditionalUI(); } else { /* Normal path: create lead + children, then navigate to page 8 */ self.createLeadAndChildren(); } }); } [2, 3, 4, 5, 6, 7, 8].forEach(function (step) { var btn = self.byId('btn-prev-' + step); if (!btn) return; btn.addEventListener('click', function () { if (!self.state.isBusy) self.goTo(step - 1); }); }); var submit = this.byId('btn-submit'); if (submit) submit.addEventListener('click', function () { self.handleSubmit(); }); /* Close button on success page — closes tab or falls back to portal home */ var btnClose = this.byId('btn-close-success'); if (btnClose) { btnClose.addEventListener('click', function () { try { window.close(); /* window.close() silently fails if page wasn't opened by script; fall back to home after a short delay */ setTimeout(function () { window.location.href = window.location.origin + '/'; }, 300); } catch (e) { window.location.href = window.location.origin + '/'; } }); } ['cb-auth', 'cb-terms', 'cb-prof', 'cb-pending-ack'].forEach(function (id) { var cb = self.byId(id); if (!cb) return; cb.addEventListener('change', function () { self.updateSubmitEnabled(); self.saveState(); }); }); var btnSubmitAgpal = this.byId('btn-submit-agpal'); if (btnSubmitAgpal) { btnSubmitAgpal.addEventListener('click', function () { self.showToast('Thank you. Please call 1300 362 111 to re-accredit.'); }); } document.querySelectorAll('input, select, textarea').forEach(function (el) { el.addEventListener('change', function () { self.saveState(); }); }); }, /* ── Web response HTML ──────────────────────────────────── */ buildAgpalWebResponseHtml: function (d) { var now = new Date().toLocaleString('en-AU', { timeZone: 'Australia/Sydney', dateStyle: 'medium', timeStyle: 'short' }); function row(label, value) { return '' + '' + (label || '') + '' + '' + (value || '\u2014') + '' + ''; } function section(title) { return '' + title + ''; } var html = '
' + '' + '' + ''; if (d.isPending) { html += ''; } html += section('1. Accreditation Status'); var aL = { 'yes-agpal': 'Yes \u2014 currently accredited with AGPAL', 'yes-other': 'Yes \u2014 accredited with another organisation', 'no': 'No \u2014 not currently accredited' }; html += row('Status', aL[d.accredStatus] || d.accredStatus); if (d.otherOrgName) html += row('Other Organisation', d.otherOrgName); if (d.otherExpiry) html += row('Expiry Date', d.otherExpiry); if (d.commenceDate) html += row('Desired Commencement Date', (function (iso) { var p = iso.split('-'); return p.length === 3 ? p[2] + '-' + p[1] + '-' + p[0] : iso; })(d.commenceDate)); if (d.standards) html += row('Standards', d.standards); html += section('2. Practice Information'); html += row('Practice Name', d.practiceName); html += row('ABN', d.practiceAbn); html += row('Phone', d.practicePhone); html += row('Fax', d.practiceFax); html += row('Email', d.practiceEmail); html += section('3. Practice Contacts'); html += row('Preferred Contact', [d.contactPrefix, d.contactFirst, d.contactLast].filter(Boolean).join(' ')); html += row('Position', d.contactPosition); html += row('Contact Phone', d.contactPhone); html += row('Contact Email', d.contactEmail); html += row('Preferred Method', d.contactMethod); html += row('Principal GP', [d.gpPrefix, d.gpFirst, d.gpLast].filter(Boolean).join(' ')); html += section('4. Practice Address'); html += row('Street Address', [d.addr1, d.addr2, d.city, d.state, d.postcode, 'Australia'].filter(Boolean).join(', ')); if (!d.postalSameAsStreet) { html += row('Postal Address', [d.postalAddr1, d.postalAddr2, d.postalCity, d.postalState, d.postalPostcode, 'Australia'].filter(Boolean).join(', ')); } else { html += row('Postal Address', 'Same as street address'); } html += section('5. Further Practice Details'); html += row('PHN', d.phn); html += row('PIP ID', d.pipId); var ptL = { gp: 'General Practice', ahs: 'After Hours Service', mds: 'Medical Deputising Service', aboriginal: 'Aboriginal Health Service' }; html += row('Practice Type', ptL[d.practiceType] || d.practiceType); var gaL = { 'yes-corporate': 'Yes \u2014 Corporate Entity', 'yes-group': 'Yes \u2014 Group', 'no': 'No' }; html += row('Group Practices Accredited', gaL[d.groupAccred] || d.groupAccred); if (d.corporateEntity) html += row('Corporate / Group Name', d.corporateEntity); html += section('6. Practitioners'); html += row('Count', d.practitionerCount === 'over-10' ? 'Over 10' : (d.practitionerCount || '\u2014')); if (d.practitioners && d.practitioners.length) { d.practitioners.forEach(function (p, i) { var displayName = [p.title, p.firstName, p.lastName].filter(Boolean).join(' ') || p.name || '\u2014'; html += ''; if (p.title) html += row('Title', p.title); html += row('First Name', p.firstName || '\u2014'); html += row('Last Name', p.lastName || '\u2014'); html += row('Hours / Week', p.hours || '\u2014'); var tL = { vocational: 'Vocational GP', registrar: 'Registrar', other: 'Other' }; html += row('Registrar/VR', tL[p.type] || p.type || '\u2014'); if (i < d.practitioners.length - 1) { html += ''; } }); } html += section('7. Self Assessment Fee'); html += row('Payment Method', d.paymentMethod === 'cc' ? 'Credit Card' : (d.paymentMethod || '\u2014')); html += '
AGPAL Registration — Accreditation Scope
Submitted: ' + now + '
' + '⚠ PENDING REGISTRATION — Practice not yet operational. DO NOT progress into Site Admin. ' + 'Notify adminbd@agpal.com.au (BCC marketingteam@agpal.com.au). Hold until practice opens.' + '
' + '👤 Practitioner ' + (i + 1) + (displayName !== '\u2014' ? ' \u2014 ' + displayName : '') + '
'; return html; } }; /* end */ /* ================================================================ Append payment details to ongc_webresponse after Stripe return. Assigned OUTSIDE App object as a regular function property to avoid object-literal syntax constraints. ================================================================ */ App.appendPaymentToWebResponse = async function (stripeData, fees) { if (!App.state.leadId) return; stripeData = stripeData || {}; fees = fees || {}; try { /* 1 — Fetch current ongc_webresponse */ var token = await App.waitForToken(); var fetchResp = await fetch( '/_api/leads(' + App.state.leadId + ')?$select=ongc_webresponse', { headers: { '__RequestVerificationToken': token, 'OData-MaxVersion': '4.0', 'OData-Version': '4.0', 'Accept': 'application/json' } } ); var currentHtml = ''; if (fetchResp.ok) { var ld = await fetchResp.json(); currentHtml = (ld && ld.ongc_webresponse) || ''; } /* 2 — Build payment section rows */ var now = new Date().toLocaleString('en-AU', { timeZone: 'Australia/Sydney', dateStyle: 'medium', timeStyle: 'short' }); function prow(label, value) { return '' + '' + label + '' + '' + (value || '\u2014') + '' + ''; } var sL = { succeeded: 'Succeeded', 'pending-cc': 'Pending (Credit Card)', 'pending-eft': 'Pending (EFT)' }; var paymentSection = '8. Payment Details' + prow('Payment Date', now) + prow('Payment Method', 'Credit Card') + prow('Status', sL[stripeData.status] || stripeData.status || 'Succeeded') + prow('Base Amount', fees.base ? '$' + Number(fees.base).toFixed(2) : '\u2014') + prow('CC Surcharge', fees.surcharge ? '$' + Number(fees.surcharge).toFixed(2) : '\u2014') + prow('GST', fees.gst ? '$' + Number(fees.gst).toFixed(2) : '\u2014') + prow('Total Paid', fees.total ? '$' + Number(fees.total).toFixed(2) : '\u2014') + prow('Payment Reference', stripeData.paymentIntentId || stripeData.chargeId || stripeData.paymentId || '\u2014') + prow('Stripe Message', stripeData.message || '\u2014'); /* 3 — Inject before */ var updatedHtml = (currentHtml && currentHtml.indexOf('') !== -1) ? currentHtml.replace('', paymentSection + '') : (currentHtml || '') + '' + paymentSection + '
'; /* 4 — PATCH lead */ token = await App.waitForToken(); var pr = await fetch('/_api/leads(' + App.state.leadId + ')', { method: 'PATCH', headers: { 'Content-Type': 'application/json', '__RequestVerificationToken': token, 'OData-MaxVersion': '4.0', 'OData-Version': '4.0', 'If-Match': '*' }, body: JSON.stringify({ ongc_webresponse: updatedHtml }) }); if (pr.ok || pr.status === 204) { console.log('[AGPAL] Payment section appended to ongc_webresponse'); } else { console.warn('[AGPAL] appendPaymentToWebResponse PATCH failed HTTP', pr.status); } } catch (e) { console.warn('[AGPAL] appendPaymentToWebResponse error:', e.message); } }; /* ================================================================ Stripe return handler ================================================================ */ window.agpalHandleStripeReturn = async function (stripeData) { stripeData = stripeData || {}; App.state.leadId = stripeData.leadId || sessionStorage.getItem(App.state.leadIdKey) || App.state.leadId; App.state.leadRef = stripeData.leadRef || sessionStorage.getItem(App.state.leadRefKey) || App.state.leadRef; try { App.setBusy(true, 'Finalising payment record\u2026'); await App.createPaymentEvidence(stripeData); App.setBusy(true, 'Updating registration record\u2026'); await App.appendPaymentToWebResponse(stripeData, App.calcFees(true)); App.goToSuccess(false); App.showToast('Payment confirmed and registration completed.'); } catch (err) { console.error('[AGPAL] Stripe return failed', err); App.showToast(err.message || 'Unable to finalise payment evidence.', true); App.setBusy(false); App.updateSubmitEnabled(); } }; document.addEventListener('DOMContentLoaded', function () { App.init(); }); })();