diff --git a/docs/crm.html b/docs/crm.html index f6eabff..647974c 100644 --- a/docs/crm.html +++ b/docs/crm.html @@ -214,6 +214,34 @@ async function saveCrm(){ } function inviteLink(){const url=`${location.origin}${location.pathname.replace('crm.html','cabinet.html')}?t=${current}`;navigator.clipboard.writeText(url).then(()=>alert("Ссылка скопирована:\n\n"+url)).catch(()=>prompt("Ссылка:",url));} +function exportSpecPDF(){ + const s=state.spec;if(!s){alert("ТЗ ещё не собрано");return} + const cn=esc(state.client_name||"Клиент"),nm=esc(state.niche||""); + const w=window.open("","_blank"); + let body=`

1. Обзор системы

${esc(s.overview)}

`; + body+=`

2. Роли пользователей

`;s.roles.forEach(r=>body+=`
${esc(r.name)} — ${esc(r.does)}
Доступ: ${esc(r.access)}
`); + body+=`

3. Модули системы

`;s.modules.forEach(m=>{body+=`
${esc(m.source_node)} ${esc(m.name)}
${esc(m.purpose)}
Экраны: ${m.screens.map(esc).join(", ")}
Вход: ${esc(m.inputs_data)}
Выход: ${esc(m.outputs_data)}
${m.rules.length?`
Бизнес-правила:
`:''}
`}); + body+=`

4. Модель данных

`;s.entities.forEach(e=>{body+=`
◆ ${esc(e.name)}${e.fields.map(f=>``).join("")}
ПолеТип
${esc(f.field)}${esc(f.type)}
${e.relations.length?`
Связи: ${e.relations.map(esc).join("; ")}
`:''}
${esc(e.example)}
`}); + if(s.open_questions&&s.open_questions.length){body+=`

5. Уточнить перед разработкой

`} + w.document.write(`ТЗ — ${cn} +
@wasrusgen1 · КОНСАЛТИНГ
Техническое задание
${cn}${nm?" · "+nm:""} · ${new Date().toLocaleDateString("ru-RU")}
+ ${body} + + `); + w.document.close(); +} + const TABS=[{id:"interview",name:"Интервью",icon:"💬"},{id:"methods",name:"Методологии",icon:"🎯"},{id:"canvas",name:"Стратегия",icon:"📊"},{id:"idef0",name:"Функции",icon:"🔧"},{id:"spec",name:"ТЗ",icon:"📋"}]; function approved(s){return state.approvals&&state.approvals[s]} async function approve(s){await fetch(`${API}/api/project/approve`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token:current,stage:s,approved:true})});state.approvals=state.approvals||{};state.approvals[s]=1;renderClient();} @@ -227,7 +255,7 @@ function renderTab(){const c=document.getElementById("tabContent"); else if(activeTab==="methods"){if(!state.selection){c.innerHTML=runCard("methods","🎯","Подбор методологий","Елена предложит набор методологий под тип бизнеса.","Подобрать →");return}const s=state.selection;c.innerHTML=`
Тип бизнеса
${esc(s.business_type)}
${s.recommended.map(r=>`
${r.use?'✅':'⬜'}
${r.method.toUpperCase()} [${r.depth}]
${esc(r.reason)}
`).join("")}
${esc(s.rationale)}
${cpBar("methods","Согласны с набором?")}`;} else if(activeTab==="canvas"){if(!state.canvas){c.innerHTML=runCard("canvas","📊","Business Model Canvas","Стратегия — 9 блоков.","Построить →");return}c.innerHTML=renderCanvas(state.canvas)+cpBar("canvas","Стратегия верна?");} else if(activeTab==="idef0"){if(!state.model){c.innerHTML=runCard("model","🔧","Функциональная модель IDEF0","Функции, входы/выходы, нормы, разрывы.","Построить →");return}c.innerHTML=renderIdef(state.model)+cpBar("idef0","Модель верна?");} - else if(activeTab==="spec"){if(!state.spec){if(!state.model){c.innerHTML=`
⚠️
Сначала IDEF0
ТЗ собирается из функциональной модели.
`;return}c.innerHTML=runCard("spec","📋","Техническое задание","Роли, модули, экраны, данные.","Собрать ТЗ →");return}c.innerHTML=renderSpec(state.spec)+cpBar("spec","ТЗ готово к разработке?");} + else if(activeTab==="spec"){if(!state.spec){if(!state.model){c.innerHTML=`
⚠️
Сначала IDEF0
ТЗ собирается из функциональной модели.
`;return}c.innerHTML=runCard("spec","📋","Техническое задание","Роли, модули, экраны, данные.","Собрать ТЗ →");return}c.innerHTML=`
`+renderSpec(state.spec)+cpBar("spec","ТЗ готово к разработке?");} } const BUILD={methods:["select-methodologies","selection"],canvas:["build-canvas","canvas"],model:["build-model","model"],idef0:["build-model","model"],spec:["build-spec","spec"]}; async function rerun(stage){const [ep,key]=BUILD[stage];const btn=document.getElementById(`rb-${stage}`);if(btn){btn.disabled=true;btn.innerHTML=' Елена анализирует...'}if(approved(stage))unapprove(stage);