mirror of
https://github.com/wasrusgen/wasrusgen1-crm.git
synced 2026-06-03 15:44:45 +00:00
feat(платежи): автозадачи-напоминания о сроках оплаты этапов
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
066a628695
commit
2fc91433da
@ -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();}
|
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){
|
async function openClient(token){
|
||||||
current=token;view="client";mainTab="deal";editPayIdx=-1;document.querySelectorAll(".nav-item").forEach(n=>n.classList.remove("active"));
|
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(){
|
function render(){
|
||||||
if(view==="dashboard")renderDashboard();
|
if(view==="dashboard")renderDashboard();
|
||||||
@ -451,14 +451,34 @@ function editStagePrice(k){
|
|||||||
state.crm.stage_prices=state.crm.stage_prices||{};
|
state.crm.stage_prices=state.crm.stage_prices||{};
|
||||||
state.crm.stage_prices[k]=n;
|
state.crm.stage_prices[k]=n;
|
||||||
// сумму сделки НЕ трогаем — разница показывается как «нераспределено»
|
// сумму сделки НЕ трогаем — разница показывается как «нераспределено»
|
||||||
saveEstimate();renderClient();
|
saveEstimate();syncPaymentReminders();renderClient();
|
||||||
}
|
}
|
||||||
// ── Сроки этапов (плановая дата оплаты) ──
|
// ── Сроки этапов (плановая дата оплаты) ──
|
||||||
function getStageDue(){return (state.crm&&state.crm.stage_due)||{};}
|
function getStageDue(){return (state.crm&&state.crm.stage_due)||{};}
|
||||||
function setStageDue(k,v){
|
function setStageDue(k,v){
|
||||||
state.crm=state.crm||{};state.crm.stage_due=state.crm.stage_due||{};
|
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];
|
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(){
|
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})}),
|
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()
|
loadProjects()
|
||||||
]);
|
]);
|
||||||
renderClient();
|
syncPaymentReminders();renderClient();
|
||||||
}
|
}
|
||||||
async function unStagePay(k){
|
async function unStagePay(k){
|
||||||
const pays=getStagePays();if(!pays[k])return;
|
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;}}
|
for(let i=arr.length-1;i>=0;i--){if(arr[i].stage===k){arr.splice(i,1);break;}}
|
||||||
state.crm.payments=arr;
|
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 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(){
|
function renderPaymentPlan(){
|
||||||
const box=document.getElementById("planBox");if(!box)return;
|
const box=document.getElementById("planBox");if(!box)return;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user