mirror of
https://github.com/itflow-org/itflow
synced 2026-11-11 07:01:16 +00:00
152 lines
6.6 KiB
JavaScript
152 lines
6.6 KiB
JavaScript
// 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 shows only that client's contacts. 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();
|
|
}
|
|
|
|
// Only the client's own contacts
|
|
function filterContacts() {
|
|
if (!client || !contact) {
|
|
return;
|
|
}
|
|
const id = client.value;
|
|
Array.prototype.forEach.call(contact.options, function (option) {
|
|
if (option.value === '0') {
|
|
return;
|
|
}
|
|
const mine = option.dataset.client === id;
|
|
option.hidden = !mine;
|
|
if (!mine && option.selected) {
|
|
contact.value = '0';
|
|
}
|
|
});
|
|
}
|
|
|
|
function setValue(name, value) {
|
|
const input = field(name);
|
|
if (!input) {
|
|
return;
|
|
}
|
|
if (input.type === 'checkbox') {
|
|
input.checked = Boolean(Number(value));
|
|
} else if (input.tagName === 'TEXTAREA' && window.tinymce) {
|
|
const editor = tinymce.get().filter(function (ed) { return ed.getElement() === input; })[0];
|
|
if (editor) {
|
|
editor.setContent(value || '');
|
|
}
|
|
input.value = value || '';
|
|
} else {
|
|
input.value = value === null || value === undefined ? '' : value;
|
|
}
|
|
input.dispatchEvent(new Event('change', { bubbles: true }));
|
|
}
|
|
|
|
if (template) {
|
|
template.addEventListener('change', function () {
|
|
const t = templates[template.value];
|
|
if (!t) {
|
|
return;
|
|
}
|
|
// The template's name only when nothing is written yet
|
|
if (!field('name').value.trim()) {
|
|
setValue('name', t.name);
|
|
}
|
|
['type', 'auto_renew', 'notice_days', 'rate_standard', 'rate_after_hours', 'net_terms', 'support_hours', 'details',
|
|
'sla_low', 'sla_medium', 'sla_high', 'sla_urgent', 'monthly_hours', 'hours_alert'].forEach(function (name) {
|
|
if (name in t) {
|
|
setValue(name, t[name]);
|
|
}
|
|
});
|
|
// A term the list doesn't have is added to it
|
|
if (!Array.prototype.some.call(term.options, function (o) { return Number(o.value) === t.term_months; })) {
|
|
term.add(new Option(t.term_months + ' months', t.term_months));
|
|
}
|
|
term.value = String(t.term_months);
|
|
followTerm();
|
|
});
|
|
}
|
|
if (client && client.tagName === 'SELECT') {
|
|
client.addEventListener('change', filterContacts);
|
|
// Select2 changes the select without a native event
|
|
if (window.jQuery) {
|
|
window.jQuery(client).on('change', filterContacts);
|
|
}
|
|
filterContacts();
|
|
}
|
|
start.addEventListener('change', followTerm);
|
|
term.addEventListener('change', followTerm);
|
|
end.addEventListener('input', function () {
|
|
end.dataset.touched = '1';
|
|
drawNotice();
|
|
});
|
|
field('notice_days').addEventListener('input', drawNotice);
|
|
field('auto_renew').addEventListener('change', drawNotice);
|
|
|
|
drawNotice();
|
|
});
|
|
})();
|