From 2fc91433da023e86fd2badd1dd7c99c8d0f6ee31 Mon Sep 17 00:00:00 2001 From: wasrusgen Date: Mon, 1 Jun 2026 09:02:00 +0300 Subject: [PATCH] =?UTF-8?q?feat(=D0=BF=D0=BB=D0=B0=D1=82=D0=B5=D0=B6=D0=B8?= =?UTF-8?q?):=20=D0=B0=D0=B2=D1=82=D0=BE=D0=B7=D0=B0=D0=B4=D0=B0=D1=87?= =?UTF-8?q?=D0=B8-=D0=BD=D0=B0=D0=BF=D0=BE=D0=BC=D0=B8=D0=BD=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=BE=20=D1=81=D1=80=D0=BE=D0=BA=D0=B0=D1=85=20?= =?UTF-8?q?=D0=BE=D0=BF=D0=BB=D0=B0=D1=82=D1=8B=20=D1=8D=D1=82=D0=B0=D0=BF?= =?UTF-8?q?=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/crm.html | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) 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;