// =========================================================================
// 🔄 МОДУЛЬ АВТОНОМНОГО МЕНЮ, ИЗОЛИРОВАННОГО КАЛЕНДАРЯ И ДИСКА v25.6 (SaaS)
// =========================================================================
// Константа управления — ID твоей главной управляющей таблицы сотрудников
var MASTER_CONTROL_ID = '15lc3jj5D4eadrrx0amG-rOE0yCPo2D1P51Hu-T-aV0c';
// 1. Автоматическое создание меню в Excel-таблице любого клиента Balloon CRM
function onOpen() {
try {
var ui = SpreadsheetApp.getUi();
ui.createMenu('🛠 CRM Платформа')
.addItem('📱 Открыть Калькулятор', 'showSidebar')
.addSeparator()
.addItem('📥 Обновить связку Tilda (YML)', 'updateTildaExport')
.addSeparator()
.addItem('📂 1. Настроить личный Диск для photo', 'setupClientFolder')
.addItem('📅 2. Связать личный Календарь', 'setupClientCalendar')
.addToUi();
} catch(e) {}
}
// 2. Функция вызова боковой панели калькулятора внутри Excel
function showSidebar() {
var html = HtmlService.createHtmlOutputFromFile('Sidebar').setTitle('Калькулятор').setWidth(350);
SpreadsheetApp.getUi().showSidebar(html);
}
// 3. Умная настройка и принудительное авто-открытие папки на Гугл Диске в браузере
function setupClientFolder() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var nSheet = ss.getSheetByName('Настройки') || ss.insertSheet('Настройки');
try {
var compName = nSheet.getLastRow() > 0 ? nSheet.getRange(2, 4).getValue().toString().trim() : "Студия";
var folder = DriveApp.createFolder("📸 Фото заказов — " + (compName || "Balloon"));
// Делаем её полностью доступной по ссылке для загрузки и просмотра
folder.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW);
// Записываем ID папки строго в настройки F2 для ядра doPost
nSheet.getRange(2, 6).setValue(folder.getId());
SpreadsheetApp.flush();
// СВЕРХУДОБНЫЙ ХАК: Принудительно открываем созданную папку на экране менеджера!
var folderUrl = folder.getUrl();
var htmlOutput = HtmlService.createHtmlOutput(
'<script>window.open("' + folderUrl + '", "_blank"); google.script.host.close();</script>'
).setWidth(10).setHeight(10);
SpreadsheetApp.getUi().showModalDialog(htmlOutput, "Открытие папки...");
} catch(e) {
SpreadsheetApp.getUi().alert("❌ Ошибка настройки Диска", e.toString(), SpreadsheetApp.getUi().ButtonSet.OK);
}
}
// 4. Автоматическое создание отдельного календаря для заказов (без смешивания с личным!)
function setupClientCalendar() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var nSheet = ss.getSheetByName('Настройки') || ss.insertSheet('Настройки');
try {
var calendarName = "🎈 CRM Balloon — ЗАКАЗЫ";
var targetCalendar = null;
// Ищем, может быть у пользователя уже создан такой календарь, чтобы не плодить дубликаты
var calendars = CalendarApp.getCalendarsByName(calendarName);
if (calendars.length > 0) {
targetCalendar = calendars[0];
} else {
// Создаем абсолютно НОВЫЙ, отдельный календарь для доставок шаров
targetCalendar = CalendarApp.createCalendar(calendarName, {
summary: "Сюда автоматически выгружаются все доставки и монтажи шаров из CRM Balloon. Вы можете раздать доступ к этому календарю своей команде.",
timeZone: "GMT+10"
});
}
// Записываем ID нового календаря в настройки E2 для ядра doPost
nSheet.getRange(2, 5).setValue(targetCalendar.getId());
SpreadsheetApp.flush();
SpreadsheetApp.getUi().alert(
"✅ УСПЕШНО СВЯЗАНО!",
"Создан отдельный изолированный календарь: \"" + calendarName + "\".\n\nID записан в ячейку настроек. Личные дела владельца в полной безопасности! Теперь вы можете раздать доступ к нему своим сотрудникам и водителям.",
SpreadsheetApp.getUi().ButtonSet.OK
);
} catch(e) {
SpreadsheetApp.getUi().alert("❌ Ошибка создания Календаря", e.toString(), SpreadsheetApp.getUi().ButtonSet.OK);
}
}
// =========================================================================
// 🔄 SaaS-ИМПОРТЕР ТОВАРОВ TILDA YML: UID, TITLE, URL v30.0 (ЯДРО)
// =========================================================================
function coreUpdateTildaExport(sheetId) {
if (!sheetId) return "error: no sheet id";
try {
var ss = SpreadsheetApp.openById(sheetId);
var goodsSheet = ss.getSheetByName('Товары') || ss.getSheetByName('каталог') || ss.getSheetByName('Каталог');
var settingsSheet = ss.getSheetByName('Настройки') || ss.getSheetByName('настройки');
if (!goodsSheet || !settingsSheet) return "error: sheets not found";
// Считываем индивидуальную YML-ссылку пользователя из ячейки B2 листа Настройки
var ymlUrl = settingsSheet.getRange("B2").getValue().toString().trim();
if (!ymlUrl || ymlUrl.indexOf("http") === -1) return "error: invalid yml url in B2";
var response = UrlFetchApp.fetch(ymlUrl);
var xmlText = response.getContentText();
var document = XmlService.parse(xmlText);
var shop = document.getRootElement().getChild('shop');
var offers = shop.getChild('offers').getChildren('offer');
// Собираем карту актуальных товаров из YML Tilda (Ключ — Tilda UID)
var tildaOffersMap = {};
offers.forEach(function(offer) {
var uid = String(offer.getAttribute('id') || '').trim(); // Tilda UID
var title = offer.getChildText('name') || offer.getChildText('model') || ""; // Title
var url = offer.getChildText('url') || ""; // Url
title = String(title).trim();
url = String(url).trim();
if (uid) {
tildaOffersMap[uid] = { uid: uid, title: title, url: url };
}
});
// Сканируем текущие строки листа "Товары" (колонки A, B, C) для умного слияния
var lastRow = goodsSheet.getLastRow();
if (lastRow < 1) lastRow = 1;
var range = goodsSheet.getRange(1, 1, lastRow, 3); // Читаем А (UID), B (Title), C (Url)
var currentValues = range.getValues();
var existingIdsLog = {};
for (var i = 1; i < currentValues.length; i++) {
var rowUid = String(currentValues[i] || '').trim();
if (!rowUid) continue;
existingIdsLog[rowUid] = true;
// УМНОЕ УДАЛЕНИЕ: Если UID товара нет в новом YML, очищаем строго колонки А, B, C этой строки
if (!tildaOffersMap[rowUid]) {
goodsSheet.getRange(i + 1, 1, 1, 3).setValues([["", "", ""]]);
goodsSheet.getRange(i + 1, 1).setNote("Товар удален из каталога Tilda: UID " + rowUid);
} else {
// Если товар остался, обновляем его Title (B) и Url (C) актуальными данными
var activeOffer = tildaOffersMap[rowUid];
goodsSheet.getRange(i + 1, 2, 1, 2).setValues([[activeOffer.title, activeOffer.url]]);
}
}
// УМНОЕ ДОБАВЛЕНИЕ: Дописываем новинки в самый низ листа
for (var keyUid in tildaOffersMap) {
if (!existingIdsLog[keyUid]) {
var nextEmptyRow = goodsSheet.getLastRow() + 1;
var newOffer = tildaOffersMap[keyUid];
goodsSheet.getRange(nextEmptyRow, 1, 1, 3).setValues([[newOffer.uid, newOffer.title, newOffer.url]]);
}
}
SpreadsheetApp.flush();
return "success";
} catch(eYml) {
return "error: " + eYml.toString();
}
}