// New Contract, Copy and Edit (agent/modals/contract/contract_form.php). // Picking a template fills the type, term, renewal, notice, cover and terms // from it. Picking the client fetches that client's contacts, its primary // contact picked - only New Contract opened without a client has a client to // pick; anywhere else the form comes with the client's contacts already. // The end date follows the start and the term - a term's length less a day - // until it is changed by hand, and the line under the term says when notice // is due. The server checks it all again. // // Wrapped in an IIFE because a modal can be opened, closed and opened again // in one page load, which re-runs this file. (function () { // 'Y-m-d' a term's length after $start, less a day - contractTermEnd() in functions/contract.php function termEnd(start, months) { const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(start || ''); if (!m || !(months > 0)) { return ''; } const year = Number(m[1]); const month = Number(m[2]) - 1 + Number(months); const day = Number(m[3]); // The next term starts on the same day of the month - or the last day of a month too short for it // (Jan 31 a month on is Feb 28) - and this one ends the day before const last = new Date(Date.UTC(year, month + 1, 0)).getUTCDate(); const end = new Date(Date.UTC(year, month, Math.min(day, last))); end.setUTCDate(end.getUTCDate() - 1); return end.toISOString().slice(0, 10); } function niceDate(ymd) { const d = new Date(ymd + 'T00:00:00Z'); return isNaN(d) ? ymd : d.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric', timeZone: 'UTC' }); } document.querySelectorAll('[data-contract-form]:not([data-wired])').forEach(function (form) { form.setAttribute('data-wired', '1'); const templateData = form.querySelector('[data-contract-templates]'); const templates = templateData ? JSON.parse(templateData.textContent) : {}; const template = form.querySelector('[data-contract-template]'); const client = form.querySelector('[data-contract-client]'); const contact = form.querySelector('[data-contract-contact]'); const start = form.querySelector('[data-contract-start]'); const term = form.querySelector('[data-contract-term]'); const end = form.querySelector('[data-contract-end]'); const noticeNote = form.querySelector('[data-contract-notice-note]'); const field = function (name) { return form.querySelector('[data-contract-field="' + name + '"]'); }; function drawNotice() { const days = Number(field('notice_days').value || 0); if (!end.value) { noticeNote.textContent = 'No end date - it runs until it is cancelled.'; return; } const notice = new Date(end.value + 'T00:00:00Z'); notice.setUTCDate(notice.getUTCDate() - days); const renews = field('auto_renew').checked; noticeNote.textContent = (renews ? 'Renews itself after ' : 'Ends on ') + niceDate(end.value) + (days ? ' - notice ' + (renews ? 'to stop it ' : '') + 'by ' + niceDate(notice.toISOString().slice(0, 10)) + '.' : '.'); } function followTerm() { if (!end.dataset.touched && start.value) { end.value = termEnd(start.value, Number(term.value)); } drawNotice(); } // The contacts of the client picked, primary first and picked. The list is rebuilt from what // ajax.php sends for that client - it used to hold every client's contacts with the others // hidden, which Safari ignores on an