feat: 费用模块重构 + 项目模块优化 + 数据库迁移
费用模块: - 字段精简为5个:费用类型(下拉6选)、月份(3年36月下拉)、金额、已发生金额、费用说明(textarea) - 三视图切换(总/季度/月度),按expense_month筛选,汇总卡片实时更新 - 类型筛选功能,Toolbar+汇总+表格三段式布局 - 删除按钮移至关闭按钮左侧,间距8px - 费用记录删除合计行,列宽固定200px,居中 项目模块: - 卡片网格6→7列(14卡片7×2满排),配置驱动 METRIC_CARDS - 新增费用/利润字段,利润=毛利-费用 - 表格行添加费用、利润列,比率列text-xs→text-sm - 项目流水tab费用与已付列对调,汇总卡片6→1×6 - 修复月度/季度切换时汇总卡片不更新 - 筛选区统一为卡片,去除'筛选:'前缀 数据库: - expense_records新增expense_type/expense_month/incurred_amount列 - migrations/columns.py幂等迁移,启动时自动执行
This commit is contained in:
@@ -164,7 +164,7 @@ function renderFinance() {
|
||||
</div>
|
||||
</div>
|
||||
<div id="financeTabBudget" class="hidden">
|
||||
<div class="grid grid-cols-5 gap-3 mb-4" id="budgetSummary">
|
||||
<div class="grid grid-cols-6 gap-3 mb-4" id="budgetSummary">
|
||||
<div class="bg-blue-50 rounded-lg p-3 text-center border border-blue-100">
|
||||
<p class="text-xs text-blue-600 font-medium">总确收</p>
|
||||
<p class="text-lg font-bold text-blue-700" id="budgetTotalRev">¥0</p>
|
||||
@@ -185,9 +185,13 @@ function renderFinance() {
|
||||
<p class="text-xs text-purple-600 font-medium">总已付</p>
|
||||
<p class="text-lg font-bold text-purple-700" id="budgetTotalPaid">¥0</p>
|
||||
</div>
|
||||
<div class="bg-orange-50 rounded-lg p-3 text-center border border-orange-100" id="budgetTotalExpenseCard">
|
||||
<p class="text-xs text-orange-600 font-medium">总费用</p>
|
||||
<p class="text-lg font-bold text-orange-700" id="budgetTotalExpense">¥0</p>
|
||||
</div>
|
||||
</div>
|
||||
<table class="w-full text-sm border border-slate-200 rounded-lg overflow-hidden" id="budgetTable">
|
||||
<thead><tr class="bg-slate-50 border-b border-slate-200"><th class="p-2.5 text-left font-medium text-slate-500" style="min-width:140px">月份</th><th class="p-2.5 text-right font-medium text-slate-500">确收</th><th class="p-2.5 text-right font-medium text-slate-500">毛利</th><th class="p-2.5 text-right font-medium text-slate-500">回款</th><th class="p-2.5 text-right font-medium text-slate-500">应付</th><th class="p-2.5 text-right font-medium text-slate-500">已付</th><th class="p-2.5 w-8"></th></tr></thead>
|
||||
<thead><tr class="bg-slate-50 border-b border-slate-200"><th class="p-2.5 text-left font-medium text-slate-500" style="min-width:140px">月份</th><th class="p-2.5 text-right font-medium text-slate-500">确收</th><th class="p-2.5 text-right font-medium text-slate-500">毛利</th><th class="p-2.5 text-right font-medium text-slate-500">回款</th><th class="p-2.5 text-right font-medium text-slate-500">应付</th><th class="p-2.5 text-right font-medium text-slate-500">已付</th><th class="p-2.5 text-right font-medium text-slate-500">费用</th><th class="p-2.5 w-8"></th></tr></thead>
|
||||
<tbody id="budgetTbody"></tbody>
|
||||
</table>
|
||||
<button type="button" class="btn btn-ghost btn-sm mt-3" onclick="addBudgetRow()"><i data-lucide="plus"></i>添加月份</button>
|
||||
@@ -231,16 +235,18 @@ function renderFinance() {
|
||||
{ label: '执行率', value: ctx => ctx.sumRev && ctx.signTotal ? Math.round(ctx.sumRev / ctx.signTotal * 100) + '%' : '—', color: 'text-slate-500' },
|
||||
{ label: '毛利', value: ctx => money(ctx.sumGross), color: 'text-green-700' },
|
||||
{ label: '毛利率', value: ctx => ctx.sumRev && ctx.sumGross ? Math.round(ctx.sumGross / ctx.sumRev * 100) + '%' : '—', color: 'text-slate-500' },
|
||||
{ label: '费用', value: ctx => money(ctx.sumExpense), color: 'text-orange-700' },
|
||||
{ label: '已回款', value: ctx => moneyWan(ctx.sumPay), color: 'text-amber-700' },
|
||||
{ label: '回款率', value: ctx => ctx.sumRev && ctx.sumPay ? Math.round(ctx.sumPay / ctx.sumRev * 100) + '%' : '—', color: 'text-slate-500' },
|
||||
{ label: '应付', value: ctx => moneyWan(ctx.sumCost), color: 'text-rose-700' },
|
||||
{ label: '已付', value: ctx => moneyWan(ctx.sumPaid), color: 'text-purple-700' },
|
||||
{ label: '付款率', value: ctx => ctx.sumCost && ctx.sumPaid ? Math.round(ctx.sumPaid / ctx.sumCost * 100) + '%' : '—', color: 'text-slate-500' },
|
||||
{ label: '现金流', value: ctx => { const v = ctx.sumPay - ctx.sumPaid; return { val: money(v), cls: v >= 0 ? 'text-green-600' : 'text-red-600' }; }, color: null },
|
||||
{ label: '利润', value: ctx => { const v = ctx.sumGross - ctx.sumExpense; return { val: moneyWan(v), cls: v >= 0 ? 'text-green-600' : 'text-red-600' }; }, color: null },
|
||||
];
|
||||
|
||||
const renderView = (rows, sumRev, sumPay, sumCost, sumPaid, sumGross, signTotal, signCnt, emptyText) => {
|
||||
const ctx = { sumRev, sumPay, sumCost, sumPaid, sumGross, signTotal, signCnt };
|
||||
const renderView = (rows, sumRev, sumPay, sumCost, sumPaid, sumGross, sumExpense, signTotal, signCnt, emptyText) => {
|
||||
const ctx = { sumRev, sumPay, sumCost, sumPaid, sumGross, sumExpense, signTotal, signCnt };
|
||||
const cards = METRIC_CARDS.map(c => {
|
||||
const v = c.value(ctx);
|
||||
return typeof v === 'object' ? [c.label, v.val, v.cls] : [c.label, v, c.color];
|
||||
@@ -258,16 +264,18 @@ function renderFinance() {
|
||||
'<th class="p-2 text-center font-semibold align-middle text-rose-600">应付</th>' +
|
||||
'<th class="p-2 text-center font-semibold align-middle text-purple-600">已付</th>' +
|
||||
'<th class="p-2 text-center font-semibold align-middle">付款率</th>' +
|
||||
'<th class="p-2 text-center font-semibold align-middle text-orange-600">费用</th>' +
|
||||
'<th class="p-2 text-center font-semibold align-middle text-slate-700">现金流</th>' +
|
||||
'<th class="p-2 text-center font-semibold align-middle">利润</th>' +
|
||||
'</tr></thead>';
|
||||
const tbody = rows.length ? `<tbody>${rows.join("")}</tbody>` : `<tr><td colspan="13" class="p-6 text-center text-slate-400">${emptyText}</td></tr>`;
|
||||
const tbody = rows.length ? `<tbody>${rows.join("")}</tbody>` : `<tr><td colspan="15" class="p-6 text-center text-slate-400">${emptyText}</td></tr>`;
|
||||
|
||||
return card(`<div class="grid grid-cols-6 gap-3 mb-3">${cards.map(([l, v, c]) => '<div class="metric-card p-3"><span class="flex items-center gap-2 text-xs text-slate-500">' + l + '</span><strong class="mt-2 block text-xl ' + c + '">' + v + '</strong></div>').join("")}</div>`, "p-4") +
|
||||
return card(`<div class="grid grid-cols-7 gap-3 mb-3">${cards.map(([l, v, c]) => '<div class="metric-card p-3"><span class="flex items-center gap-2 text-xs text-slate-500">' + l + '</span><strong class="mt-2 block text-xl ' + c + '">' + v + '</strong></div>').join("")}</div>`, "p-4") +
|
||||
card(`<div class="overflow-x-auto"><table class="w-full text-sm">${thead}${tbody}</table></div>`, "p-4");
|
||||
};
|
||||
|
||||
const fmtNoUnit = (v) => v ? `<span class="font-medium">${Number(v||0).toLocaleString('zh-CN')}</span>` : '<span class="text-slate-300">—</span>';
|
||||
const tdRow = (pf, rev, payment, cost, paid, gross) => {
|
||||
const tdRow = (pf, rev, payment, cost, paid, gross, expense) => {
|
||||
const cashflow = payment - paid;
|
||||
const exe = rev && pf.sign_amount ? Math.round(rev / pf.sign_amount * 100) + '%' : '<span class="text-slate-300">—</span>';
|
||||
const payR = rev && payment ? Math.round(payment / rev * 100) + '%' : '<span class="text-slate-300">—</span>';
|
||||
@@ -276,7 +284,10 @@ function renderFinance() {
|
||||
const st = pf.status === '已签约' ? '<span class="text-green-600">已签约</span>' : '<span class="text-amber-600">待签约</span>';
|
||||
const cf = cashflow ? money(cashflow) : '<span class="text-slate-300">—</span>';
|
||||
const cfCls = cashflow >= 0 ? 'text-green-600' : 'text-red-600';
|
||||
return `<tr class="border-b border-slate-100 hover:bg-slate-50 cursor-pointer" onclick="openPfEditModal(${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">${st}</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-xs">${exe}</td><td class="p-2 text-center align-middle text-sm font-medium">${fmtNoUnit(gross)}</td><td class="p-2 text-center align-middle text-xs">${grossR}</td><td class="p-2 text-center text-amber-700 align-middle">${fmtNoUnit(payment)}</td><td class="p-2 text-center align-middle text-xs">${payR}</td><td class="p-2 text-center text-rose-700 align-middle">${fmtNoUnit(cost)}</td><td class="p-2 text-center text-purple-700 align-middle">${fmtNoUnit(paid)}</td><td class="p-2 text-center align-middle text-xs">${paidR}</td><td class="p-2 text-center font-semibold align-middle ${cfCls}">${cf}</td></tr>`;
|
||||
const profit = gross - expense;
|
||||
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="openPfEditModal(${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">${st}</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">${exe}</td><td class="p-2 text-center align-middle text-sm font-medium">${fmtNoUnit(gross)}</td><td class="p-2 text-center align-middle text-sm">${grossR}</td><td class="p-2 text-center text-amber-700 align-middle">${fmtNoUnit(payment)}</td><td class="p-2 text-center align-middle text-sm">${payR}</td><td class="p-2 text-center text-rose-700 align-middle">${fmtNoUnit(cost)}</td><td class="p-2 text-center text-purple-700 align-middle">${fmtNoUnit(paid)}</td><td class="p-2 text-center align-middle text-sm">${paidR}</td><td class="p-2 text-center text-orange-700 align-middle">${fmtNoUnit(expense)}</td><td class="p-2 text-center font-semibold align-middle ${cfCls}">${cf}</td><td class="p-2 text-center font-semibold align-middle ${pfCls}">${pfVal}</td></tr>`;
|
||||
};
|
||||
|
||||
if (state.finView === 'monthly') {
|
||||
@@ -286,7 +297,7 @@ function renderFinance() {
|
||||
if (!state.finMonth) state.finMonth = defaultMonth;
|
||||
const selMonth = state.finMonth;
|
||||
const rows = [];
|
||||
let sumRev = 0, sumPay = 0, sumCost = 0, sumPaid = 0, sumGross = 0;
|
||||
let sumRev = 0, sumPay = 0, sumCost = 0, sumPaid = 0, sumGross = 0, sumExpense = 0;
|
||||
allPfs.forEach(pf => {
|
||||
let bd = []; try { bd = JSON.parse(pf.budget_data || "[]"); } catch (e) {}
|
||||
const b = bd.find(x => (x.month || "") === selMonth) || {};
|
||||
@@ -295,13 +306,14 @@ function renderFinance() {
|
||||
const cost = Math.round(parseFloat(b.cost || 0) || 0);
|
||||
const paid = Math.round(parseFloat(b.paid || 0) || 0);
|
||||
const gross = Math.round(parseFloat(b.gross || 0) || 0);
|
||||
if (!rev && !payment && !cost && !paid && !gross) return;
|
||||
sumRev += rev; sumPay += payment; sumCost += cost; sumPaid += paid; sumGross += gross;
|
||||
rows.push(tdRow(pf, rev, payment, cost, paid, gross));
|
||||
const expense = Math.round(parseFloat(b.expense || 0) || 0);
|
||||
if (!rev && !payment && !cost && !paid && !gross && !expense) return;
|
||||
sumRev += rev; sumPay += payment; sumCost += cost; sumPaid += paid; sumGross += gross; sumExpense += expense;
|
||||
rows.push(tdRow(pf, rev, payment, cost, paid, gross, expense));
|
||||
});
|
||||
const signTotal = Math.round(allPfs.filter(x => (x.sign_month || "").substring(0, 7) === selMonth).reduce((a, p) => a + (p.sign_amount || 0), 0));
|
||||
const signCnt = allPfs.filter(x => x.status === "已签约" && (x.sign_month || "").substring(0, 7) === selMonth).length;
|
||||
return renderView(rows, sumRev, sumPay, sumCost, sumPaid, sumGross, signTotal, signCnt, '该月份暂无数据');
|
||||
return renderView(rows, sumRev, sumPay, sumCost, sumPaid, sumGross, sumExpense, signTotal, signCnt, '该月份暂无数据');
|
||||
}
|
||||
|
||||
if (state.finView === 'quarterly') {
|
||||
@@ -319,7 +331,7 @@ function renderFinance() {
|
||||
try { JSON.parse(pf.budget_data || "[]").forEach(b => { const m = parseInt((b.month || "").substring(5)) || 0; if (qRange.includes(m)) total += parseFloat(b[field] || 0); }); } catch (e) {}
|
||||
return total;
|
||||
};
|
||||
let sumRev = 0, sumPay = 0, sumCost = 0, sumPaid = 0, sumGross = 0;
|
||||
let sumRev = 0, sumPay = 0, sumCost = 0, sumPaid = 0, sumGross = 0, sumExpense = 0;
|
||||
const rows = [];
|
||||
allPfs.forEach(pf => {
|
||||
const rev = sumBudget(pf, "rev");
|
||||
@@ -327,33 +339,34 @@ function renderFinance() {
|
||||
const cost = sumBudget(pf, "cost");
|
||||
const paid = sumBudget(pf, "paid");
|
||||
const gross = sumBudget(pf, "gross");
|
||||
sumRev += rev; sumPay += payment; sumCost += cost; sumPaid += paid; sumGross += gross;
|
||||
rows.push(tdRow(pf, rev, payment, cost, paid, gross));
|
||||
const expense = sumBudget(pf, "expense");
|
||||
sumRev += rev; sumPay += payment; sumCost += cost; sumPaid += paid; sumGross += gross; sumExpense += expense;
|
||||
rows.push(tdRow(pf, rev, payment, cost, paid, gross, expense));
|
||||
});
|
||||
const signTotal = Math.round(allPfs.filter(x => { const m = parseInt((x.sign_month || "0").substring(5)) || 0; return qRange.includes(m); }).reduce((a, p) => a + (p.sign_amount || 0), 0));
|
||||
const signCnt = allPfs.filter(x => x.status === "已签约" && qRange.includes(parseInt((x.sign_month || "0").substring(5)) || 0)).length;
|
||||
return renderView(rows, sumRev, sumPay, sumCost, sumPaid, sumGross, signTotal, signCnt, '该季度暂无数据');
|
||||
return renderView(rows, sumRev, sumPay, sumCost, sumPaid, sumGross, sumExpense, signTotal, signCnt, '该季度暂无数据');
|
||||
}
|
||||
|
||||
// 总视图
|
||||
const calcTotals = (pf) => {
|
||||
let budget = []; try { budget = JSON.parse(pf.budget_data || "[]"); } catch (e) {}
|
||||
let rev = 0, payment = 0, cost = 0, gross = 0;
|
||||
budget.forEach(b => { rev += parseFloat(b.rev || 0) || 0; gross += parseFloat(b.gross || 0) || 0; payment += parseFloat(b.payment || 0) || 0; cost += parseFloat(b.cost || 0) || 0; });
|
||||
let rev = 0, payment = 0, cost = 0, gross = 0, expense = 0;
|
||||
budget.forEach(b => { rev += parseFloat(b.rev || 0) || 0; gross += parseFloat(b.gross || 0) || 0; payment += parseFloat(b.payment || 0) || 0; cost += parseFloat(b.cost || 0) || 0; expense += parseFloat(b.expense || 0) || 0; });
|
||||
const paid = parseFloat(pf.total_paid) || 0;
|
||||
return { rev: Math.round(rev), payment: Math.round(payment), cost: Math.round(cost), paid: Math.round(paid), gross: Math.round(gross) };
|
||||
return { rev: Math.round(rev), payment: Math.round(payment), cost: Math.round(cost), paid: Math.round(paid), gross: Math.round(gross), expense: Math.round(expense) };
|
||||
};
|
||||
const allPfs = pfs.filter(x => x.status === state.finFilter);
|
||||
let sumRev = 0, sumPay = 0, sumCost = 0, sumPaid = 0, sumGross = 0;
|
||||
let sumRev = 0, sumPay = 0, sumCost = 0, sumPaid = 0, sumGross = 0, sumExpense = 0;
|
||||
const rows = [];
|
||||
allPfs.forEach(pf => {
|
||||
const t = calcTotals(pf);
|
||||
sumRev += t.rev; sumPay += t.payment; sumCost += t.cost; sumPaid += t.paid; sumGross += t.gross;
|
||||
rows.push(tdRow(pf, t.rev, t.payment, t.cost, t.paid, t.gross));
|
||||
sumRev += t.rev; sumPay += t.payment; sumCost += t.cost; sumPaid += t.paid; sumGross += t.gross; sumExpense += t.expense;
|
||||
rows.push(tdRow(pf, t.rev, t.payment, t.cost, t.paid, t.gross, t.expense));
|
||||
});
|
||||
const signTotal = Math.round(allPfs.reduce((a, p) => a + (p.sign_amount || 0), 0));
|
||||
const signCnt = allPfs.filter(x => x.status === "已签约").length;
|
||||
return renderView(rows, sumRev, sumPay, sumCost, sumPaid, sumGross, signTotal, signCnt, '暂无数据');
|
||||
return renderView(rows, sumRev, sumPay, sumCost, sumPaid, sumGross, sumExpense, signTotal, signCnt, '暂无数据');
|
||||
})()}
|
||||
</div>`;
|
||||
if (window.lucide) window.lucide.createIcons();
|
||||
@@ -374,7 +387,7 @@ window.openFinanceModal = () => {
|
||||
modal.classList.remove("hidden");
|
||||
};
|
||||
|
||||
window.addBudgetRow = (month = '', rev = '', gross = '', payment = '', cost = '', paid = '') => {
|
||||
window.addBudgetRow = (month = '', rev = '', gross = '', payment = '', cost = '', paid = '', expense = '') => {
|
||||
const tbody = document.querySelector("#budgetTbody");
|
||||
if (!tbody) return;
|
||||
const row = document.createElement("tr");
|
||||
@@ -384,6 +397,7 @@ window.addBudgetRow = (month = '', rev = '', gross = '', payment = '', cost = ''
|
||||
<td><input name="budget_payment[]" type="number" step="0.01" class="form-ctrl form-ctrl-sm w-full text-right" placeholder="0" value="${payment}" oninput="updateBudgetSummary()"></td>
|
||||
<td><input name="budget_cost[]" type="number" step="0.01" class="form-ctrl form-ctrl-sm w-full text-right" placeholder="0" value="${cost}" oninput="updateBudgetSummary()"></td>
|
||||
<td><input name="budget_paid[]" type="number" step="0.01" class="form-ctrl form-ctrl-sm w-full text-right" placeholder="0" value="${paid}" oninput="updateBudgetSummary()"></td>
|
||||
<td><input name="budget_expense[]" type="number" step="0.01" class="form-ctrl form-ctrl-sm w-full text-right" placeholder="0" value="${expense}" oninput="updateBudgetSummary()"></td>
|
||||
<td><button type="button" class="btn btn-ghost btn-sm text-red-500 p-0 w-6 h-6" onclick="this.closest('tr').remove();updateBudgetSummary()"><i data-lucide="trash-2" style="width:14px;height:14px"></i></button></td>`;
|
||||
tbody.appendChild(row);
|
||||
if (window.lucide) window.lucide.createIcons();
|
||||
@@ -394,23 +408,27 @@ window.updateBudgetSummary = () => {
|
||||
const grossEl = document.querySelector("#budgetTotalGross");
|
||||
const paymentEl = document.querySelector("#budgetTotalPayment");
|
||||
const costEl = document.querySelector("#budgetTotalCost");
|
||||
const expenseEl = document.querySelector("#budgetTotalExpense");
|
||||
const paidEl = document.querySelector("#budgetTotalPaid");
|
||||
if (!revEl || !grossEl) return;
|
||||
const revInputs = document.querySelectorAll('[name="budget_rev[]"]');
|
||||
const grossInputs = document.querySelectorAll('[name="budget_gross[]"]');
|
||||
const paymentInputs = document.querySelectorAll('[name="budget_payment[]"]');
|
||||
const costInputs = document.querySelectorAll('[name="budget_cost[]"]');
|
||||
const expenseInputs = document.querySelectorAll('[name="budget_expense[]"]');
|
||||
const paidInputs = document.querySelectorAll('[name="budget_paid[]"]');
|
||||
let totalRev = 0, totalGross = 0, totalPayment = 0, totalCost = 0, totalPaid = 0;
|
||||
let totalRev = 0, totalGross = 0, totalPayment = 0, totalCost = 0, totalExpense = 0, totalPaid = 0;
|
||||
revInputs.forEach(el => { totalRev += parseFloat(el.value) || 0; });
|
||||
grossInputs.forEach(el => { totalGross += parseFloat(el.value) || 0; });
|
||||
paymentInputs.forEach(el => { totalPayment += parseFloat(el.value) || 0; });
|
||||
costInputs.forEach(el => { totalCost += parseFloat(el.value) || 0; });
|
||||
expenseInputs.forEach(el => { totalExpense += parseFloat(el.value) || 0; });
|
||||
paidInputs.forEach(el => { totalPaid += parseFloat(el.value) || 0; });
|
||||
revEl.textContent = money(totalRev);
|
||||
grossEl.textContent = money(totalGross);
|
||||
if (paymentEl) paymentEl.textContent = money(totalPayment);
|
||||
if (costEl) costEl.textContent = money(totalCost);
|
||||
if (expenseEl) expenseEl.textContent = money(totalExpense);
|
||||
if (paidEl) paidEl.textContent = money(totalPaid);
|
||||
};
|
||||
|
||||
@@ -419,7 +437,7 @@ window.initBudgetTable = (budgetData) => {
|
||||
if (!tbody) return;
|
||||
tbody.innerHTML = "";
|
||||
const rows = budgetData || [];
|
||||
rows.forEach(r => addBudgetRow(r.month || '', r.rev || '', r.gross || '', r.payment || '', r.cost || '', r.paid || ''));
|
||||
rows.forEach(r => addBudgetRow(r.month || '', r.rev || '', r.gross || '', r.payment || '', r.cost || '', r.paid || '', r.expense || ''));
|
||||
setTimeout(() => updateBudgetSummary(), 50);
|
||||
};
|
||||
|
||||
@@ -687,6 +705,7 @@ window.createFinance = async (event) => {
|
||||
const grosses = form.querySelectorAll('[name="budget_gross[]"]');
|
||||
const payments = form.querySelectorAll('[name="budget_payment[]"]');
|
||||
const costs = form.querySelectorAll('[name="budget_cost[]"]');
|
||||
const expenses = form.querySelectorAll('[name="budget_expense[]"]');
|
||||
const paids = form.querySelectorAll('[name="budget_paid[]"]');
|
||||
const budgetRows = [];
|
||||
let totalRev = 0, totalGross = 0, totalPaidFromBudget = 0;
|
||||
@@ -697,8 +716,9 @@ window.createFinance = async (event) => {
|
||||
const gross = parseFloat(grosses[i].value) || 0;
|
||||
const payment = parseFloat(payments[i].value) || 0;
|
||||
const cost = parseFloat(costs[i].value) || 0;
|
||||
const expense = parseFloat(expenses[i].value) || 0;
|
||||
const paid = parseFloat(paids[i].value) || 0;
|
||||
budgetRows.push({ month: m, rev, gross, payment, cost, paid });
|
||||
budgetRows.push({ month: m, rev, gross, payment, cost, expense, paid });
|
||||
totalRev += rev;
|
||||
totalGross += gross;
|
||||
totalPaidFromBudget += paid;
|
||||
@@ -707,10 +727,12 @@ window.createFinance = async (event) => {
|
||||
data.total_rev = totalRev;
|
||||
data.total_gross = totalGross;
|
||||
data.total_paid = totalPaidFromBudget;
|
||||
let totalPayment = 0, totalCost = 0;
|
||||
for (const r of budgetRows) { totalPayment += r.payment; totalCost += r.cost; }
|
||||
let totalPayment = 0, totalCost = 0, totalExpense = 0;
|
||||
for (const r of budgetRows) { totalPayment += r.payment; totalCost += r.cost; totalExpense += r.expense; }
|
||||
data.total_payment = totalPayment;
|
||||
data.total_cost = totalCost;
|
||||
data.total_expense = totalExpense;
|
||||
data.total_cost = totalCost;
|
||||
// 收集任务管理数据
|
||||
const taskTypeInputs = form.querySelectorAll('[name="task_type[]"]');
|
||||
const taskCountInputs = form.querySelectorAll('[name="task_count[]"]');
|
||||
|
||||
Reference in New Issue
Block a user