Файли final, finalnew і finalv7_approved не показують погоджений документ. Автоматизація погодження документів керує станами, версіями й рішеннями.

Для workflow достатньо Sheets, Drive й Apps Script. Не ECM-система: практичний процес.

Автоматизація погодження документів: статуси, snapshot версії та фінальне рішення
Автоматизація погодження документів: статуси, snapshot версії та фінальне рішення

Чому файли final_v7 не працюють

Назва файлу — коментар автора, а не стан процесу. Хтось може завантажити final_v8, не знаючи, що попередню версію погодили, або змінити файл після approval.

Типові симптоми:

  • версію шукають у чатах;
  • «погоджено» не пов'язане з файлом;
  • невідомі approver і дедлайн;
  • документ перезаписують без історії;
  • усім видали editor-доступ.

Як і в автоматизації замовлень, дія залежить від статусу, а не повідомлення в чаті.

Реєстр документів у Google Sheets

Реєстр документів показує статуси, винятки та завершені рішення
Реєстр документів показує статуси, винятки та завершені рішення

Один рядок реєстру представляє документ як бізнес-об'єкт:

ПолеПризначення
document_idстабільний ID, незалежний від номера рядка
titleзрозуміла назва документа
file_idID актуального файла в Google Drive
versionномер версії
ownerхто готує та оновлює документ
approverхто приймає рішення
statusпоточний стан workflow
submitted_atколи відправлено на погодження
due_atстрок рішення
decision_atчас останнього рішення
decision_commentпричина approval, rejection або змін

documentid не будуйте з номера рядка: таблицю сортують. Підійде DOC-2026-0042. fileid вказує на робочу версію, snapshot-копії зберігаються окремо.

Статуси погодження

Мінімальна статусна модель:

СтатусДозволена дія
draftowner редагує документ
submittedapprover перевіряє зафіксовану версію
changes_requestedowner створює наступну версію
approvedверсію дозволено використовувати
rejectedдокумент відхилено з коментарем
archivedпроцес закрито, файл лише для історії

Не дозволяйте approved напряму з draft: потрібні submitted, snapshot і запис у журналі.

function submitDocument(documentId) {
  const doc = getDocumentById(documentId);
  if (!doc || !["draft", "changes_requested"].includes(doc.status)) {
    throw new Error("Document is not ready for submission");
  }

  const snapshot = createVersionSnapshot(doc);
  const now = new Date();

  updateDocument(documentId, {
    status: "submitted",
    submitted_at: now.toISOString(),
    due_at: addBusinessDays(now, 2).toISOString(),
    snapshot_file_id: snapshot.getId()
  });

  writeApprovalLog(documentId, "submitted", {
    actor: Session.getActiveUser().getEmail(),
    version: doc.version,
    snapshot_file_id: snapshot.getId()
  });
}

Як зберігати версії й коментарі

Snapshot-копії зберігають історію версій документа без перезаписування
Snapshot-копії зберігають історію версій документа без перезаписування

Перед submit створіть snapshot в окремій папці, інакше після правок не видно, що погоджував approver. Це підтримує Drive service.

function createVersionSnapshot(doc) {
  const source = DriveApp.getFileById(doc.file_id);
  const folder = DriveApp.getFolderById(getSetting("APPROVAL_ARCHIVE_FOLDER"));
  const version = String(doc.version).padStart(2, "0");
  const name = `${doc.document_id}-v${version}-${doc.title}`;

  const copy = source.makeCopy(name, folder);
  copy.setDescription(
    `Snapshot for approval; document=${doc.document_id}; version=${doc.version}`
  );
  return copy;
}

Після changes_requested owner збільшує version і подає документ повторно. Старий snapshot не змінюється. Рішення записується структуровано:

