Наши филиалы заказывают книги по таблице в Google Sheets с разграничением доступа по ячейкам. Таблица ранее была создана автоматически — Построение дополнительной таблицы в Excel.
После этого была перемещена в Google Sheets и написан скрипт для автоматической расстановки защиты данных.
/**
* Проставляет права редактирования по филиалам
*/
function applyBranchEditors() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getActiveSheet();
// === Настройки ===
const branches = ["256", "258", "259", "260", "261", "262", "263", "264", "265", "Юбилейный"];
const ACCESS = {
"256": "256@newmoslib.ru",
"258": "258@newmoslib.ru",
"259": "259@newmoslib.ru",
"260": "260@newmoslib.ru",
"261": "261@newmoslib.ru",
"262": "262@newmoslib.ru",
"263": "263@newmoslib.ru",
"264": "264@newmoslib.ru",
"265": "265@newmoslib.ru",
"Юбилейный": "libyubileyny@newmoslib.ru",
};
const ADMINS = [
Session.getEffectiveUser().getEmail(),
"***@***.ru", // сюда можно вставить EMail адреса, которым будет доступно редактирование всего
];
const BASE_COL = 8; // H
const START_COL = 10; // J
const HEADER_ROW_1 = 1;
const HEADER_ROW_2 = 2;
const FIRST_DATA_ROW = 3;
if (!branches.length) throw new Error("Список филиалов пуст.");
// Последняя строка данных и строка итогов
const lastDataRow = getLastDataRowByColumn_(sh, BASE_COL, FIRST_DATA_ROW);
const totalRow = Math.max(FIRST_DATA_ROW, lastDataRow) + 1;
// Границы блока по колонкам
const lastBranchPairStart = START_COL + (branches.length - 1) * 2; // начало последней пары
const lastBranchCol = lastBranchPairStart + 1; // «сумма» последнего филиала
// === Сносим прошлые защиты, созданные этим скриптом ===
const PROTECT_PREFIX = "[BRANCH-PROTECT]";
sh.getProtections(SpreadsheetApp.ProtectionType.RANGE).forEach(p => {
const d = p.getDescription();
if (d && d.startsWith(PROTECT_PREFIX)) { try { p.remove(); } catch (e) {} }
});
// Важно: НЕ защищаем весь лист, только непересекающиеся диапазоны.
// 1) Шапка (строки 1–2, весь блок филиалов) — только АДМИНЫ
const headerRange = sh.getRange(
HEADER_ROW_1,
START_COL,
HEADER_ROW_2 - HEADER_ROW_1 + 1,
lastBranchCol - START_COL + 1
);
const protHeader = headerRange.protect();
protHeader.setDescription(`${PROTECT_PREFIX} HEAD all-branches`);
protHeader.setWarningOnly(false);
setEditorsSafe_(protHeader, ADMINS);
// 2) Для каждой «кол-во» — разрешаем филиалу (только строки данных);
// «сумма» (включая итог) и итог «кол-во» — только админы
for (let i = 0; i < branches.length; i++) {
const name = branches[i];
const editorEmail = ACCESS[name];
const qtyCol = START_COL + i * 2; // «кол-во»
const sumCol = qtyCol + 1; // «сумма»
// «кол-во»: только строки данных (без итога!)
if (editorEmail) {
const qtyDataRange = sh.getRange(FIRST_DATA_ROW, qtyCol, (lastDataRow - FIRST_DATA_ROW + 1), 1);
const protQty = qtyDataRange.protect();
protQty.setDescription(`${PROTECT_PREFIX} QTY ${name}`);
protQty.setWarningOnly(false);
setEditorsSafe_(protQty, unique_(ADMINS.concat(editorEmail)));
} else {
Logger.log(`Нет e-mail для "${name}" — пропуск разрешённого диапазона "кол-во".`);
}
// «сумма»: данные + итог — только админы
const sumAllRange = sh.getRange(FIRST_DATA_ROW, sumCol, (lastDataRow - FIRST_DATA_ROW + 1) + 1, 1);
const protSum = sumAllRange.protect();
protSum.setDescription(`${PROTECT_PREFIX} SUM ${name}`);
protSum.setWarningOnly(false);
setEditorsSafe_(protSum, ADMINS);
// Итог «кол-во»: одна ячейка — только админы
const qtyTotalCell = sh.getRange(totalRow, qtyCol, 1, 1);
const protQtyTot = qtyTotalCell.protect();
protQtyTot.setDescription(`${PROTECT_PREFIX} QTY_TOTAL ${name}`);
protQtyTot.setWarningOnly(false);
setEditorsSafe_(protQtyTot, ADMINS);
}
// 3) Остальной лист (вне блока филиалов) — только админы (три несмещающихся зоны)
const maxRows = sh.getMaxRows();
const maxCols = sh.getMaxColumns();
if (START_COL > 1) {
const leftRange = sh.getRange(1, 1, totalRow, START_COL - 1);
const protLeft = leftRange.protect();
protLeft.setDescription(`${PROTECT_PREFIX} OUTSIDE left`);
protLeft.setWarningOnly(false);
setEditorsSafe_(protLeft, ADMINS);
}
if (lastBranchCol < maxCols) {
const rightRange = sh.getRange(1, lastBranchCol + 1, totalRow, maxCols - lastBranchCol);
const protRight = rightRange.protect();
protRight.setDescription(`${PROTECT_PREFIX} OUTSIDE right`);
protRight.setWarningOnly(false);
setEditorsSafe_(protRight, ADMINS);
}
if (totalRow < maxRows) {
const belowRange = sh.getRange(totalRow + 1, 1, maxRows - totalRow, maxCols);
const protBelow = belowRange.protect();
protBelow.setDescription(`${PROTECT_PREFIX} OUTSIDE below`);
protBelow.setWarningOnly(false);
setEditorsSafe_(protBelow, ADMINS);
}
SpreadsheetApp.flush();
SpreadsheetApp.getUi().alert('Права применены: «кол-во» — свой филиал; шапки, суммы, итоги и всё остальное — только админы.');
}
/** Последняя непустая строка по указанной колонке, начиная с FIRST_DATA_ROW */
function getLastDataRowByColumn_(sh, col, FIRST_DATA_ROW) {
const lastRowSheet = sh.getLastRow();
if (lastRowSheet < FIRST_DATA_ROW) return FIRST_DATA_ROW;
const vals = sh.getRange(FIRST_DATA_ROW, col, lastRowSheet - FIRST_DATA_ROW + 1, 1).getValues();
for (let i = vals.length - 1; i >= 0; i--) {
if (vals[i][0] !== '' && vals[i][0] !== null) return FIRST_DATA_ROW + i;
}
return FIRST_DATA_ROW;
}
/** Устанавливает список редакторов для защиты, перезаписывая старых */
function setEditorsSafe_(prot, editors) {
try { prot.setDomainEdit(false); } catch (e) { /* ignore */ }
try {
const current = prot.getEditors();
if (current && current.length) prot.removeEditors(current);
} catch (e) { /* ignore */ }
try {
prot.addEditors(editors);
} catch (e) {
editors.forEach(email => { try { prot.addEditor(email); } catch (e2) {} });
}
}
/** Удаляет дубли из массива */
function unique_(arr) {
return Array.from(new Set(arr));
}