diff --git a/index.html b/index.html
index adc319f..c9316b5 100644
--- a/index.html
+++ b/index.html
@@ -122,7 +122,6 @@
| Артист |
Артист (Z) |
- Организатор (Rom) |
Постановщик (Rop) |
Техник (Rot) |
Комиссионер (Rok) |
@@ -132,7 +131,7 @@
- | Нет добавленных артистов |
+ Нет добавленных артистов |
diff --git a/js/app.js b/js/app.js
index c7834ce..42bfe81 100644
--- a/js/app.js
+++ b/js/app.js
@@ -31,9 +31,12 @@
s: document.getElementById('summary-s'),
};
- let artists = []; // [{ name, roles: { artist, organizer, director, technician, commissioner } }]
+ let artists = []; // [{ name, roles: { artist, director, technician, commissioner } }]
let allArtistsList = [];
+ // Track which fields were auto-calculated so we can recalc them when roster changes
+ const autoCalculated = new Set();
+
async function loadArtists() {
try {
allArtistsList = window.__ARTISTS__ || [];
@@ -68,10 +71,9 @@
}
function countRoles() {
- const counts = { artist: 0, organizer: 0, director: 0, technician: 0, commissioner: 0 };
+ const counts = { artist: 0, director: 0, technician: 0, commissioner: 0 };
for (const a of artists) {
if (a.roles.artist) counts.artist++;
- if (a.roles.organizer) counts.organizer++;
if (a.roles.director) counts.director++;
if (a.roles.technician) counts.technician++;
if (a.roles.commissioner) counts.commissioner++;
@@ -79,11 +81,16 @@
return counts;
}
- function calculate() {
+ function getMaterials(vals) {
+ return (vals.pz || 0) + (vals.tz || 0) + (vals.lz || 0) * (vals.nl || 0);
+ }
+
+ function calculate(opts = {}) {
+ const { fromRosterChange } = opts;
messageEl.textContent = '';
messageEl.className = 'input-info mt-4';
- const vals = {
+ let vals = {
s: getValue(inputs.s),
pz: getValue(inputs.pz),
tz: getValue(inputs.tz),
@@ -96,10 +103,10 @@
const filled = editableKeys.filter(k => vals[k] !== null);
const missing = editableKeys.filter(k => vals[k] === null);
- // Auto-calculate one missing variable
+ // Auto-calculate one missing variable (initial user input phase)
if (missing.length === 1 && filled.length === 5) {
const missKey = missing[0];
- const materials = (vals.pz || 0) + (vals.tz || 0) + (vals.lz || 0) * (vals.nl || 0);
+ const materials = getMaterials(vals);
const counts = countRoles();
const na = counts.artist;
@@ -137,7 +144,6 @@
vals.nl = ((vals.s || 0) - (vals.pz || 0) - (vals.tz || 0) - (na * (vals.z || 0)) / 0.70) / (vals.lz || 0);
}
- // Validate non-negative
if (vals[missKey] < 0) {
showMessage(`Расчёт дал отрицательное значение для ${missKey.toUpperCase()}. Проверьте введённые данные.`, 'error');
updateComputed(vals.s || 0, materials, 0, 0, 0, 0);
@@ -145,13 +151,47 @@
}
setValue(inputs[missKey], vals[missKey]);
+ autoCalculated.add(missKey);
+ }
+
+ // When roster changes and all fields are filled, recalc auto-calculated fields
+ if (fromRosterChange && missing.length === 0) {
+ const counts = countRoles();
+ const na = counts.artist;
+ const materials = getMaterials(vals);
+
+ if (na === 0) {
+ showMessage('Добавьте артистов', 'warning');
+ updateComputed(vals.s || 0, materials, 0, 0, 0, 0);
+ return;
+ }
+
+ if (autoCalculated.has('z') && !autoCalculated.has('s')) {
+ vals.z = (0.70 * ((vals.s || 0) - materials)) / na;
+ if (vals.z < 0) vals.z = 0;
+ setValue(inputs.z, vals.z);
+ autoCalculated.add('z');
+ } else if (autoCalculated.has('s') && !autoCalculated.has('z')) {
+ vals.s = materials + (na * (vals.z || 0)) / 0.70;
+ if (vals.s < 0) vals.s = 0;
+ setValue(inputs.s, vals.s);
+ autoCalculated.add('s');
+ } else if (autoCalculated.has('z') && autoCalculated.has('s')) {
+ // Prefer recalculating Z when both were auto-calculated
+ vals.z = (0.70 * ((vals.s || 0) - materials)) / na;
+ if (vals.z < 0) vals.z = 0;
+ setValue(inputs.z, vals.z);
+ autoCalculated.add('z');
+ } else if (!autoCalculated.has('z') && !autoCalculated.has('s')) {
+ // Neither S nor Z was auto-calculated — warn that totals won't match
+ showMessage('Внимание: изменился состав артистов. Проверьте сумму S или зарплату Z.', 'warning');
+ }
} else if (missing.length > 1) {
showMessage('Заполните все поля кроме одного, чтобы выполнить расчёт', 'info');
}
- // If all filled, just recalc
const allFilled = editableKeys.every(k => getValue(inputs[k]) !== null);
- if (!allFilled && missing.length !== 1) {
+ if (!allFilled) {
updateComputed(0, 0, 0, 0, 0, 0);
return;
}
@@ -182,7 +222,7 @@
setValue(inputs.rok, rok);
updateComputed(s, materials, base, totalRoyalties, salaries, counts);
- updatePayouts(counts, z, rom, rop, rot, rok);
+ updatePayouts(counts, z, rop, rot, rok);
saveState();
}
@@ -209,17 +249,17 @@
}
}
- function updatePayouts(counts, z, rom, rop, rot, rok) {
+ function updatePayouts(counts, z, rop, rot, rok) {
const rows = artistsTbody.querySelectorAll('tr[data-index]');
rows.forEach(row => {
const idx = parseInt(row.dataset.index, 10);
const artist = artists[idx];
let payout = 0;
if (artist.roles.artist) payout += z;
- if (artist.roles.organizer && counts.organizer > 0) payout += rom / counts.organizer;
- if (artist.roles.director && counts.director > 0) payout += rop / counts.director;
- if (artist.roles.technician && counts.technician > 0) payout += rot / counts.technician;
- if (artist.roles.commissioner && counts.commissioner > 0) payout += rok / counts.commissioner;
+ // Royalties go in full to each assigned person (not split)
+ if (artist.roles.director) payout += rop;
+ if (artist.roles.technician) payout += rot;
+ if (artist.roles.commissioner) payout += rok;
const cell = row.querySelector('.payout-cell');
if (cell) cell.textContent = round(payout).toFixed(2);
});
@@ -241,7 +281,6 @@
const nl = getValue(inputs.nl) || 0;
const materials = pz + tz + lz * nl;
const base = s - materials;
- const rom = base * 0.10;
const rop = base * 0.05;
const rot = base * 0.05;
const rok = base * 0.10;
@@ -249,10 +288,9 @@
artists.forEach((artist, idx) => {
let payout = 0;
if (artist.roles.artist) payout += z;
- if (artist.roles.organizer && counts.organizer > 0) payout += rom / counts.organizer;
- if (artist.roles.director && counts.director > 0) payout += rop / counts.director;
- if (artist.roles.technician && counts.technician > 0) payout += rot / counts.technician;
- if (artist.roles.commissioner && counts.commissioner > 0) payout += rok / counts.commissioner;
+ if (artist.roles.director) payout += rop;
+ if (artist.roles.technician) payout += rot;
+ if (artist.roles.commissioner) payout += rok;
const tr = document.createElement('tr');
tr.className = 'table-row';
@@ -260,7 +298,6 @@
tr.innerHTML = `
${escapeHtml(artist.name)} |
|
- |
|
|
|
@@ -278,22 +315,22 @@
showMessage('Этот артист уже добавлен', 'warning');
return;
}
- artists.push({ name, roles: { artist: true, organizer: false, director: false, technician: false, commissioner: false } });
+ artists.push({ name, roles: { artist: true, director: false, technician: false, commissioner: false } });
artistSelect.value = '';
renderArtists();
- calculate();
+ calculate({ fromRosterChange: true });
}
function removeArtist(index) {
artists.splice(index, 1);
renderArtists();
- calculate();
+ calculate({ fromRosterChange: true });
}
function toggleRole(index, role) {
artists[index].roles[role] = !artists[index].roles[role];
renderArtists();
- calculate();
+ calculate({ fromRosterChange: true });
}
function saveState() {
@@ -307,6 +344,7 @@
z: inputs.z.value,
},
artists,
+ autoCalculated: Array.from(autoCalculated),
};
localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
}
@@ -328,6 +366,9 @@
artists = state.artists;
renderArtists();
}
+ if (Array.isArray(state.autoCalculated)) {
+ state.autoCalculated.forEach(k => autoCalculated.add(k));
+ }
calculate();
} catch (e) {
console.error('Failed to load state', e);
@@ -345,6 +386,7 @@
z: inputs.z.value,
},
artists,
+ autoCalculated: Array.from(autoCalculated),
};
const blob = new Blob([JSON.stringify(state, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
@@ -372,6 +414,10 @@
inputs.nl.value = state.inputs.nl || '';
inputs.z.value = state.inputs.z || '';
artists = state.artists;
+ autoCalculated.clear();
+ if (Array.isArray(state.autoCalculated)) {
+ state.autoCalculated.forEach(k => autoCalculated.add(k));
+ }
renderArtists();
calculate();
showMessage('Импорт завершён успешно', 'success');
@@ -395,16 +441,19 @@
inputs.rot.value = '';
inputs.rok.value = '';
artists = [];
+ autoCalculated.clear();
renderArtists();
calculate();
localStorage.removeItem(STORAGE_KEY);
showMessage('Форма сброшена', 'info');
}
- // Event listeners
+ // Mark manually-edited fields as no longer auto-calculated
Object.values(inputs).forEach(el => {
if (!el.readOnly) {
- el.addEventListener('input', calculate);
+ el.addEventListener('input', () => {
+ autoCalculated.delete(el.id.replace('input-', ''));
+ });
el.addEventListener('change', calculate);
}
});