mirror of
https://github.com/wasrusgen/wasrusgen1-crm.git
synced 2026-06-03 14:04:46 +00:00
fix: payment stages from client progress (5 analysis stages), no generator; rule: 100% paid = ТЗ unlocked; backend adds stage_payments
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
78b3e22eb6
commit
d6923e5ad1
105
Mokap/crm.html
105
Mokap/crm.html
@ -306,51 +306,78 @@ async function setBilling(t){
|
||||
renderClient();await loadProjects();
|
||||
}
|
||||
// ── План оплаты (привязка к вехам анализа) ──
|
||||
const PAY_TRIGGERS=[{key:"start",name:"Старт работы"},{key:"methods",name:"Согл. методологий"},{key:"canvas",name:"Согл. стратегии"},{key:"idef0",name:"Согл. модели IDEF0"},{key:"spec",name:"Выдача ТЗ"}];
|
||||
function trigName(k){const t=PAY_TRIGGERS.find(x=>x.key===k);return t?t.name:k;}
|
||||
function genSchedule(type){
|
||||
const deal=(state.crm&&state.crm.deal_amount)||0;
|
||||
if(!deal){alert("Сначала укажите сумму сделки (вкладка «Сделка» или «Ценообразование»).");return;}
|
||||
let arr;
|
||||
if(type==="full")arr=[{name:"Предоплата 100%",amount:deal,trigger:"start",status:"pending"}];
|
||||
else if(type==="half"){const a=Math.round(deal/2);arr=[{name:"Аванс 50%",amount:a,trigger:"start",status:"pending"},{name:"Постоплата 50%",amount:deal-a,trigger:"spec",status:"pending"}];}
|
||||
else{const a=Math.round(deal*0.4),b=Math.round(deal*0.3);arr=[{name:"Аванс 40%",amount:a,trigger:"start",status:"pending"},{name:"Промежуточный 30%",amount:b,trigger:"idef0",status:"pending"},{name:"Финал 30%",amount:deal-a-b,trigger:"spec",status:"pending"}];}
|
||||
arr.forEach((s,i)=>s.id="s"+i);
|
||||
state.crm=state.crm||{};state.crm.payment_schedule=arr;
|
||||
saveSchedule();renderClient();
|
||||
// ── Этапы оплаты — от прогресса клиента ──────────────────
|
||||
// Этапы = 5 этапов анализа. Отмечаем оплату по мере прохождения.
|
||||
// Правило: ТЗ (печать + выгрузка) только после 100% оплаты.
|
||||
const CLIENT_STAGES=[
|
||||
{key:"interview", name:"Интервью", icon:"💬"},
|
||||
{key:"methods", name:"Методологии", icon:"🎯"},
|
||||
{key:"canvas", name:"Стратегия", icon:"📊"},
|
||||
{key:"idef0", name:"Функции IDEF0", icon:"🔧"},
|
||||
{key:"spec", name:"Выдача ТЗ", icon:"📋"},
|
||||
];
|
||||
function stagePayKey(k){return "pay_"+k;}
|
||||
function stageIsDone(k){
|
||||
if(k==="interview") return (state.messages||[]).length>0;
|
||||
if(k==="methods") return !!state.selection;
|
||||
if(k==="canvas") return !!state.canvas;
|
||||
if(k==="idef0") return !!(state.model);
|
||||
if(k==="spec") return !!state.spec;
|
||||
return false;
|
||||
}
|
||||
async function saveSchedule(){await fetch(`${API}/api/project/crm`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token:current,payment_schedule:state.crm.payment_schedule})});await loadProjects();}
|
||||
function clearSchedule(){if(!confirm("Сменить схему плана оплаты? Реестр фактических платежей не затрагивается."))return;state.crm.payment_schedule=[];saveSchedule();renderClient();}
|
||||
function markStagePaid(i){
|
||||
const s=state.crm.payment_schedule[i];if(!s||s.status==="paid")return;
|
||||
s.status="paid";s.paid_at=new Date().toISOString().slice(0,10);
|
||||
function getStagePays(){return (state.crm&&state.crm.stage_payments)||{};}
|
||||
async function markStagePaid(k){
|
||||
const deal=(state.crm&&state.crm.deal_amount)||0;
|
||||
const pays=getStagePays();
|
||||
if(pays[k])return;
|
||||
const amt=prompt(`Сумма оплаты за этап «${CLIENT_STAGES.find(s=>s.key===k).name}» (₽):`,deal>0?Math.round(deal/5):"");
|
||||
if(!amt||isNaN(+amt))return;
|
||||
const today=new Date().toISOString().slice(0,10);
|
||||
pays[k]={amount:+amt,date:today};
|
||||
state.crm=state.crm||{};
|
||||
state.crm.stage_payments=pays;
|
||||
state.crm.payments=state.crm.payments||[];
|
||||
state.crm.payments.push({date:s.paid_at,amount:s.amount,note:"Этап: "+s.name});
|
||||
Promise.all([saveSchedule(),savePayments()]).then(()=>renderClient());
|
||||
state.crm.payments.push({date:today,amount:+amt,note:"Этап: "+CLIENT_STAGES.find(s=>s.key===k).name});
|
||||
await Promise.all([
|
||||
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();
|
||||
}
|
||||
function renderPaymentPlan(){
|
||||
const box=document.getElementById("planBox");if(!box)return;
|
||||
const sch=(state.crm&&state.crm.payment_schedule)||[];
|
||||
if(!sch.length){
|
||||
box.innerHTML=`<div class="blk" style="margin-bottom:14px"><div style="font-size:13px;font-weight:700;margin-bottom:4px">💼 План оплаты</div><div style="font-size:12px;color:var(--muted);margin-bottom:12px">Выберите схему — система раскидает суммы от суммы сделки и привяжет к вехам анализа.</div>
|
||||
<div style="display:flex;gap:8px;flex-wrap:wrap">
|
||||
<button class="cp-btn cp-a" onclick="genSchedule('full')">100% предоплата</button>
|
||||
<button class="cp-btn cp-a" onclick="genSchedule('half')">Аванс 50% + 50%</button>
|
||||
<button class="cp-btn cp-a" onclick="genSchedule('staged')">Поэтапно (3 платежа)</button>
|
||||
</div></div>`;
|
||||
return;
|
||||
}
|
||||
const total=sch.reduce((s,x)=>s+(x.amount||0),0);
|
||||
const paid=sch.filter(x=>x.status==="paid").reduce((s,x)=>s+(x.amount||0),0);
|
||||
const deal=(state.crm&&state.crm.deal_amount)||0;
|
||||
const pays=getStagePays();
|
||||
const paidTotal=Object.values(pays).reduce((s,p)=>s+(p.amount||0),0);
|
||||
const billing=(state.crm&&state.crm.billing_type)||"paid";
|
||||
const isUnlocked=billing==="free"||(deal>0&&paidTotal>=deal);
|
||||
box.innerHTML=`<div class="blk" style="margin-bottom:14px">
|
||||
<div style="display:flex;align-items:center;margin-bottom:6px"><span style="font-size:13px;font-weight:700">💼 План оплаты</span><span style="margin-left:auto;font-size:12px;color:var(--muted)">Оплачено ${money(paid)} из ${money(total)}</span><button class="cp-btn cp-r" style="margin-left:10px;padding:5px 10px" onclick="clearSchedule()">↻ Сменить</button></div>
|
||||
${sch.map((s,i)=>{const isPaid=s.status==="paid";return `<div style="display:flex;align-items:center;gap:12px;padding:10px 0;border-top:1px solid var(--bg)">
|
||||
<span style="width:24px;height:24px;border-radius:50%;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:800;background:${isPaid?'#047857':'#F1F5F9'};color:${isPaid?'#fff':'#9ca3af'}">${isPaid?'✓':i+1}</span>
|
||||
<div style="flex:1;min-width:0"><div style="font-weight:700;font-size:13px">${esc(s.name)}</div><div style="font-size:11px;color:var(--muted)">Веха: ${esc(trigName(s.trigger))}${isPaid&&s.paid_at?` · оплачен ${fmtDate(s.paid_at)}`:''}</div></div>
|
||||
<span style="font-weight:700;color:var(--primary);white-space:nowrap">${money(s.amount)}</span>
|
||||
${isPaid?`<span style="font-size:11px;font-weight:700;color:#047857;background:#ECFDF5;padding:4px 10px;border-radius:6px;white-space:nowrap">Оплачен</span>`:`<button class="cp-btn cp-a" style="padding:6px 11px;white-space:nowrap" onclick="markStagePaid(${i})">Отметить оплату</button>`}
|
||||
</div>`}).join("")}
|
||||
<div style="margin-top:10px;padding:9px 12px;background:linear-gradient(135deg,rgba(4,120,87,.05),transparent);border-radius:8px;font-size:11.5px;color:var(--muted)">📄 ТЗ выдаётся клиенту (печать + выгрузка) после полной оплаты — финальный этап «Выдача ТЗ».</div>
|
||||
<div style="display:flex;align-items:center;gap:10px;margin-bottom:14px">
|
||||
<span style="font-size:13px;font-weight:700">💼 Прогресс оплаты по этапам</span>
|
||||
${deal>0?`<span style="margin-left:auto;font-size:12px;color:var(--muted)">Получено <b style="color:var(--primary)">${money(paidTotal)}</b> из ${money(deal)}</span>`:'<span style="margin-left:auto;font-size:11px;color:#f59e0b">⚠ Укажите сумму сделки во вкладке «Сделка»</span>'}
|
||||
</div>
|
||||
${CLIENT_STAGES.map(s=>{
|
||||
const done=stageIsDone(s.key);
|
||||
const paid=pays[s.key];
|
||||
return `<div style="display:flex;align-items:center;gap:12px;padding:11px 0;border-top:1px solid var(--bg)">
|
||||
<span style="width:32px;height:32px;border-radius:8px;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:15px;background:${done?'#ECFDF5':'#F9FAFB'};border:1.5px solid ${done?'#6EE7B7':'#E5E7EB'}">${s.icon}</span>
|
||||
<div style="flex:1;min-width:0">
|
||||
<div style="font-weight:700;font-size:13px;color:${done?'var(--text)':'#9CA3AF'}">${s.name}${done?' <span style="font-size:10px;font-weight:700;color:#047857;background:#D1FAE5;padding:1px 6px;border-radius:4px;margin-left:4px">✓ Выполнен</span>':''}</div>
|
||||
${paid?`<div style="font-size:11px;color:#047857;margin-top:2px">Оплачено ${money(paid.amount)} · ${fmtDate(paid.date)}</div>`:done?`<div style="font-size:11px;color:var(--muted);margin-top:2px">Этап выполнен — можно запросить оплату</div>`:`<div style="font-size:11px;color:#CBD5E1;margin-top:2px">Ожидается выполнение этапа</div>`}
|
||||
</div>
|
||||
${paid
|
||||
?`<span style="font-size:11px;font-weight:700;color:#047857;background:#ECFDF5;padding:5px 11px;border-radius:8px;white-space:nowrap">${money(paid.amount)}</span>`
|
||||
:done
|
||||
?`<button class="cp-btn cp-a" style="white-space:nowrap" onclick="markStagePaid('${s.key}')">Отметить оплату</button>`
|
||||
:`<span style="font-size:11px;color:#E5E7EB;font-weight:600">—</span>`
|
||||
}
|
||||
</div>`;
|
||||
}).join("")}
|
||||
<div style="margin-top:12px;padding:11px 14px;border-radius:10px;font-size:12px;line-height:1.5;${isUnlocked?'background:#ECFDF5;border:1px solid #6EE7B7;color:#047857':'background:#FFF7ED;border:1px solid #FDE68A;color:#92400E'}">
|
||||
${isUnlocked
|
||||
?`✅ <b>Оплата получена.</b> Клиент может скачать ТЗ и печатные документы.`
|
||||
:`🔒 <b>Правило:</b> ТЗ (печать + выгрузка) выдаётся клиенту только после получения полной суммы — ${money(deal)}.`}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
function renderPricing(){
|
||||
|
||||
@ -645,7 +645,7 @@ def update_crm():
|
||||
"pipeline": "lead", "deal_amount": 0, "paid_amount": 0,
|
||||
"contact": "", "source": "", "note": "", "payments": [], "billing_type": "paid"
|
||||
}
|
||||
for k in ("pipeline", "deal_amount", "paid_amount", "contact", "source", "note", "payments", "billing_type", "payment_schedule"):
|
||||
for k in ("pipeline", "deal_amount", "paid_amount", "contact", "source", "note", "payments", "billing_type", "payment_schedule", "stage_payments"):
|
||||
if k in data: crm[k] = data[k]
|
||||
# paid_amount = сумма платежей (если есть реестр)
|
||||
if "payments" in crm and isinstance(crm["payments"], list):
|
||||
|
||||
105
docs/crm.html
105
docs/crm.html
@ -306,51 +306,78 @@ async function setBilling(t){
|
||||
renderClient();await loadProjects();
|
||||
}
|
||||
// ── План оплаты (привязка к вехам анализа) ──
|
||||
const PAY_TRIGGERS=[{key:"start",name:"Старт работы"},{key:"methods",name:"Согл. методологий"},{key:"canvas",name:"Согл. стратегии"},{key:"idef0",name:"Согл. модели IDEF0"},{key:"spec",name:"Выдача ТЗ"}];
|
||||
function trigName(k){const t=PAY_TRIGGERS.find(x=>x.key===k);return t?t.name:k;}
|
||||
function genSchedule(type){
|
||||
const deal=(state.crm&&state.crm.deal_amount)||0;
|
||||
if(!deal){alert("Сначала укажите сумму сделки (вкладка «Сделка» или «Ценообразование»).");return;}
|
||||
let arr;
|
||||
if(type==="full")arr=[{name:"Предоплата 100%",amount:deal,trigger:"start",status:"pending"}];
|
||||
else if(type==="half"){const a=Math.round(deal/2);arr=[{name:"Аванс 50%",amount:a,trigger:"start",status:"pending"},{name:"Постоплата 50%",amount:deal-a,trigger:"spec",status:"pending"}];}
|
||||
else{const a=Math.round(deal*0.4),b=Math.round(deal*0.3);arr=[{name:"Аванс 40%",amount:a,trigger:"start",status:"pending"},{name:"Промежуточный 30%",amount:b,trigger:"idef0",status:"pending"},{name:"Финал 30%",amount:deal-a-b,trigger:"spec",status:"pending"}];}
|
||||
arr.forEach((s,i)=>s.id="s"+i);
|
||||
state.crm=state.crm||{};state.crm.payment_schedule=arr;
|
||||
saveSchedule();renderClient();
|
||||
// ── Этапы оплаты — от прогресса клиента ──────────────────
|
||||
// Этапы = 5 этапов анализа. Отмечаем оплату по мере прохождения.
|
||||
// Правило: ТЗ (печать + выгрузка) только после 100% оплаты.
|
||||
const CLIENT_STAGES=[
|
||||
{key:"interview", name:"Интервью", icon:"💬"},
|
||||
{key:"methods", name:"Методологии", icon:"🎯"},
|
||||
{key:"canvas", name:"Стратегия", icon:"📊"},
|
||||
{key:"idef0", name:"Функции IDEF0", icon:"🔧"},
|
||||
{key:"spec", name:"Выдача ТЗ", icon:"📋"},
|
||||
];
|
||||
function stagePayKey(k){return "pay_"+k;}
|
||||
function stageIsDone(k){
|
||||
if(k==="interview") return (state.messages||[]).length>0;
|
||||
if(k==="methods") return !!state.selection;
|
||||
if(k==="canvas") return !!state.canvas;
|
||||
if(k==="idef0") return !!(state.model);
|
||||
if(k==="spec") return !!state.spec;
|
||||
return false;
|
||||
}
|
||||
async function saveSchedule(){await fetch(`${API}/api/project/crm`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token:current,payment_schedule:state.crm.payment_schedule})});await loadProjects();}
|
||||
function clearSchedule(){if(!confirm("Сменить схему плана оплаты? Реестр фактических платежей не затрагивается."))return;state.crm.payment_schedule=[];saveSchedule();renderClient();}
|
||||
function markStagePaid(i){
|
||||
const s=state.crm.payment_schedule[i];if(!s||s.status==="paid")return;
|
||||
s.status="paid";s.paid_at=new Date().toISOString().slice(0,10);
|
||||
function getStagePays(){return (state.crm&&state.crm.stage_payments)||{};}
|
||||
async function markStagePaid(k){
|
||||
const deal=(state.crm&&state.crm.deal_amount)||0;
|
||||
const pays=getStagePays();
|
||||
if(pays[k])return;
|
||||
const amt=prompt(`Сумма оплаты за этап «${CLIENT_STAGES.find(s=>s.key===k).name}» (₽):`,deal>0?Math.round(deal/5):"");
|
||||
if(!amt||isNaN(+amt))return;
|
||||
const today=new Date().toISOString().slice(0,10);
|
||||
pays[k]={amount:+amt,date:today};
|
||||
state.crm=state.crm||{};
|
||||
state.crm.stage_payments=pays;
|
||||
state.crm.payments=state.crm.payments||[];
|
||||
state.crm.payments.push({date:s.paid_at,amount:s.amount,note:"Этап: "+s.name});
|
||||
Promise.all([saveSchedule(),savePayments()]).then(()=>renderClient());
|
||||
state.crm.payments.push({date:today,amount:+amt,note:"Этап: "+CLIENT_STAGES.find(s=>s.key===k).name});
|
||||
await Promise.all([
|
||||
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();
|
||||
}
|
||||
function renderPaymentPlan(){
|
||||
const box=document.getElementById("planBox");if(!box)return;
|
||||
const sch=(state.crm&&state.crm.payment_schedule)||[];
|
||||
if(!sch.length){
|
||||
box.innerHTML=`<div class="blk" style="margin-bottom:14px"><div style="font-size:13px;font-weight:700;margin-bottom:4px">💼 План оплаты</div><div style="font-size:12px;color:var(--muted);margin-bottom:12px">Выберите схему — система раскидает суммы от суммы сделки и привяжет к вехам анализа.</div>
|
||||
<div style="display:flex;gap:8px;flex-wrap:wrap">
|
||||
<button class="cp-btn cp-a" onclick="genSchedule('full')">100% предоплата</button>
|
||||
<button class="cp-btn cp-a" onclick="genSchedule('half')">Аванс 50% + 50%</button>
|
||||
<button class="cp-btn cp-a" onclick="genSchedule('staged')">Поэтапно (3 платежа)</button>
|
||||
</div></div>`;
|
||||
return;
|
||||
}
|
||||
const total=sch.reduce((s,x)=>s+(x.amount||0),0);
|
||||
const paid=sch.filter(x=>x.status==="paid").reduce((s,x)=>s+(x.amount||0),0);
|
||||
const deal=(state.crm&&state.crm.deal_amount)||0;
|
||||
const pays=getStagePays();
|
||||
const paidTotal=Object.values(pays).reduce((s,p)=>s+(p.amount||0),0);
|
||||
const billing=(state.crm&&state.crm.billing_type)||"paid";
|
||||
const isUnlocked=billing==="free"||(deal>0&&paidTotal>=deal);
|
||||
box.innerHTML=`<div class="blk" style="margin-bottom:14px">
|
||||
<div style="display:flex;align-items:center;margin-bottom:6px"><span style="font-size:13px;font-weight:700">💼 План оплаты</span><span style="margin-left:auto;font-size:12px;color:var(--muted)">Оплачено ${money(paid)} из ${money(total)}</span><button class="cp-btn cp-r" style="margin-left:10px;padding:5px 10px" onclick="clearSchedule()">↻ Сменить</button></div>
|
||||
${sch.map((s,i)=>{const isPaid=s.status==="paid";return `<div style="display:flex;align-items:center;gap:12px;padding:10px 0;border-top:1px solid var(--bg)">
|
||||
<span style="width:24px;height:24px;border-radius:50%;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:800;background:${isPaid?'#047857':'#F1F5F9'};color:${isPaid?'#fff':'#9ca3af'}">${isPaid?'✓':i+1}</span>
|
||||
<div style="flex:1;min-width:0"><div style="font-weight:700;font-size:13px">${esc(s.name)}</div><div style="font-size:11px;color:var(--muted)">Веха: ${esc(trigName(s.trigger))}${isPaid&&s.paid_at?` · оплачен ${fmtDate(s.paid_at)}`:''}</div></div>
|
||||
<span style="font-weight:700;color:var(--primary);white-space:nowrap">${money(s.amount)}</span>
|
||||
${isPaid?`<span style="font-size:11px;font-weight:700;color:#047857;background:#ECFDF5;padding:4px 10px;border-radius:6px;white-space:nowrap">Оплачен</span>`:`<button class="cp-btn cp-a" style="padding:6px 11px;white-space:nowrap" onclick="markStagePaid(${i})">Отметить оплату</button>`}
|
||||
</div>`}).join("")}
|
||||
<div style="margin-top:10px;padding:9px 12px;background:linear-gradient(135deg,rgba(4,120,87,.05),transparent);border-radius:8px;font-size:11.5px;color:var(--muted)">📄 ТЗ выдаётся клиенту (печать + выгрузка) после полной оплаты — финальный этап «Выдача ТЗ».</div>
|
||||
<div style="display:flex;align-items:center;gap:10px;margin-bottom:14px">
|
||||
<span style="font-size:13px;font-weight:700">💼 Прогресс оплаты по этапам</span>
|
||||
${deal>0?`<span style="margin-left:auto;font-size:12px;color:var(--muted)">Получено <b style="color:var(--primary)">${money(paidTotal)}</b> из ${money(deal)}</span>`:'<span style="margin-left:auto;font-size:11px;color:#f59e0b">⚠ Укажите сумму сделки во вкладке «Сделка»</span>'}
|
||||
</div>
|
||||
${CLIENT_STAGES.map(s=>{
|
||||
const done=stageIsDone(s.key);
|
||||
const paid=pays[s.key];
|
||||
return `<div style="display:flex;align-items:center;gap:12px;padding:11px 0;border-top:1px solid var(--bg)">
|
||||
<span style="width:32px;height:32px;border-radius:8px;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:15px;background:${done?'#ECFDF5':'#F9FAFB'};border:1.5px solid ${done?'#6EE7B7':'#E5E7EB'}">${s.icon}</span>
|
||||
<div style="flex:1;min-width:0">
|
||||
<div style="font-weight:700;font-size:13px;color:${done?'var(--text)':'#9CA3AF'}">${s.name}${done?' <span style="font-size:10px;font-weight:700;color:#047857;background:#D1FAE5;padding:1px 6px;border-radius:4px;margin-left:4px">✓ Выполнен</span>':''}</div>
|
||||
${paid?`<div style="font-size:11px;color:#047857;margin-top:2px">Оплачено ${money(paid.amount)} · ${fmtDate(paid.date)}</div>`:done?`<div style="font-size:11px;color:var(--muted);margin-top:2px">Этап выполнен — можно запросить оплату</div>`:`<div style="font-size:11px;color:#CBD5E1;margin-top:2px">Ожидается выполнение этапа</div>`}
|
||||
</div>
|
||||
${paid
|
||||
?`<span style="font-size:11px;font-weight:700;color:#047857;background:#ECFDF5;padding:5px 11px;border-radius:8px;white-space:nowrap">${money(paid.amount)}</span>`
|
||||
:done
|
||||
?`<button class="cp-btn cp-a" style="white-space:nowrap" onclick="markStagePaid('${s.key}')">Отметить оплату</button>`
|
||||
:`<span style="font-size:11px;color:#E5E7EB;font-weight:600">—</span>`
|
||||
}
|
||||
</div>`;
|
||||
}).join("")}
|
||||
<div style="margin-top:12px;padding:11px 14px;border-radius:10px;font-size:12px;line-height:1.5;${isUnlocked?'background:#ECFDF5;border:1px solid #6EE7B7;color:#047857':'background:#FFF7ED;border:1px solid #FDE68A;color:#92400E'}">
|
||||
${isUnlocked
|
||||
?`✅ <b>Оплата получена.</b> Клиент может скачать ТЗ и печатные документы.`
|
||||
:`🔒 <b>Правило:</b> ТЗ (печать + выгрузка) выдаётся клиенту только после получения полной суммы — ${money(deal)}.`}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
function renderPricing(){
|
||||
|
||||
Loading…
Reference in New Issue
Block a user