feat(платежи): автозадачи-напоминания о сроках оплаты этапов

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
wasrusgen 2026-06-01 09:02:00 +03:00
parent 066a628695
commit 2fc91433da

View File

@ -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;