Files
mkd/backend/paymentInvoiceWorkflow.js

154 lines
4.8 KiB
JavaScript
Raw Permalink Normal View History

2026-02-04 00:17:04 +05:00
/**
* Модуль для управления workflow согласования счетов на оплату
*/
const WORKFLOW_STAGES = {
draft: {
next: 'pending_manager_approval',
role: null,
description: 'Черновик'
},
pending_manager_approval: {
next: 'pending_finance_manager_approval',
role: 'manager',
description: 'На согласовании у руководителя'
},
pending_finance_manager_approval: {
next: 'approved',
role: 'finance_manager',
description: 'На согласовании у финансового руководителя'
},
approved: {
next: 'scheduled',
role: 'financier',
description: 'Согласован, ожидает постановки в график'
},
scheduled: {
next: 'paid',
role: null,
description: 'В графике платежей'
},
paid: {
next: null,
role: null,
description: 'Оплачен'
},
postponed: {
next: 'scheduled',
role: null,
description: 'Отложен'
},
cancelled: {
next: null,
role: null,
description: 'Отменен'
},
rejected: {
next: 'draft',
role: null,
description: 'Отклонен'
},
completed: {
next: null,
role: null,
description: 'Выполнено'
}
};
// Роли высшего звена, которые пропускают этап manager
const TOP_MANAGEMENT_ROLES = ['finance_director', 'director', 'top_management'];
/**
* Определяет следующий статус для счета
* @param {string} currentStatus - текущий статус
* @param {string[]} userRoles - роли пользователя
* @returns {string|null} следующий статус
*/
function getNextStatus(currentStatus, userRoles = []) {
const stage = WORKFLOW_STAGES[currentStatus];
if (!stage) return null;
// Если текущий статус pending_manager_approval и пользователь из высшего звена
// пропускаем этап manager и идем сразу на finance_manager
if (currentStatus === 'pending_manager_approval' &&
userRoles.some(role => TOP_MANAGEMENT_ROLES.includes(role))) {
return 'pending_finance_manager_approval';
}
return stage.next;
}
/**
* Определяет, может ли пользователь согласовать счет на текущем этапе
* @param {string} currentStatus - текущий статус счета
* @param {string[]} userRoles - роли пользователя
* @returns {boolean}
*/
function canApprove(currentStatus, userRoles = []) {
const stage = WORKFLOW_STAGES[currentStatus];
if (!stage || !stage.role) return false;
// Если требуется роль manager, но пользователь из высшего звена - может согласовать
if (stage.role === 'manager' &&
userRoles.some(role => TOP_MANAGEMENT_ROLES.includes(role))) {
return true;
}
return userRoles.includes(stage.role);
}
/**
* Определяет роль следующего согласующего
* @param {string} currentStatus - текущий статус
* @param {string[]} userRoles - роли пользователя (для определения пропуска этапа)
* @returns {string|null}
*/
function getNextApproverRole(currentStatus, userRoles = []) {
const nextStatus = getNextStatus(currentStatus, userRoles);
if (!nextStatus) return null;
const nextStage = WORKFLOW_STAGES[nextStatus];
return nextStage ? nextStage.role : null;
}
/**
* Создает запись в истории согласования
* @param {string} userId - ID пользователя
* @param {string} userRole - роль пользователя
* @param {string} action - действие (approve, reject, etc.)
* @param {string} comment - комментарий
* @returns {object}
*/
function createApprovalHistoryEntry(userId, userRole, action, comment = '') {
return {
userId,
userRole,
action,
comment,
date: new Date().toISOString()
};
}
/**
* Генерирует номер счета
* @param {Date} date - дата создания
* @returns {string}
*/
function generateInvoiceNumber(date = new Date()) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const timestamp = Date.now().toString().slice(-6);
return `INV-${year}${month}${day}-${timestamp}`;
}
module.exports = {
WORKFLOW_STAGES,
TOP_MANAGEMENT_ROLES,
getNextStatus,
canApprove,
getNextApproverRole,
createApprovalHistoryEntry,
generateInvoiceNumber
};