function recordDecision(documentId, decision, comment) {
  const allowed = ["approved", "rejected", "changes_requested"];
  const doc = getDocumentById(documentId);
  const actor = Session.getActiveUser().getEmail();

  if (!doc || doc.status !== "submitted") {
    throw new Error("Only submitted documents can receive a decision");
  }
  if (actor !== doc.approver || !allowed.includes(decision)) {
    throw new Error("Decision is not allowed");
  }
  if (decision !== "approved" && !String(comment || "").trim()) {
    throw new Error("Comment is required");
  }

  updateDocument(documentId, {
    status: decision,
    decision_at: new Date().toISOString(),
    decision_comment: String(comment || "").trim()
  });
  writeApprovalLog(documentId, decision, {
    actor,
    version: doc.version,
    comment
  });
}

Права owner, approver і viewer

Owner готує документ, approver приймає рішення, viewer отримує read-only доступ
Owner готує документ, approver приймає рішення, viewer отримує read-only доступ
  • owner готує документ, створює нову версію й відправляє її;
  • approver читає snapshot і приймає рішення, але не має тихо виправляти подану версію;
  • viewer бачить дозволений документ або звіт без права змінювати процес;
  • технічний admin підтримує Apps Script, налаштування й доступи.

Принцип least privilege важливіший за «дати editor усім». Ролі описані в гайді Google Drive про доступи. Для інтеграцій використовуйте Apps Script webhook-сервер, а не публічний Sheet.

Нагадування відповідальному

Журнал рішень і нагадування фіксують події та прострочений строк погодження
Журнал рішень і нагадування фіксують події та прострочений строк погодження

Time-driven trigger раз на день знаходить прострочені submitted. Нагадування лише повідомляє approver і пише подію в журнал.

function sendApprovalReminder() {
  const now = new Date();
  const documents = getDocuments().filter(doc =>
    doc.status === "submitted" &&
    doc.due_at &&
    new Date(doc.due_at) < now
  );

  documents.forEach(doc => {
    MailApp.sendEmail({
      to: doc.approver,
      subject: `Потрібне рішення: ${doc.title}`,
      htmlBody: `Документ ${doc.document_id}, версія ${doc.version}, очікує рішення.`
    });
    writeApprovalLog(doc.document_id, "reminder_sent", {
      actor: "automation",
      approver: doc.approver
    });
  });
}

Mail service підходить для простих повідомлень. Ескалації й SLA винесіть в окремі правила.

Журнал рішень

Аркуш ApprovalLog має бути append-only:

ПолеПриклад
event_at2026-06-08T14:00:00Z
document_idDOC-2026-0042
eventchanges_requested
actoremail відповідального
version3
details_jsonкоментар, snapshot ID, approver
function writeApprovalLog(documentId, event, details = {}) {
  const sheet = SpreadsheetApp.getActive()
    .getSheetByName("ApprovalLog");

  sheet.appendRow([
    new Date().toISOString(),
    documentId,
    event,
    details.actor || "",
    details.version || "",
    JSON.stringify(details)
  ]);
}

У details_json не дублюйте секрети чи PII. Журнал пояснює процес, а не копіює документ.

Як журнал рішень прибрав суперечки про актуальну версію

У робочому сценарії кошториси погоджували в чаті. Після «ок» хтось завантажував новий файл, і вже не було зрозуміло, яку версію підтвердили.

Я додав document_id, версію, owner, approver і статус. Submit створював snapshot, рішення потрапляло в ApprovalLog, а правки запускали нову версію.

Команда перестала шукати approval у чатах: стали видимі snapshot, автор, час і коментар. Sheets не стала ECM-системою, але операційна невизначеність зникла.

Практичний підсумок

Автоматизація має фіксувати файл і процес рішення. Реєстр показує стан, snapshot зберігає версію, а ApprovalLog пояснює історію.

Почніть із шести статусів, трьох ролей і п'яти дій: submit, snapshot, decision, reminder та log. Не перезаписуйте погоджену версію й не використовуйте реакцію в месенджері як єдине підтвердження.