diff --git a/backend/elena_app.py b/backend/elena_app.py
index 70eadb4..f186ebc 100644
--- a/backend/elena_app.py
+++ b/backend/elena_app.py
@@ -609,10 +609,13 @@ def update_crm():
return jsonify({"error": "project not found"}), 404
crm = latest_artifact(proj["id"], "crm") or {
"pipeline": "lead", "deal_amount": 0, "paid_amount": 0,
- "contact": "", "source": "", "note": ""
+ "contact": "", "source": "", "note": "", "payments": []
}
- for k in ("pipeline", "deal_amount", "paid_amount", "contact", "source", "note"):
+ for k in ("pipeline", "deal_amount", "paid_amount", "contact", "source", "note", "payments"):
if k in data: crm[k] = data[k]
+ # paid_amount = сумма платежей (если есть реестр)
+ if "payments" in crm and isinstance(crm["payments"], list):
+ crm["paid_amount"] = sum(p.get("amount", 0) for p in crm["payments"])
save_artifact(proj["id"], "crm", crm)
return jsonify({"ok": True, "crm": crm})
diff --git a/docs/crm.html b/docs/crm.html
index d9a4957..663dc83 100644
--- a/docs/crm.html
+++ b/docs/crm.html
@@ -169,15 +169,17 @@ function renderDashboard(){
const total=projects.length;
const byPipe=k=>projects.filter(p=>((p.crm&&p.crm.pipeline)||"lead")===k).length;
const active=byPipe("active"),done=byPipe("done"),leads=byPipe("lead");
- const revenue=projects.reduce((s,p)=>s+((p.crm&&p.crm.paid_amount)||0),0);
+ const paidOf=p=>{const c=p.crm||{};return (c.payments||[]).reduce((s,x)=>s+(x.amount||0),0)||c.paid_amount||0};
+ const revenue=projects.reduce((s,p)=>s+paidOf(p),0);
+ const expected=projects.reduce((s,p)=>{const c=p.crm||{};return s+Math.max(0,(c.deal_amount||0)-paidOf(p))},0);
const inwork=projects.filter(p=>((p.crm&&p.crm.pipeline)||"")==="active").reduce((s,p)=>s+((p.crm&&p.crm.deal_amount)||0),0);
const conv=total?Math.round(done/total*100):0;
document.getElementById("view").innerHTML=`
Дашборд
-
-
${active}
Активных клиентов
${money(inwork)} в работе
-
${money(revenue)}
Выручка (оплачено)
+
${leads}
Новых лидов
${active} в работе
+
${money(revenue)}
Выручка (получено)
+
${money(expected)}
Ожидается (остатки)
${money(inwork)} в активных сделках
${conv}%
Конверсия в сделку
${done} завершено
${renderUpcomingTasks()}
@@ -208,16 +210,35 @@ function renderClient(){
Статус воронки
-
+
+
${TABS.map(t=>`
${t.icon} ${t.name}
`).join("")}
`;
renderTasks();
+ renderPayments();
renderTab();
}
+function renderPayments(){
+ const crm=state.crm||{};const deal=crm.deal_amount||0;const pays=crm.payments||[];
+ const paid=pays.reduce((s,p)=>s+(p.amount||0),0);const left=deal-paid;
+ const st=paid<=0?["Не оплачено","#DC2626","#FEF2F2"]:left>0?["Частично","#92400E","#FEF3C7"]:["Оплачено","#047857","#ECFDF5"];
+ const sb=document.getElementById("payStatusBox");
+ if(sb)sb.innerHTML=`Оплата
${money(paid)}${deal>0?`${st[0]}`:''}
`;
+ const box=document.getElementById("paymentsBox");if(!box)return;
+ box.innerHTML=`
+
💰 Платежи
+ ${deal>0?`Сделка ${money(deal)} · Получено ${money(paid)} · Остаток ${money(left)}`:''}
+ ${pays.map((p,i)=>`
${esc(p.date||'')}${esc(p.note||'Платёж')}${money(p.amount)}
`).join("")||'
Платежей нет
'}
+
+
`;
+}
+async function savePayments(){await fetch(`${API}/api/project/crm`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token:current,payments:state.crm.payments})});await loadProjects();}
+function addPayment(){const amt=+document.getElementById("payAmt").value;if(!amt){alert("Укажите сумму");return}const date=document.getElementById("payDate").value;const note=document.getElementById("payNote").value;state.crm=state.crm||{};state.crm.payments=state.crm.payments||[];state.crm.payments.push({date,amount:amt,note});renderPayments();savePayments();}
+function delPayment(i){state.crm.payments.splice(i,1);renderPayments();savePayments();}
function renderTasks(){
const box=document.getElementById("tasksBox");if(!box)return;
const tasks=state.tasks||[];
@@ -234,7 +255,7 @@ function addTask(){const t=document.getElementById("newTask").value.trim();if(!t
function toggleTask(i){state.tasks[i].done=!state.tasks[i].done;renderTasks();saveTasks();}
function delTask(i){state.tasks.splice(i,1);renderTasks();saveTasks();}
async function saveCrm(){
- const crm={pipeline:document.getElementById("cpPipe").value,deal_amount:+document.getElementById("cpDeal").value||0,paid_amount:+document.getElementById("cpPaid").value||0,source:document.getElementById("cpSrc").value};
+ const crm={pipeline:document.getElementById("cpPipe").value,deal_amount:+document.getElementById("cpDeal").value||0,source:document.getElementById("cpSrc").value};
state.crm={...state.crm,...crm};
await fetch(`${API}/api/project/crm`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token:current,...crm})});
await loadProjects();