pasport-ii-agenta-chernovik-3/app.js

593 lines
23 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Демо-данные договоров аренды ОЦО ЮС
const DEMO_CONTRACTS = [
{
id: 1,
number: 'ДА-2026-001',
counterparty: 'ТОО "Алма-Ата Ритейл"',
bin: '203456789012',
branch: 'Алматы',
initiator: 'Иванова Мария Петровна',
object: 'Помещение офиса',
area: 120,
cadastral: '01:49:000123:456',
rate: 650000,
term: 24,
total: 15600000,
signer: 'Ахметов Ержан Болатович, Директор',
status: 'ai-review',
risks: ['high'],
riskDetails: [
{ level: 'high', text: 'Тариф не соответствует SAP (заявка: 650000, SAP: 720000)' },
{ level: 'medium', text: 'Отсутствие кадастрового паспорта в приложении' }
],
sapTariffDate: '14.09.2026',
sapTariffRate: 720000,
aiChecks: [
{ name: 'ГК РК Статья 403', passed: true, comment: 'Существенные условия указаны' },
{ name: 'Полномочия подписанта', passed: true, comment: 'Доверенность №123 от 01.01.2026 действительна' },
{ name: 'Тариф SAP', passed: false, comment: 'Ставка в заявке ниже актуального тарифа на 9.7%' },
{ name: 'Кадастровые данные', passed: false, comment: 'Номер указан, паспорт не приложен' }
],
date: '12.09.2026',
comment: ''
},
{
id: 2,
number: 'ДА-2026-002',
counterparty: 'ИП "Самат"',
bin: '198765432109',
branch: 'Астана',
initiator: 'Кузнецов Андрей Владимирович',
object: 'Торговая площадь',
area: 85,
cadastral: '02:11:000456:789',
rate: 480000,
term: 12,
total: 5760000,
signer: 'Саматов Самат Нурланович, ИП',
status: 'new',
risks: [],
riskDetails: [],
sapTariffDate: '14.09.2026',
sapTariffRate: 480000,
aiChecks: [
{ name: 'ГК РК Статья 403', passed: true, comment: 'Существенные условия указаны' },
{ name: 'Полномочия подписанта', passed: true, comment: 'ИП действует без доверенности' },
{ name: 'Тариф SAP', passed: true, comment: 'Ставка соответствует тарифу' },
{ name: 'Кадастровые данные', passed: true, comment: 'Паспорт приложен' }
],
date: '13.09.2026',
comment: ''
},
{
id: 3,
number: 'ДА-2026-003',
counterparty: 'АО "Каспий Банк"',
bin: '211234567890',
branch: 'Алматы',
initiator: 'Султанова Гульнара Ахметовна',
object: 'Помещение отделения',
area: 250,
cadastral: '01:49:000789:012',
rate: 1200000,
term: 36,
total: 43200000,
signer: 'Ибраев Нурлан Жуматович, Управляющий',
status: 'lawyer-check',
risks: ['medium'],
riskDetails: [
{ level: 'medium', text: 'Контрагентская версия содержит отклонения в пункте 5.2' }
],
sapTariffDate: '14.09.2026',
sapTariffRate: 1150000,
aiChecks: [
{ name: 'ГК РК Статья 403', passed: true, comment: 'Существенные условия указаны' },
{ name: 'Полномочия подписанта', passed: true, comment: 'Приказ №456 от 15.03.2025' },
{ name: 'Тариф SAP', passed: false, comment: 'Ставка выше тарифа на 4.3% (требуется обоснование)' },
{ name: 'Кадастровые данные', passed: true, comment: 'Все документы в порядке' }
],
diff: [
{ op: 'del', text: '5.2. Арендатор обязуется оплатить аренду до 10 числа текущего месяца' },
{ op: 'add', text: '5.2. Арендатор обязуется оплатить аренду до 15 числа текущего месяца' },
{ op: 'del', text: '7.1. Договор действует до 31.12.2028' },
{ op: 'add', text: '7.1. Договор действует до 30.06.2029 с автоматической пролонгацией' }
],
date: '10.09.2026',
comment: ''
},
{
id: 4,
number: 'ДА-2026-004',
counterparty: 'ТОО "СтройМаш"',
bin: '205678901234',
branch: 'Караганда',
initiator: 'Петров Сергей Михайлович',
object: 'Складское помещение',
area: 500,
cadastral: '',
rate: 350000,
term: 6,
total: 2100000,
signer: 'Куанышев Болатбек Асылбекович, Директор',
status: 'revision',
risks: ['high'],
riskDetails: [
{ level: 'high', text: 'Отсутствуют кадастровые данные объекта' }
],
sapTariffDate: '14.09.2026',
sapTariffRate: 380000,
aiChecks: [
{ name: 'ГК РК Статья 403', passed: true, comment: 'Существенные условия указаны' },
{ name: 'Полномочия подписанта', passed: true, comment: 'Доверенность №789 от 20.02.2026' },
{ name: 'Тариф SAP', passed: false, comment: 'Ставка ниже тарифа на 7.9%' },
{ name: 'Кадастровые данные', passed: false, comment: 'Данные отсутствуют' }
],
date: '08.09.2026',
comment: 'Запрошены кадастровые документы у инициатора'
},
{
id: 5,
number: 'ДА-2026-005',
counterparty: 'ТОО "Нур-СултанTrade"',
bin: '219876543210',
branch: 'Астана',
initiator: 'Омарова Айгуль Ержановна',
object: 'Офисное помещение',
area: 180,
cadastral: '02:11:001234:567',
rate: 900000,
term: 18,
total: 16200000,
signer: 'Жуманов Ермек Бауыржанович, Генеральный директор',
status: 'approved',
risks: [],
riskDetails: [],
sapTariffDate: '14.09.2026',
sapTariffRate: 900000,
aiChecks: [
{ name: 'ГК РК Статья 403', passed: true, comment: 'Существенные условия указаны' },
{ name: 'Полномочия подписанта', passed: true, comment: 'Устав подтверждает полномочия' },
{ name: 'Тариф SAP', passed: true, comment: 'Ставка соответствует тарифу' },
{ name: 'Кадастровые данные', passed: true, comment: 'Все документы приложены' }
],
date: '05.09.2026',
comment: 'Согласовано, отправлено на подпись'
},
{
id: 6,
number: 'ДА-2026-006',
counterparty: 'ИП "Алия"',
bin: '187654321098',
branch: 'Шымкент',
initiator: 'Турсумбаев Максат Алиевич',
object: 'Торговая точка',
area: 45,
cadastral: '03:22:002345:678',
rate: 220000,
term: 12,
total: 2640000,
signer: 'Каримова Алия Сериковна, ИП',
status: 'ai-review',
risks: ['low'],
riskDetails: [
{ level: 'low', text: 'Небольшие отклонения в формулировках' }
],
sapTariffDate: '14.09.2026',
sapTariffRate: 230000,
aiChecks: [
{ name: 'ГК РК Статья 403', passed: true, comment: 'Существенные условия указаны' },
{ name: 'Полномочия подписанта', passed: true, comment: 'ИП действует без доверенности' },
{ name: 'Тариф SAP', passed: false, comment: 'Ставка ниже тарифа на 4.3%' },
{ name: 'Кадастровые данные', passed: true, comment: 'Документы приложены' }
],
date: '14.09.2026',
comment: ''
},
{
id: 7,
number: 'ДА-2026-007',
counterparty: 'ТОО "Актобе-Инвест"',
bin: '213456789012',
branch: 'Актобе',
initiator: 'Жанбеков Руслан Кенжевич',
object: 'Производственное помещение',
area: 320,
cadastral: '04:33:003456:789',
rate: 580000,
term: 24,
total: 13920000,
signer: 'Алиев Серикжан Болатович, Директор',
status: 'approved',
risks: [],
riskDetails: [],
sapTariffDate: '14.09.2026',
sapTariffRate: 580000,
aiChecks: [
{ name: 'ГК РК Статья 403', passed: true, comment: 'Существенные условия указаны' },
{ name: 'Полномочия подписанта', passed: true, comment: 'Доверенность №321 от 10.01.2026' },
{ name: 'Тариф SAP', passed: true, comment: 'Ставка соответствует тарифу' },
{ name: 'Кадастровые данные', passed: true, comment: 'Все документы в порядке' }
],
date: '03.09.2026',
comment: 'Согласовано'
},
{
id: 8,
number: 'ДА-2026-008',
counterparty: 'ТОО "Байтерек-Строй"',
bin: '224567890123',
branch: 'Астана',
initiator: 'Ким Дмитрий Андреевич',
object: 'Офис',
area: 95,
cadastral: '',
rate: 520000,
term: 12,
total: 6240000,
signer: 'Ли Владимир Петрович, Генеральный директор',
status: 'revision',
risks: ['high'],
riskDetails: [
{ level: 'high', text: 'Нет кадастровых данных' },
{ level: 'medium', text: 'Срок аренды не соответствует стандарту' }
],
sapTariffDate: '14.09.2026',
sapTariffRate: 550000,
aiChecks: [
{ name: 'ГК РК Статья 403', passed: true, comment: 'Существенные условия указаны' },
{ name: 'Полномочия подписанта', passed: true, comment: 'Протокол №5 от 15.12.2025' },
{ name: 'Тариф SAP', passed: false, comment: 'Ставка ниже тарифа на 5.5%' },
{ name: 'Кадастровые данные', passed: false, comment: 'Отсутствуют' }
],
date: '11.09.2026',
comment: 'Ожидание кадастровых документов'
}
];
// Состояние приложения
let contracts = [];
let currentFilter = 'all';
let currentScreen = 'queue';
let selectedContract = null;
// Инициализация
function init() {
loadContracts();
renderQueue();
renderMetrics();
renderProblems();
renderRegistry();
setupNavigation();
setupFilters();
setupSearch();
setupForm();
setupThemeToggle();
}
// Загрузка данных из localStorage или демо
function loadContracts() {
const stored = localStorage.getItem('contracts');
if (stored) {
contracts = JSON.parse(stored);
} else {
contracts = JSON.parse(JSON.stringify(DEMO_CONTRACTS));
saveContracts();
}
}
// Сохранение в localStorage
function saveContracts() {
localStorage.setItem('contracts', JSON.stringify(contracts));
}
// Навигация
function setupNavigation() {
document.querySelectorAll('.kt-ai-nav-item').forEach(item => {
item.addEventListener('click', () => {
const screen = item.dataset.screen;
navigateTo(screen);
});
});
}
function navigateTo(screen) {
currentScreen = screen;
// Обновляем активный пункт меню
document.querySelectorAll('.kt-ai-nav-item').forEach(item => {
item.classList.toggle('active', item.dataset.screen === screen);
});
// Показываем экран
document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));
document.getElementById(`screen-${screen}`).classList.add('active');
// Обновляем заголовок
const titles = {
queue: { title: 'Очередь договоров', subtitle: 'Проверка и согласование договоров аренды' },
problems: { title: 'Проблемные заявки', subtitle: 'Требуют дозапроса данных у инициатора' },
registry: { title: 'Реестр договоров', subtitle: 'История всех договоров' },
new: { title: 'Новая заявка', subtitle: 'Создание заявки на договор аренды' },
contract: { title: 'Карточка договора', subtitle: 'Детальная информация' }
};
document.getElementById('page-title').textContent = titles[screen]?.title || 'Договоры аренды';
document.getElementById('page-subtitle').textContent = titles[screen]?.subtitle || '';
if (screen === 'queue') renderQueue();
if (screen === 'problems') renderProblems();
if (screen === 'registry') renderRegistry();
}
// Фильтры
function setupFilters() {
document.querySelectorAll('.kt-ai-filter-chip').forEach(chip => {
chip.addEventListener('click', () => {
document.querySelectorAll('.kt-ai-filter-chip').forEach(c => c.classList.remove('active'));
chip.classList.add('active');
currentFilter = chip.dataset.filter;
renderQueue();
});
});
}
function filterByStatus(status) {
document.querySelectorAll('.kt-ai-filter-chip').forEach(c => {
c.classList.toggle('active', c.dataset.filter === status);
});
currentFilter = status;
renderQueue();
navigateTo('queue');
}
// Поиск
function setupSearch() {
document.getElementById('search-input').addEventListener('input', (e) => {
renderQueue(e.target.value);
});
}
// Рендер очереди
function renderQueue(searchTerm = '') {
const tbody = document.getElementById('queue-table-body');
let filtered = contracts;
// Фильтр по статусу
if (currentFilter !== 'all') {
filtered = filtered.filter(c => c.status === currentFilter);
}
// Поиск
if (searchTerm) {
const term = searchTerm.toLowerCase();
filtered = filtered.filter(c =>
c.number.toLowerCase().includes(term) ||
c.counterparty.toLowerCase().includes(term) ||
c.branch.toLowerCase().includes(term)
);
}
tbody.innerHTML = filtered.map(c => `
<tr onclick="openContract(${c.id})">
<td style="font-weight: var(--kt-ai-weight-medium);">${c.number}</td>
<td>${c.counterparty}</td>
<td>${c.branch}</td>
<td>${formatMoney(c.total)}</td>
<td><span class="kt-ai-status-pill" data-status="${c.status}">${getStatusName(c.status)}</span></td>
<td>${c.risks.length > 0 ? `<span class="kt-ai-risk-flag ${c.risks[0]}">⚠ ${c.risks.length}</span>` : '<span style="color: var(--kt-ai-fg-muted);">—</span>'}</td>
<td style="color: var(--kt-ai-fg-muted);">${c.date}</td>
</tr>
`).join('');
}
// Рендер метрик
function renderMetrics() {
const total = contracts.length;
const pending = contracts.filter(c => ['new', 'ai-review', 'lawyer-check'].includes(c.status)).length;
const risks = contracts.filter(c => c.risks.some(r => r === 'high' || r === 'medium')).length;
document.getElementById('metric-total').textContent = total;
document.getElementById('metric-pending').textContent = pending;
document.getElementById('metric-risks').textContent = risks;
const highRisks = contracts.filter(c => c.risks.some(r => r === 'high')).length;
const banner = document.getElementById('banner-risk');
if (highRisks > 0) {
document.getElementById('risk-count').textContent = highRisks;
banner.classList.remove('hidden');
} else {
banner.classList.add('hidden');
}
}
// Рендер проблемных
function renderProblems() {
const tbody = document.getElementById('problems-table-body');
const problems = contracts.filter(c => c.status === 'revision' || (c.risks.length > 0 && c.risks.includes('high')));
tbody.innerHTML = problems.map(c => `
<tr onclick="openContract(${c.id})">
<td style="font-weight: var(--kt-ai-weight-medium);">${c.number}</td>
<td>${c.riskDetails[0]?.text || 'Неполные данные'}</td>
<td>${c.branch}</td>
<td style="color: var(--kt-ai-fg-muted);">${c.date}</td>
<td><button class="kt-ai-btn" data-variant="primary" onclick="event.stopPropagation(); sendReminder(${c.id})">Напомнить</button></td>
</tr>
`).join('');
}
// Рендер реестра
function renderRegistry() {
const tbody = document.getElementById('registry-table-body');
tbody.innerHTML = contracts.map(c => `
<tr onclick="openContract(${c.id})">
<td style="font-weight: var(--kt-ai-weight-medium);">${c.number}</td>
<td>${c.counterparty}</td>
<td>${c.object}</td>
<td>${formatMoney(c.total)}</td>
<td><span class="kt-ai-status-pill" data-status="${c.status}">${getStatusName(c.status)}</span></td>
<td style="color: var(--kt-ai-fg-muted); font-size: var(--kt-ai-text-xs);">${c.comment || 'Без изменений'}</td>
</tr>
`).join('');
}
// Открытие карточки договора
function openContract(id) {
const c = contracts.find(x => x.id === id);
if (!c) return;
selectedContract = c;
document.getElementById('contract-title').textContent = c.number;
document.getElementById('contract-status').setAttribute('data-status', c.status);
document.getElementById('contract-status').textContent = getStatusName(c.status);
document.getElementById('contract-counterparty').textContent = c.counterparty;
document.getElementById('contract-bin').textContent = c.bin;
document.getElementById('contract-branch').textContent = c.branch;
document.getElementById('contract-initiator').textContent = c.initiator;
document.getElementById('contract-object').textContent = c.object;
document.getElementById('contract-area').textContent = c.area + ' м²';
document.getElementById('contract-cadastral').textContent = c.cadastral || 'Не указан';
document.getElementById('contract-rate').textContent = formatMoney(c.rate);
document.getElementById('contract-term').textContent = c.term + ' мес';
document.getElementById('contract-total').textContent = formatMoney(c.total);
// SAP тариф
document.getElementById('sap-tariff-date').textContent = c.sapTariffDate;
document.getElementById('sap-tariff-rate').textContent = formatMoney(c.sapTariffRate);
const diff = c.rate - c.sapTariffRate;
const diffPercent = ((diff / c.sapTariffRate) * 100).toFixed(1);
document.getElementById('sap-diff').textContent = `${diff > 0 ? '+' : ''}${diffPercent}% (${diff > 0 ? 'выше' : 'ниже'})`;
// Проверки ИИ
const checksHtml = c.aiChecks.map(check => `
<div style="display: flex; gap: var(--kt-ai-space-2); align-items: flex-start; margin-bottom: var(--kt-ai-space-3);">
<span style="font-size: 16px;">${check.passed ? '✅' : '⚠️'}</span>
<div style="flex: 1;">
<div style="font-size: var(--kt-ai-text-sm); font-weight: var(--kt-ai-weight-medium);">${check.name}</div>
<div style="font-size: var(--kt-ai-text-xs); color: var(--kt-ai-fg-muted);">${check.comment}</div>
</div>
</div>
`).join('');
document.getElementById('ai-checks').innerHTML = checksHtml;
// Отклонения
const diffCard = document.getElementById('diff-card');
if (c.diff && c.diff.length > 0) {
diffCard.style.display = 'block';
document.getElementById('diff-content').innerHTML = c.diff.map(d =>
`<div class="line" data-op="${d.op}">${d.op === 'add' ? '+' : '-'} ${d.text}</div>`
).join('');
} else {
diffCard.style.display = 'none';
}
navigateTo('contract');
}
// Форма новой заявки
function setupForm() {
document.getElementById('new-contract-form').addEventListener('submit', (e) => {
e.preventDefault();
const form = e.target;
const formData = new FormData(form);
const newContract = {
id: contracts.length + 1,
number: `ДА-2026-${String(contracts.length + 1).padStart(3, '0')}`,
counterparty: formData.get('counterparty'),
bin: formData.get('bin'),
branch: formData.get('branch'),
initiator: formData.get('initiator'),
object: formData.get('object'),
area: Number(formData.get('area')),
cadastral: formData.get('cadastral'),
rate: Number(formData.get('rate')),
term: Number(formData.get('term')),
total: Number(formData.get('rate')) * Number(formData.get('term')),
signer: formData.get('signer'),
status: 'new',
risks: [],
riskDetails: [],
sapTariffDate: new Date().toLocaleDateString('ru-RU'),
sapTariffRate: Number(formData.get('rate')),
aiChecks: [],
date: new Date().toLocaleDateString('ru-RU'),
comment: formData.get('comment')
};
contracts.unshift(newContract);
saveContracts();
renderMetrics();
renderQueue();
renderRegistry();
form.reset();
alert('Заявка отправлена! ИИ-агент начнёт обработку в ближайшее время.');
navigateTo('queue');
});
}
// Переключение темы
function setupThemeToggle() {
const btn = document.getElementById('theme-toggle');
btn.addEventListener('click', () => {
const html = document.documentElement;
const isDark = html.getAttribute('data-theme') === 'dark';
html.setAttribute('data-theme', isDark ? 'light' : 'dark');
btn.textContent = isDark ? '🌙' : '☀️';
});
}
// Вспомогательные функции
function getStatusName(status) {
const names = {
'new': 'Новый',
'ai-review': 'ИИ проверка',
'lawyer-check': 'Проверка юриста',
'approved': 'Согласовано',
'revision': 'На доработке'
};
return names[status] || status;
}
function formatMoney(amount) {
return new Intl.NumberFormat('ru-RU', { style: 'currency', currency: 'KZT', maximumFractionDigits: 0 }).format(amount);
}
function sendReminder(id) {
alert('Напоминание отправлено инициатору через Лотус');
}
// Кнопки действий
document.getElementById('btn-approve')?.addEventListener('click', () => {
if (selectedContract) {
selectedContract.status = 'approved';
selectedContract.comment = 'Согласовано ' + new Date().toLocaleDateString('ru-RU');
saveContracts();
alert('Договор согласован и отправлен на подпись');
navigateTo('queue');
}
});
document.getElementById('btn-revision')?.addEventListener('click', () => {
if (selectedContract) {
selectedContract.status = 'revision';
selectedContract.comment = 'На доработке ' + new Date().toLocaleDateString('ru-RU');
saveContracts();
alert('Договор возвращён на доработку');
navigateTo('queue');
}
});
document.getElementById('btn-download')?.addEventListener('click', () => {
alert('Файл договора будет скачан. В рабочей версии — генерация Word через ИИ.');
});
// Запуск
init();