Реалізація Workflow погодження і підписання документів на сайті

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

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

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Реалізація Workflow погодження і підписання документів на сайті
Складна
~2-4 тижні
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Реалізація Workflow согласування та підписання документів

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

Види workflow

Послідовне согласування—кожен наступний согласуючий бачить документ тільки після схвалення попереднього. Використовується коли порядок важливий (керівник → директор → CEO).

Паралельне согласування—всі согласуючі отримують документ одночасно. Документ затверджений, коли всі (або N з M) поставили согласування.

Змішане—комбінація: група согласуючих паралельно, затим итогове підписання у директора.

Модель даних

-- Шаблони workflow
CREATE TABLE workflow_templates (
  id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  name        VARCHAR(200),
  description TEXT,
  steps       JSONB NOT NULL,  -- Масив кроків з конфігурацією
  created_by  UUID REFERENCES users(id)
);

-- Екземпляр workflow для конкретного документа
CREATE TABLE workflow_instances (
  id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  template_id     UUID REFERENCES workflow_templates(id),
  document_id     UUID REFERENCES documents(id),
  initiator_id    UUID REFERENCES users(id),
  current_step    INT DEFAULT 1,
  status          VARCHAR(50) DEFAULT 'in_progress', -- in_progress, approved, rejected, cancelled
  metadata        JSONB DEFAULT '{}',
  created_at      TIMESTAMPTZ DEFAULT NOW(),
  completed_at    TIMESTAMPTZ
);

-- Завдання согласування
CREATE TABLE workflow_tasks (
  id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  instance_id     UUID REFERENCES workflow_instances(id),
  step_number     INT NOT NULL,
  assignee_id     UUID REFERENCES users(id),
  assignee_role   VARCHAR(100),    -- Альтернатива assignee_id для динамічних ролей
  task_type       VARCHAR(50),     -- 'approve', 'sign', 'review'
  status          VARCHAR(50) DEFAULT 'pending', -- pending, approved, rejected, delegated
  comment         TEXT,
  due_at          TIMESTAMPTZ,
  completed_at    TIMESTAMPTZ,
  completed_by_id UUID REFERENCES users(id)  -- Якщо делегував
);

Рушій стану

class WorkflowEngine {
  async processDecision(
    taskId: string,
    decision: 'approve' | 'reject' | 'request_changes',
    comment: string,
    userId: string
  ) {
    const task = await db.workflowTasks.findOne(taskId, { include: 'instance.template' });
    if (task.assigneeId !== userId) throw new Error('Not authorized');

    await db.workflowTasks.update(taskId, {
      status: decision,
      comment,
      completedAt: new Date(),
    });

    await auditLog.record({
      action: `task.${decision}`,
      userId,
      taskId,
      instanceId: task.instanceId,
    });

    switch (decision) {
      case 'approve':
        await this.onTaskApproved(task);
        break;
      case 'reject':
        await this.onTaskRejected(task);
        break;
      case 'request_changes':
        await this.returnToInitiator(task, comment);
        break;
    }
  }

  private async onTaskApproved(task: WorkflowTask) {
    const instance = task.instance;
    const template = JSON.parse(instance.template.steps);
    const currentStep = template[task.stepNumber - 1];

    // Паралельний крок: перевіримо чи всі затвердили
    if (currentStep.type === 'parallel') {
      const stepTasks = await db.workflowTasks.findAll({
        instanceId: instance.id,
        stepNumber: task.stepNumber,
      });

      const allApproved = stepTasks.every(t => t.status === 'approve');
      const anyRejected = stepTasks.some(t => t.status === 'reject');

      if (anyRejected) return this.onTaskRejected(task);
      if (!allApproved) return; // Чекаємо решти
    }

    // Переходимо до наступного кроку
    const nextStep = template[task.stepNumber]; // Наступний елемент
    if (!nextStep) {
      // Всі кроки пройдені—workflow завершений
      await this.completeWorkflow(instance.id);
    } else {
      await this.activateStep(instance.id, nextStep, task.stepNumber + 1);
    }
  }

  private async activateStep(instanceId: string, step: WorkflowStep, stepNumber: number) {
    await db.workflowInstances.update(instanceId, { currentStep: stepNumber });

    const assignees = await this.resolveAssignees(step);
    const dueAt = step.deadlineHours ? addHours(new Date(), step.deadlineHours) : null;

    for (const assignee of assignees) {
      const task = await db.workflowTasks.create({
        instanceId,
        stepNumber,
        assigneeId: assignee.id,
        taskType: step.taskType,
        dueAt,
      });

      await notifyAssignee(assignee, task);
    }
  }
}

Делегування

Согласуючий може передати завдання іншому співробітнику:

async function delegateTask(taskId, delegateToId, reason, requesterId) {
  const task = await db.workflowTasks.findByPk(taskId);
  if (task.assigneeId !== requesterId) throw new Error('Not authorized');

  // Закриваємо поточне завдання
  await db.workflowTasks.update(taskId, {
    status: 'delegated',
    comment: `Делегировано: ${reason}`,
    completedAt: new Date(),
  });

  // Створюємо нове для делегата
  await db.workflowTasks.create({
    ...task.toJSON(),
    id: undefined,
    assigneeId: delegateToId,
    status: 'pending',
    completedAt: null,
    metadata: { delegatedFrom: task.assigneeId, reason },
  });

  await notifyDelegate(delegateToId, taskId);
}

Таймаути та еськалація

// Cron job: перевіряємо прострочені завдання кожну годину
async function processOverdueTasks() {
  const overdueTasks = await db.workflowTasks.findAll({
    status: 'pending',
    dueAt: { lt: new Date() },
    escalationSentAt: null,
  });

  for (const task of overdueTasks) {
    const step = getStepConfig(task);

    if (step.escalationUserId) {
      // Повідомляємо керівника
      await notifyEscalation(step.escalationUserId, task);
      await db.workflowTasks.update(task.id, { escalationSentAt: new Date() });
    }

    if (step.autoApproveOnTimeout) {
      await workflowEngine.processDecision(task.id, 'approve', 'Auto-approved on timeout', 'system');
    }
  }
}

Візуалізація прогресу

Timeline workflow для ініціатора: який крок виконаний, хто затвердив, хто ще не відповів, скільки часу чекаємо. React-компонент з вертикальним timeline, іконками статусів (✓, ✗, ⏳) та tooltip'ами з коментарями.

Нотифікації

Подія Кому Терміност
Завдання назначено Согласуючий Немедленно
Дедлайн через 4ч Согласуючий Push + Email
Просрочена задача Согласуючий + еськалація Email
Документ одобрений Ініціатор In-app + Email
Документ відхилений Ініціатор Немедленно, всі канали

Терміни

Базовий workflow рушій з послідовним согласуванням, завданнями та нотифікаціями—7–10 днів. Паралельне согласування, делегування, еськалація, таймауты—ще 5–7 днів. Візуальний конструктор шаблонів workflow—7–10 днів.