From ead6f2ac46937f8bed6fd251c7c2b43853c45a03 Mon Sep 17 00:00:00 2001 From: mac Date: Fri, 10 Jul 2026 20:06:03 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E5=BE=85=E5=9B=9E?= =?UTF-8?q?=E6=AC=BE=E5=88=97=E5=92=8C=E5=8D=A1=E7=89=87+=E5=8F=96?= =?UTF-8?q?=E6=B6=88=E4=BB=98=E6=AC=BE=E7=8E=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 项目财务明细表格增加待回款列(确收-回款, 现金流后) - 汇总卡片增加待回款(毛利前), 取消付款率 - 展开明细增加待回款列 - 版本号 v1.2.0.13 --- static/modules/finance.js | 10 +++++++--- static/modules/plan.js | 10 +++++++--- templates/index.html | 2 +- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/static/modules/finance.js b/static/modules/finance.js index 8a5d89b..0310269 100644 --- a/static/modules/finance.js +++ b/static/modules/finance.js @@ -256,6 +256,7 @@ function renderFinance() { const METRIC_CARDS = [ { label: '签约金额', value: ctx => moneyWan(ctx.signTotal), color: 'text-slate-700' }, { label: '已确收', hideUnsigned: true, value: ctx => moneyWan(ctx.sumRev), color: 'text-blue-700' }, + { label: '待回款', hideUnsigned: true, value: ctx => { const v = ctx.sumRev - ctx.sumPay; return { val: moneyWan(v), cls: v > 0 ? 'text-red-600' : v < 0 ? 'text-green-600' : 'text-slate-400' }; }, color: null }, { label: '毛利', value: ctx => moneyWan(ctx.sumGross), color: 'text-green-700' }, { label: '成本(不含平台费用)', value: ctx => moneyWan(ctx.sumCost), color: 'text-rose-700' }, { label: '回款', hideUnsigned: true, value: ctx => moneyWan(ctx.sumPay), color: 'text-amber-700' }, @@ -263,7 +264,6 @@ function renderFinance() { { label: '现金流(回款-支出)', hideUnsigned: true, value: ctx => { const v = ctx.sumPay - ctx.sumPaid; return { val: moneyWan(v), cls: v >= 0 ? 'text-green-600' : 'text-red-600' }; }, color: null }, { label: '毛利率', hideUnsigned: true, value: ctx => ctx.sumRev && ctx.sumGross ? Math.round(ctx.sumGross / ctx.sumRev * 100) + '%' : '—', color: 'text-slate-500' }, { label: '回款率', hideUnsigned: true, value: ctx => ctx.sumRev && ctx.sumPay ? Math.round(ctx.sumPay / ctx.sumRev * 100) + '%' : '—', color: 'text-slate-500' }, - { label: '付款率', hideUnsigned: true, value: ctx => ctx.sumCost && ctx.sumPaid ? Math.round(ctx.sumPaid / ctx.sumCost * 100) + '%' : '—', color: 'text-slate-500' }, ].filter(c => true); // 列排序(默认按确收金额从高到低) @@ -300,6 +300,7 @@ function renderFinance() { { key: 'payment', label: '回款' }, { key: 'pay_rate', label: '回款率' }, { key: 'cost', label: '成本' }, { key: 'paid', label: '支出' }, { key: 'paid_rate', label: '付款率' }, { key: 'cashflow', label: '现金流' }, + { key: 'pending', label: '待回款' }, ]; const UNSIGNED_COLS = [ { key: 'client_name', label: '客户' }, { key: 'customer_name', label: '项目名称' }, { key: 'sign_amount', label: '签约金额' }, @@ -336,6 +337,9 @@ function renderFinance() { const grossR = rev && gross ? Math.round(gross / rev * 100) + '%' : ''; const cf = cashflow ? money(cashflow) : ''; const cfCls = cashflow >= 0 ? 'text-green-600' : 'text-red-600'; + const pending = rev - payment; + const pendingCls = pending > 0 ? 'text-red-600' : pending < 0 ? 'text-green-600' : 'text-slate-400'; + const pendingVal = pending ? money(pending) : ''; var budgetArr = []; var expenseArr = []; try { budgetArr = JSON.parse(pf.budget_data || "[]"); } catch(e) {} @@ -356,9 +360,9 @@ function renderFinance() { var bRev = parseFloat(b.rev || 0), bGross = parseFloat(b.gross || 0), bPay = parseFloat(b.payment || 0); var eCost = parseFloat(exp.cost || 0), ePaid = parseFloat(exp.paid || 0); var bCf = bPay - ePaid; - return '' + m + '' + (bRev ? money(bRev) : '—') + '' + (bGross ? money(bGross) : '—') + '' + (bPay ? money(bPay) : '—') + '' + (eCost ? money(eCost) : '—') + '' + (ePaid ? money(ePaid) : '—') + '' + (bCf ? money(bCf) : '—') + ''; + return '' + m + '' + (bRev ? money(bRev) : '—') + '' + (bGross ? money(bGross) : '—') + '' + (bPay ? money(bPay) : '—') + '' + (eCost ? money(eCost) : '—') + '' + (ePaid ? money(ePaid) : '—') + '' + (bCf ? money(bCf) : '—') + '' + (bRev - bPay ? money(bRev - bPay) : '—') + ''; }).join(''); - return `${esc(pf.client_name || "")}${esc(pf.customer_name)}${money(pf.sign_amount)}${fmtNoUnit(rev)}${fmtNoUnit(gross)}${grossR}${fmtNoUnit(payment)}${payR}${fmtNoUnit(cost)}${fmtNoUnit(paid)}${paidR}${cf}${detailRows}
月份确收毛利回款成本支出现金流
`; + return `${esc(pf.client_name || "")}${esc(pf.customer_name)}${money(pf.sign_amount)}${fmtNoUnit(rev)}${fmtNoUnit(gross)}${grossR}${fmtNoUnit(payment)}${payR}${fmtNoUnit(cost)}${fmtNoUnit(paid)}${paidR}${cf}${pendingVal}${detailRows}
月份确收毛利回款成本支出现金流待回款
`; }; // 待签约简版行 const tdRowUnsigned = (pf, cost, gross) => { diff --git a/static/modules/plan.js b/static/modules/plan.js index 633eb34..ea2650e 100644 --- a/static/modules/plan.js +++ b/static/modules/plan.js @@ -254,6 +254,7 @@ function renderPlan() { const METRIC_CARDS = [ { label: '签约金额', value: ctx => moneyWan(ctx.signTotal), color: 'text-slate-700' }, { label: '已确收', hideUnsigned: true, value: ctx => moneyWan(ctx.sumRev), color: 'text-blue-700' }, + { label: '待回款', hideUnsigned: true, value: ctx => { const v = ctx.sumRev - ctx.sumPay; return { val: moneyWan(v), cls: v > 0 ? 'text-red-600' : v < 0 ? 'text-green-600' : 'text-slate-400' }; }, color: null }, { label: '毛利', value: ctx => moneyWan(ctx.sumGross), color: 'text-green-700' }, { label: '成本(不含平台费用)', value: ctx => moneyWan(ctx.sumCost), color: 'text-rose-700' }, { label: '回款', hideUnsigned: true, value: ctx => moneyWan(ctx.sumPay), color: 'text-amber-700' }, @@ -261,7 +262,6 @@ function renderPlan() { { label: '现金流(回款-支出)', hideUnsigned: true, value: ctx => { const v = ctx.sumPay - ctx.sumPaid; return { val: moneyWan(v), cls: v >= 0 ? 'text-green-600' : 'text-red-600' }; }, color: null }, { label: '毛利率', hideUnsigned: true, value: ctx => ctx.sumRev && ctx.sumGross ? Math.round(ctx.sumGross / ctx.sumRev * 100) + '%' : '—', color: 'text-slate-500' }, { label: '回款率', hideUnsigned: true, value: ctx => ctx.sumRev && ctx.sumPay ? Math.round(ctx.sumPay / ctx.sumRev * 100) + '%' : '—', color: 'text-slate-500' }, - { label: '付款率', hideUnsigned: true, value: ctx => ctx.sumCost && ctx.sumPaid ? Math.round(ctx.sumPaid / ctx.sumCost * 100) + '%' : '—', color: 'text-slate-500' }, ].filter(c => true); // 列排序(默认按确收金额从高到低) @@ -310,6 +310,7 @@ function renderPlan() { { key: 'paid', label: '付款' }, { key: 'paid_rate', label: '付款率' }, { key: 'cashflow', label: '现金流' }, + { key: 'pending', label: '待回款' }, ]; const UNSIGNED_COLS = [ { key: 'client_name', label: '客户' }, @@ -351,6 +352,9 @@ function renderPlan() { const grossR = rev && gross ? Math.round(gross / rev * 100) + '%' : ''; const cf = cashflow ? money(cashflow) : ''; const cfCls = cashflow >= 0 ? 'text-green-600' : 'text-red-600'; + const pending = rev - payment; + const pendingCls = pending > 0 ? 'text-red-600' : pending < 0 ? 'text-green-600' : 'text-slate-400'; + const pendingVal = pending ? money(pending) : ''; var budgetArr = []; var expenseArr = []; try { budgetArr = JSON.parse(pf.budget_data || "[]"); } catch(e) {} @@ -371,9 +375,9 @@ function renderPlan() { var bRev = parseFloat(b.rev || 0), bGross = parseFloat(b.gross || 0), bPay = parseFloat(b.payment || 0); var eCost = parseFloat(exp.cost || 0), ePaid = parseFloat(exp.paid || 0); var bCf = bPay - ePaid; - return '' + m + '' + (bRev ? money(bRev) : '—') + '' + (bGross ? money(bGross) : '—') + '' + (bPay ? money(bPay) : '—') + '' + (eCost ? money(eCost) : '—') + '' + (ePaid ? money(ePaid) : '—') + '' + (bCf ? money(bCf) : '—') + ''; + return '' + m + '' + (bRev ? money(bRev) : '—') + '' + (bGross ? money(bGross) : '—') + '' + (bPay ? money(bPay) : '—') + '' + (eCost ? money(eCost) : '—') + '' + (ePaid ? money(ePaid) : '—') + '' + (bCf ? money(bCf) : '—') + '' + (bRev - bPay ? money(bRev - bPay) : '—') + ''; }).join(''); - return `${esc(pf.client_name || "")}${esc(pf.customer_name)}${planInlineSelect('weight', pf.id, 'weight', pf.weight || '20%', ['20%','40%','60%','80%','100%'])}${planInlineSelect('ptype', pf.id, 'project_type', pf.project_type || '待签约', ['待签约','已签约'])}${money(pf.sign_amount)}${fmtNoUnit(rev)}${fmtNoUnit(gross)}${grossR}${fmtNoUnit(payment)}${payR}${fmtNoUnit(cost)}${fmtNoUnit(paid)}${paidR}${cf}${detailRows}
月份确收毛利回款成本支出现金流
`; + return `${esc(pf.client_name || "")}${esc(pf.customer_name)}${planInlineSelect('weight', pf.id, 'weight', pf.weight || '20%', ['20%','40%','60%','80%','100%'])}${planInlineSelect('ptype', pf.id, 'project_type', pf.project_type || '待签约', ['待签约','已签约'])}${money(pf.sign_amount)}${fmtNoUnit(rev)}${fmtNoUnit(gross)}${grossR}${fmtNoUnit(payment)}${payR}${fmtNoUnit(cost)}${fmtNoUnit(paid)}${paidR}${cf}${pendingVal}${detailRows}
月份确收毛利回款成本支出现金流待回款
`; }; // 待签约简版行 const tdRowUnsigned = (pf, cost, gross) => { diff --git a/templates/index.html b/templates/index.html index a8fa1f5..5bc1110 100644 --- a/templates/index.html +++ b/templates/index.html @@ -76,7 +76,7 @@
-

OPC Manager v1.2.0.12

+

OPC Manager v1.2.0.13

科普 OPC 工作台