ui: полное редактирование сметы — правка цены этапа, отмена оплаты, синхронизация с реестром

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
wasrusgen 2026-06-01 08:00:44 +03:00
parent 24bd0e29d5
commit 9b0d0af277

View File

@ -499,6 +499,17 @@ async function confirmStagePay(k){
]); ]);
renderClient(); renderClient();
} }
async function unStagePay(k){
const pays=getStagePays();if(!pays[k])return;
if(!confirm('Отменить оплату этапа «'+CLIENT_STAGES.find(s=>s.key===k).name+'»? Связанный платёж удалится из реестра.'))return;
delete pays[k];
state.crm.stage_payments=pays;
const arr=state.crm.payments||[];
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();
}
function renderPaymentPlan(){ function renderPaymentPlan(){
const box=document.getElementById("planBox");if(!box)return; const box=document.getElementById("planBox");if(!box)return;
const billing=(state.crm&&state.crm.billing_type)||"paid"; const billing=(state.crm&&state.crm.billing_type)||"paid";
@ -532,7 +543,7 @@ function renderPaymentPlan(){
<span style="font-size:11px;font-weight:700;color:var(--primary);background:#ECFDF5;padding:3px 9px;border-radius:6px">${CX_SHORT[cx]}</span> <span style="font-size:11px;font-weight:700;color:var(--primary);background:#ECFDF5;padding:3px 9px;border-radius:6px">${CX_SHORT[cx]}</span>
<button class="cp-btn cp-r" style="padding:4px 9px;margin-left:auto" onclick="buildEstimate()">↻ Пересчитать</button> <button class="cp-btn cp-r" style="padding:4px 9px;margin-left:auto" onclick="buildEstimate()">↻ Пересчитать</button>
</div> </div>
<div style="font-size:11px;color:var(--muted);margin-bottom:12px">Получено <b style="color:var(--primary)">${money(paidTotal)}</b> из ${money(total)} · нажмите цену чтобы изменить</div> <div style="font-size:11px;color:var(--muted);margin-bottom:12px">Получено <b style="color:var(--primary)">${money(paidTotal)}</b> из ${money(total)} · <b></b> — изменить цену · <b style="color:#DC2626"></b> — отменить оплату · «↻ Пересчитать» сбрасывает к ставкам</div>
${CLIENT_STAGES.map(s=>{ ${CLIENT_STAGES.map(s=>{
const done=stageIsDone(s.key); const done=stageIsDone(s.key);
const paid=pays[s.key]; const paid=pays[s.key];
@ -562,7 +573,7 @@ function renderPaymentPlan(){
<div style="font-weight:700;font-size:13px;color:${done||isFree?'var(--text)':'#9CA3AF'}">${s.name}${badge}</div> <div style="font-weight:700;font-size:13px;color:${done||isFree?'var(--text)':'#9CA3AF'}">${s.name}${badge}</div>
${sub} ${sub}
</div> </div>
${action} <div style="display:flex;align-items:center;gap:7px">${action}<span onclick="event.stopPropagation();editStagePrice('${s.key}')" title="Изменить цену этапа" style="cursor:pointer;color:#9CA3AF;font-size:14px;line-height:1"></span>${paid?`<span onclick="event.stopPropagation();unStagePay('${s.key}')" title="Отменить оплату" style="cursor:pointer;color:#DC2626;font-size:14px;line-height:1"></span>`:''}</div>
</div>`; </div>`;
}).join("")} }).join("")}
<div style="display:flex;align-items:center;gap:10px;padding:12px 0 2px;border-top:2px solid var(--bg);margin-top:4px"> <div style="display:flex;align-items:center;gap:10px;padding:12px 0 2px;border-top:2px solid var(--bg);margin-top:4px">
@ -649,7 +660,15 @@ function addPayment(){
} }
savePayments();renderClient(); savePayments();renderClient();
} }
function delPayment(i){state.crm.payments.splice(i,1);savePayments();renderClient();} function delPayment(i){
const removed=state.crm.payments[i];
state.crm.payments.splice(i,1);
if(removed&&removed.stage&&state.crm.stage_payments){
const stillPaid=state.crm.payments.some(p=>p.stage===removed.stage);
if(!stillPaid)delete state.crm.stage_payments[removed.stage];
}
savePayments();renderClient();
}
function remindBalance(left){ function remindBalance(left){
const due=new Date(Date.now()+7*864e5).toISOString().slice(0,10); const due=new Date(Date.now()+7*864e5).toISOString().slice(0,10);
state.tasks=state.tasks||[];state.tasks.push({text:`Получить остаток ${money(left)} от ${state.client_name||'клиента'}`,due,done:false}); state.tasks=state.tasks||[];state.tasks.push({text:`Получить остаток ${money(left)} от ${state.client_name||'клиента'}`,due,done:false});