|
|
|
|
@@ -96,7 +96,7 @@ function renderPlan() {
|
|
|
|
|
})();
|
|
|
|
|
const sm = pf.sign_month || "";
|
|
|
|
|
const signMonthCell = `<td class="p-2 text-center align-middle text-sm"><span class="pf-sm-text cursor-pointer hover:text-blue-600" id="pf-sm-${pf.id}" onclick="event.stopPropagation(); planEditSignMonth(event, ${pf.id})">${sm || '—'}</span></td>`;
|
|
|
|
|
return `<tr class="border-b border-slate-100 hover:bg-slate-50 cursor-pointer" onclick="planOpenPfEditModal(${pf.id})"><td class="p-2 text-sm font-medium text-center align-middle">${esc(pf.customer_name)}</td>${signMonthCell}<td class="p-2 text-center align-middle text-sm">${money(pf.sign_amount)}</td>${mCols}${totalCol}</tr>`;
|
|
|
|
|
return `<tr class="border-b border-slate-100 hover:bg-slate-50 cursor-pointer" onclick="planOpenPfEditModal(${pf.id})"><td class="p-2 text-center align-middle text-sm">${esc(pf.client_name || "")}</td><td class="p-2 text-sm font-medium text-center align-middle">${esc(pf.customer_name)}</td><td class="p-2 text-center align-middle text-sm">${esc(pf.weight || "20%")}</td><td class="p-2 text-center align-middle text-sm">${esc(pf.project_type || "待签约")}</td>${signMonthCell}<td class="p-2 text-center align-middle text-sm">${money(pf.sign_amount)}</td>${mCols}${totalCol}</tr>`;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const now2 = new Date();
|
|
|
|
|
@@ -150,7 +150,7 @@ function renderPlan() {
|
|
|
|
|
</div>
|
|
|
|
|
<div class="grid grid-cols-3 gap-3">
|
|
|
|
|
<label class="block"><span class="fin-label">联系电话</span><input name="contact_phone" class="form-ctrl" placeholder="请输入联系电话"></label>
|
|
|
|
|
<label class="block"><span class="fin-label">其他</span><input name="other_info" class="form-ctrl" placeholder="备注信息"></label>
|
|
|
|
|
<label class="block"><span class="fin-label">其他</span><input name="other_info" class="form-ctrl" placeholder="备注信息"></label></div><div class="grid grid-cols-3 gap-3"><label class="block"><span class="fin-label">客户名称</span><input name="client_name" class="form-ctrl" placeholder="客户名称"></label><label class="block"><span class="fin-label">项目类型</span><select name="project_type" class="form-ctrl bg-white"><option>待签约</option><option>已签约</option></select></label><label class="block"><span class="fin-label">加权</span><select name="weight" class="form-ctrl bg-white"><option>20%</option><option>40%</option><option>60%</option><option>80%</option><option>100%</option></select></label>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="block"><span class="fin-label">业务类型</span><div class="flex flex-wrap gap-2 mt-1" id="plan_businessTypeChecks">${fmTypes.map(t => `<label class="inline-flex items-center gap-1 px-2.5 py-1 rounded-full border border-slate-200 cursor-pointer hover:border-blue-300 text-sm bt-chip" onclick="planToggleBtChip(this)"><input type="checkbox" name="business_type[]" value="${t}" class="hidden"><span>${t}</span></label>`).join("")}</div></div>
|
|
|
|
|
</div>
|
|
|
|
|
@@ -285,8 +285,10 @@ function renderPlan() {
|
|
|
|
|
return h;
|
|
|
|
|
};
|
|
|
|
|
const SIGNED_COLS = [
|
|
|
|
|
{ key: 'client_name', label: '客户' },
|
|
|
|
|
{ key: 'customer_name', label: '项目名称' },
|
|
|
|
|
,
|
|
|
|
|
{ key: 'weight', label: '加权' },
|
|
|
|
|
{ key: 'project_type', label: '项目类型' },
|
|
|
|
|
{ key: 'sign_amount', label: '签约金额' },
|
|
|
|
|
{ key: 'rev', label: '已确收' },
|
|
|
|
|
{ key: 'gross', label: '毛利' },
|
|
|
|
|
@@ -296,13 +298,13 @@ function renderPlan() {
|
|
|
|
|
{ key: 'paid', label: '已付' },
|
|
|
|
|
{ key: 'cashflow', label: '现金流' },
|
|
|
|
|
{ key: 'exe_rate', label: '执行率' },
|
|
|
|
|
{ key: 'gross_rate', label: '毛利率' },
|
|
|
|
|
{ key: 'pay_rate', label: '回款率' },
|
|
|
|
|
{ key: 'paid_rate', label: '付款率' },
|
|
|
|
|
];
|
|
|
|
|
const UNSIGNED_COLS = [
|
|
|
|
|
{ key: 'client_name', label: '客户' },
|
|
|
|
|
{ key: 'customer_name', label: '项目名称' },
|
|
|
|
|
,
|
|
|
|
|
{ key: 'weight', label: '加权' },
|
|
|
|
|
{ key: 'project_type', label: '项目类型' },
|
|
|
|
|
{ key: 'sign_amount', label: '签约金额' },
|
|
|
|
|
{ key: 'sign_month', label: '签约月份' },
|
|
|
|
|
{ key: 'gross', label: '毛利' },
|
|
|
|
|
@@ -317,7 +319,7 @@ function renderPlan() {
|
|
|
|
|
return typeof v === 'object' ? [c.label, v.val, v.cls] : [c.label, v, c.color];
|
|
|
|
|
});
|
|
|
|
|
const thead = isUnsigned ? buildThead(UNSIGNED_COLS, 'uns') : buildThead(SIGNED_COLS, 'sig');
|
|
|
|
|
const theadCols = isUnsigned ? 6 : 13;
|
|
|
|
|
const theadCols = isUnsigned ? 9 : 14;
|
|
|
|
|
const tbody = rows.length ? `<tbody>${rows.join("")}</tbody>` : `<tr><td colspan="${theadCols}" class="p-6 text-center text-slate-400">${emptyText}</td></tr>`;
|
|
|
|
|
|
|
|
|
|
const cardGrid = isUnsigned ? 'grid-cols-4' : 'grid-cols-6';
|
|
|
|
|
@@ -342,14 +344,14 @@ function renderPlan() {
|
|
|
|
|
const profit = gross;
|
|
|
|
|
const pfCls = profit >= 0 ? 'text-green-600' : 'text-red-600';
|
|
|
|
|
const pfVal = profit ? money(profit) : '<span class="text-slate-300">—</span>';
|
|
|
|
|
return `<tr class="border-b border-slate-100 hover:bg-slate-50 cursor-pointer" onclick="planOpenPfEditModal(${pf.id})"><td class="p-2 text-sm font-medium text-center align-middle truncate" style="min-width:300px;max-width:300px;width:300px" title="${esc(pf.customer_name)}">${esc(pf.customer_name)}</td><td class="p-2 text-center align-middle text-sm font-medium">${money(pf.sign_amount)}</td><td class="p-2 text-center text-blue-700 align-middle">${fmtNoUnit(rev)}</td><td class="p-2 text-center align-middle text-sm font-medium">${fmtNoUnit(gross)}</td><td class="p-2 text-center text-rose-700 align-middle">${fmtNoUnit(cost)}</td><td class="p-2 text-center font-semibold align-middle ${pfCls}">${pfVal}</td><td class="p-2 text-center text-amber-700 align-middle">${fmtNoUnit(payment)}</td><td class="p-2 text-center text-purple-700 align-middle">${fmtNoUnit(paid)}</td><td class="p-2 text-center font-semibold align-middle ${cfCls}">${cf}</td><td class="p-2 text-center align-middle text-sm">${exe}</td><td class="p-2 text-center align-middle text-sm">${grossR}</td><td class="p-2 text-center align-middle text-sm">${payR}</td><td class="p-2 text-center align-middle text-sm">${paidR}</td></tr>`;
|
|
|
|
|
return `<tr class="border-b border-slate-100 hover:bg-slate-50 cursor-pointer" onclick="planOpenPfEditModal(${pf.id})"><td class="p-2 text-center align-middle text-sm">${esc(pf.client_name || "")}</td><td class="p-2 text-sm font-medium text-center align-middle truncate" style="min-width:300px;max-width:300px;width:300px" title="${esc(pf.customer_name)}">${esc(pf.customer_name)}</td><td class="p-2 text-center align-middle text-sm" onclick="event.stopPropagation()">${planInlineSelect('weight', pf.id, 'weight', pf.weight || '20%', ['20%','40%','60%','80%','100%'])}</td><td class="p-2 text-center align-middle text-sm" onclick="event.stopPropagation()">${planInlineSelect('ptype', pf.id, 'project_type', pf.project_type || '待签约', ['待签约','已签约'])}</td><td class="p-2 text-center align-middle text-sm font-medium">${money(pf.sign_amount)}</td><td class="p-2 text-center text-blue-700 align-middle">${fmtNoUnit(rev)}</td><td class="p-2 text-center align-middle text-sm font-medium">${fmtNoUnit(gross)}</td><td class="p-2 text-center text-rose-700 align-middle">${fmtNoUnit(cost)}</td><td class="p-2 text-center font-semibold align-middle ${pfCls}">${pfVal}</td><td class="p-2 text-center text-amber-700 align-middle">${fmtNoUnit(payment)}</td><td class="p-2 text-center text-purple-700 align-middle">${fmtNoUnit(paid)}</td><td class="p-2 text-center font-semibold align-middle ${cfCls}">${cf}</td><td class="p-2 text-center align-middle text-sm">${exe}</td><td class="p-2 text-center align-middle text-sm">${payR}</td></tr>`;
|
|
|
|
|
};
|
|
|
|
|
// 待签约简版行
|
|
|
|
|
const tdRowUnsigned = (pf, cost, gross) => {
|
|
|
|
|
const profit = gross;
|
|
|
|
|
const pfCls = profit >= 0 ? 'text-green-600' : 'text-red-600';
|
|
|
|
|
const pfVal = profit ? money(profit) : '<span class="text-slate-300">—</span>';
|
|
|
|
|
return `<tr class="border-b border-slate-100 hover:bg-slate-50 cursor-pointer" onclick="planOpenPfEditModal(${pf.id})"><td class="p-2 text-sm font-medium text-center align-middle truncate" style="min-width:300px;max-width:300px;width:300px" title="${esc(pf.customer_name)}">${esc(pf.customer_name)}</td><td class="p-2 text-center align-middle text-sm font-medium">${money(pf.sign_amount)}</td><td class="p-2 text-center align-middle text-sm">${pf.sign_month || '—'}</td><td class="p-2 text-center align-middle text-sm font-medium">${fmtNoUnit(gross)}</td><td class="p-2 text-center text-rose-700 align-middle">${fmtNoUnit(cost)}</td><td class="p-2 text-center font-semibold align-middle ${pfCls}">${pfVal}</td></tr>`;
|
|
|
|
|
return `<tr class="border-b border-slate-100 hover:bg-slate-50 cursor-pointer" onclick="planOpenPfEditModal(${pf.id})"><td class="p-2 text-center align-middle text-sm">${esc(pf.client_name || "")}</td><td class="p-2 text-sm font-medium text-center align-middle truncate" style="min-width:300px;max-width:300px;width:300px" title="${esc(pf.customer_name)}">${esc(pf.customer_name)}</td><td class="p-2 text-center align-middle text-sm" onclick="event.stopPropagation()">${planInlineSelect('weight', pf.id, 'weight', pf.weight || '20%', ['20%','40%','60%','80%','100%'])}</td><td class="p-2 text-center align-middle text-sm" onclick="event.stopPropagation()">${planInlineSelect('ptype', pf.id, 'project_type', pf.project_type || '待签约', ['待签约','已签约'])}</td><td class="p-2 text-center align-middle text-sm font-medium">${money(pf.sign_amount)}</td><td class="p-2 text-center align-middle text-sm">${pf.sign_month || '—'}</td><td class="p-2 text-center align-middle text-sm font-medium">${fmtNoUnit(gross)}</td><td class="p-2 text-center text-rose-700 align-middle">${fmtNoUnit(cost)}</td><td class="p-2 text-center font-semibold align-middle ${pfCls}">${pfVal}</td></tr>`;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (state.planView === 'monthly') {
|
|
|
|
|
@@ -784,7 +786,7 @@ window.planOpenPfEditModal = (pfId) => {
|
|
|
|
|
document.querySelector("#plan_pf-id-input").value = pf.id;
|
|
|
|
|
document.querySelector("#planModalTitle").textContent = "编辑项目财务";
|
|
|
|
|
document.querySelector("#planDeleteBtn").classList.remove("hidden");
|
|
|
|
|
const form = document.querySelector("#financeModal form");
|
|
|
|
|
const form = document.querySelector("#planModal form");
|
|
|
|
|
form.querySelector('[name="project_id"]').value = pf.project_id || "";
|
|
|
|
|
const deptDisplay = form.querySelector('.bg-slate-50 [disabled]');
|
|
|
|
|
if (deptDisplay) deptDisplay.value = pf.project_id || "";
|
|
|
|
|
@@ -820,6 +822,9 @@ window.planOpenPfEditModal = (pfId) => {
|
|
|
|
|
setVal("contact_name", pf.contact_name);
|
|
|
|
|
setVal("contact_phone", pf.contact_phone);
|
|
|
|
|
setVal("other_info", pf.other_info);
|
|
|
|
|
setVal("client_name", pf.client_name);
|
|
|
|
|
form.querySelector('[name="project_type"]').value = pf.project_type || "待签约";
|
|
|
|
|
form.querySelector('[name="weight"]').value = pf.weight || "20%";
|
|
|
|
|
let budgetData = [];
|
|
|
|
|
try { budgetData = JSON.parse(pf.budget_data || "[]"); } catch (e) { budgetData = []; }
|
|
|
|
|
planInitRevpayTable(budgetData.length ? budgetData : null);
|
|
|
|
|
@@ -1046,3 +1051,21 @@ function renderPlanCharts(data) {
|
|
|
|
|
state.planChart4 = new Chart(c4, Object.assign({}, iconf, { data: { labels: labels, datasets: [{ label: "利润", data: data.map(function(x) { return x.gross || 0; }), borderColor: "#6366f1", backgroundColor: "rgba(99,102,241,0.06)", fill: true, tension: 0.3 }] } }));
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function planInlineSelect(name, pfId, field, currentVal, options) {
|
|
|
|
|
var opts = '';
|
|
|
|
|
for (var i = 0; i < options.length; i++) {
|
|
|
|
|
opts += '<option value="' + options[i] + '"' + (options[i] === currentVal ? ' selected' : '') + '>' + options[i] + '</option>';
|
|
|
|
|
}
|
|
|
|
|
return '<select onchange="planInlineUpdate(' + pfId + ', \'' + field + '\'' + ', this.value)" class="text-sm bg-transparent border-0 cursor-pointer text-center" style="appearance:auto;-webkit-appearance:auto;vertical-align:middle">' + opts + '</select>';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
window.planInlineUpdate = async (pfId, field, value) => {
|
|
|
|
|
try {
|
|
|
|
|
await api("/api/planFinances/" + pfId, { method: "PUT", body: JSON.stringify({ data: { [field]: value } }) });
|
|
|
|
|
await load();
|
|
|
|
|
toast("已更新", "success");
|
|
|
|
|
} catch (error) {
|
|
|
|
|
toast("更新失败:" + error.message, "error");
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|