diff --git a/docs/crm.html b/docs/crm.html index 7768722..52313e8 100644 --- a/docs/crm.html +++ b/docs/crm.html @@ -195,7 +195,7 @@ async function newClient(){ function setView(v){view=v;current=null;document.getElementById("nav-dash").classList.toggle("active",v==="dashboard");document.getElementById("nav-pipe").classList.toggle("active",v==="pipeline");renderClientList();render();} async function openClient(token){ current=token;view="client";mainTab="deal";editPayIdx=-1;document.querySelectorAll(".nav-item").forEach(n=>n.classList.remove("active")); - const r=await fetch(`${API}/api/project/${token}`);state=await r.json();renderClientList();render(); + const r=await fetch(`${API}/api/project/${token}`);state=await r.json();renderClientList();render();syncPaymentReminders(); } function render(){ if(view==="dashboard")renderDashboard(); @@ -451,14 +451,34 @@ function editStagePrice(k){ state.crm.stage_prices=state.crm.stage_prices||{}; state.crm.stage_prices[k]=n; // сумму сделки НЕ трогаем — разница показывается как «нераспределено» - saveEstimate();renderClient(); + saveEstimate();syncPaymentReminders();renderClient(); } // ── Сроки этапов (плановая дата оплаты) ── function getStageDue(){return (state.crm&&state.crm.stage_due)||{};} function setStageDue(k,v){ state.crm=state.crm||{};state.crm.stage_due=state.crm.stage_due||{}; if(v)state.crm.stage_due[k]=v; else delete state.crm.stage_due[k]; - fetch(`${API}/api/project/crm`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token:current,stage_due:state.crm.stage_due})}).then(loadProjects); + fetch(`${API}/api/project/crm`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token:current,stage_due:state.crm.stage_due})}) + .then(()=>{syncPaymentReminders();return loadProjects();}); +} +// ── Автозадачи-напоминания о сроках оплаты этапов (идемпотентно) ── +function syncPaymentReminders(){ + if(!state||!state.crm)return; + const due=getStageDue(), sp=getStagePrices()||{}, pays=getStagePays(); + state.tasks=state.tasks||[]; + let changed=false; + CLIENT_STAGES.forEach(s=>{ + const marker='pay_'+s.key; + const need=due[s.key]&&(sp[s.key]||0)>0&&!pays[s.key]; + const idx=state.tasks.findIndex(t=>t.auto===marker); + if(need){ + const text=`💳 Получить оплату за «${s.name}» — ${money(sp[s.key])}`; + if(idx<0){state.tasks.push({text,due:due[s.key],done:false,auto:marker});changed=true;} + else if(state.tasks[idx].due!==due[s.key]||state.tasks[idx].text!==text||state.tasks[idx].done){ + state.tasks[idx].due=due[s.key];state.tasks[idx].text=text;state.tasks[idx].done=false;changed=true;} + } else if(idx>=0){state.tasks.splice(idx,1);changed=true;} + }); + if(changed)saveTasks(); } // ── Подогнать сумму сделки под смету ── function alignDealToEstimate(){ @@ -516,7 +536,7 @@ async function confirmStagePay(k){ fetch(`${API}/api/project/crm`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token:current,stage_payments:pays,payments:state.crm.payments})}), loadProjects() ]); - renderClient(); + syncPaymentReminders();renderClient(); } async function unStagePay(k){ const pays=getStagePays();if(!pays[k])return; @@ -527,7 +547,7 @@ async function unStagePay(k){ for(let i=arr.length-1;i>=0;i--){if(arr[i].stage===k){arr.splice(i,1);break;}} state.crm.payments=arr; await fetch(`${API}/api/project/crm`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token:current,stage_payments:pays,payments:arr})}); - await loadProjects();renderClient(); + await loadProjects();syncPaymentReminders();renderClient(); } function renderPaymentPlan(){ const box=document.getElementById("planBox");if(!box)return;