feat: 项目流水拆分为确收与回款+费用明细两Tab + 数据迁移
后端: - 新增 expense_data TEXT 列(columns.py迁移) - 新增 data_split 迁移:旧 budget_data cost/paid → expense_data(幂等) - sum_budget cost/paid → sum_expense cost/paid(routes.py/helpers.py) - TABLES 配置加入 expense_data 前端: - 项目详情弹窗Tab:基本信息→确收与回款→费用明细→执行信息→任务管理→活动 - 确收与回款Tab:月份/确收/毛利/回款/备注 - 费用明细Tab:月份/费用类型/应付/已付/备注 - 项目表格三视图(月/季/总)cost/paid来源切换为expense_data - 保存逻辑分两路: budget_data(rev/gross/payment)+expense_data(cost/paid)
This commit is contained in:
@@ -121,7 +121,8 @@ function renderFinance() {
|
||||
<div id="financeModal" class="hidden fixed inset-0 z-50 flex items-center justify-center bg-black/40" onclick="closeFinanceModal()"><div class="bg-white rounded-2xl shadow-2xl w-full max-w-6xl mx-4 max-h-[92vh] overflow-y-auto" onclick="event.stopPropagation()"><div class="sticky top-0 z-10 bg-white/95 backdrop-blur border-b border-slate-100 px-8 py-5 flex items-center justify-between"><div><h3 class="text-xl font-bold text-slate-800" id="financeModalTitle">新增项目财务</h3><p class="text-xs text-slate-400 mt-0.5">填写项目财务信息与月度预算</p></div><div class="flex items-center gap-2"><button class="btn btn-ghost btn-sm text-red-600 hidden" id="financeDeleteBtn" onclick="deleteFinanceItem()"><i data-lucide="trash-2"></i>删除</button><button class="btn btn-ghost btn-sm rounded-full w-8 h-8 p-0" onclick="closeFinanceModal()"><i data-lucide="x"></i></button></div></div>
|
||||
<div class="finance-tabs">
|
||||
<button class="finance-tab active" data-tab="info" onclick="switchFinanceTab('info')"><i data-lucide="info" style="width:14px;height:14px;display:inline-block;vertical-align:-2px;margin-right:4px"></i>基本信息</button>
|
||||
<button class="finance-tab" data-tab="budget" onclick="switchFinanceTab('budget')"><i data-lucide="calendar" style="width:14px;height:14px;display:inline-block;vertical-align:-2px;margin-right:4px"></i>项目流水</button>
|
||||
<button class="finance-tab" data-tab="revpay" onclick="switchFinanceTab('revpay')"><i data-lucide="dollar-sign" style="width:14px;height:14px;display:inline-block;vertical-align:-2px;margin-right:4px"></i>确收与回款</button>
|
||||
<button class="finance-tab" data-tab="cost" onclick="switchFinanceTab('cost')"><i data-lucide="receipt" style="width:14px;height:14px;display:inline-block;vertical-align:-2px;margin-right:4px"></i>费用明细</button>
|
||||
<button class="finance-tab" data-tab="exec" onclick="switchFinanceTab('exec')"><i data-lucide="play-circle" style="width:14px;height:14px;display:inline-block;vertical-align:-2px;margin-right:4px"></i>执行信息</button>
|
||||
<button class="finance-tab" data-tab="tasks" onclick="switchFinanceTab('tasks')"><i data-lucide="list-checks" style="width:14px;height:14px;display:inline-block;vertical-align:-2px;margin-right:4px"></i>任务管理</button>
|
||||
<button class="finance-tab" data-tab="activity" onclick="switchFinanceTab('activity')"><i data-lucide="message-square" style="width:14px;height:14px;display:inline-block;vertical-align:-2px;margin-right:4px"></i>活动与跟进</button>
|
||||
@@ -163,34 +164,43 @@ function renderFinance() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="financeTabBudget" class="hidden">
|
||||
<div class="grid grid-cols-6 gap-3 mb-4" id="budgetSummary">
|
||||
<div id="financeTabRevpay" class="hidden">
|
||||
<div class="grid grid-cols-3 gap-3 mb-4" id="revpaySummary">
|
||||
<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>
|
||||
<p class="text-lg font-bold text-blue-700" id="revpayTotalRev">¥0</p>
|
||||
</div>
|
||||
<div class="bg-green-50 rounded-lg p-3 text-center border border-green-100">
|
||||
<p class="text-xs text-green-600 font-medium">总毛利</p>
|
||||
<p class="text-lg font-bold text-green-700" id="budgetTotalGross">¥0</p>
|
||||
<p class="text-lg font-bold text-green-700" id="revpayTotalGross">¥0</p>
|
||||
</div>
|
||||
<div class="bg-amber-50 rounded-lg p-3 text-center border border-amber-100" id="budgetTotalPaymentCard">
|
||||
<div class="bg-amber-50 rounded-lg p-3 text-center border border-amber-100">
|
||||
<p class="text-xs text-amber-600 font-medium">总回款</p>
|
||||
<p class="text-lg font-bold text-amber-700" id="budgetTotalPayment">¥0</p>
|
||||
<p class="text-lg font-bold text-amber-700" id="revpayTotalPayment">¥0</p>
|
||||
</div>
|
||||
<div class="bg-rose-50 rounded-lg p-3 text-center border border-rose-100" id="budgetTotalCostCard">
|
||||
</div>
|
||||
<table class="w-full text-sm border border-slate-200 rounded-lg overflow-hidden" id="revpayTable">
|
||||
<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-left font-medium text-slate-500">备注</th><th class="p-2.5 w-8"></th></tr></thead>
|
||||
<tbody id="revpayTbody"></tbody>
|
||||
</table>
|
||||
<button type="button" class="btn btn-ghost btn-sm mt-3" onclick="addRevpayRow()"><i data-lucide="plus"></i>添加月份</button>
|
||||
</div>
|
||||
<div id="financeTabCost" class="hidden">
|
||||
<div class="grid grid-cols-2 gap-3 mb-4" id="costSummary">
|
||||
<div class="bg-rose-50 rounded-lg p-3 text-center border border-rose-100">
|
||||
<p class="text-xs text-rose-600 font-medium">总应付</p>
|
||||
<p class="text-lg font-bold text-rose-700" id="budgetTotalCost">¥0</p>
|
||||
<p class="text-lg font-bold text-rose-700" id="costTotalCost">¥0</p>
|
||||
</div>
|
||||
<div class="bg-purple-50 rounded-lg p-3 text-center border border-purple-100">
|
||||
<p class="text-xs text-purple-600 font-medium">总已付</p>
|
||||
<p class="text-lg font-bold text-purple-700" id="budgetTotalPaid">¥0</p>
|
||||
<p class="text-lg font-bold text-purple-700" id="costTotalPaid">¥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>
|
||||
<tbody id="budgetTbody"></tbody>
|
||||
<table class="w-full text-sm border border-slate-200 rounded-lg overflow-hidden" id="costTable">
|
||||
<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-left font-medium text-slate-500" style="min-width:120px">费用类型</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-left font-medium text-slate-500">备注</th><th class="p-2.5 w-8"></th></tr></thead>
|
||||
<tbody id="costTbody"></tbody>
|
||||
</table>
|
||||
<button type="button" class="btn btn-ghost btn-sm mt-3" onclick="addBudgetRow()"><i data-lucide="plus"></i>添加月份</button>
|
||||
<button type="button" class="btn btn-ghost btn-sm mt-3" onclick="addCostRow()"><i data-lucide="plus"></i>添加月份</button>
|
||||
</div>
|
||||
<div id="financeTabTasks" class="hidden">
|
||||
<table class="w-full text-sm border border-slate-200 rounded-lg overflow-hidden" id="taskTable">
|
||||
@@ -293,12 +303,14 @@ function renderFinance() {
|
||||
let sumRev = 0, sumPay = 0, sumCost = 0, sumPaid = 0, sumGross = 0;
|
||||
allPfs.forEach(pf => {
|
||||
let bd = []; try { bd = JSON.parse(pf.budget_data || "[]"); } catch (e) {}
|
||||
let ed = []; try { ed = JSON.parse(pf.expense_data || "[]"); } catch (e) {}
|
||||
const b = bd.find(x => (x.month || "") === selMonth) || {};
|
||||
const e = ed.find(x => (x.month || "") === selMonth) || {};
|
||||
const rev = Math.round(parseFloat(b.rev || 0) || 0);
|
||||
const payment = Math.round(parseFloat(b.payment || 0) || 0);
|
||||
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);
|
||||
const cost = Math.round(parseFloat(e.cost || 0) || 0);
|
||||
const paid = Math.round(parseFloat(e.paid || 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));
|
||||
@@ -323,14 +335,19 @@ 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;
|
||||
};
|
||||
const sumExpense = (pf, field) => {
|
||||
let total = 0;
|
||||
try { JSON.parse(pf.expense_data || "[]").forEach(e => { const m = parseInt((e.month || "").substring(5)) || 0; if (qRange.includes(m)) total += parseFloat(e[field] || 0); }); } catch (e) {}
|
||||
return total;
|
||||
};
|
||||
let sumRev = 0, sumPay = 0, sumCost = 0, sumPaid = 0, sumGross = 0;
|
||||
const rows = [];
|
||||
allPfs.forEach(pf => {
|
||||
const rev = sumBudget(pf, "rev");
|
||||
const payment = sumBudget(pf, "payment");
|
||||
const cost = sumBudget(pf, "cost");
|
||||
const paid = sumBudget(pf, "paid");
|
||||
const gross = sumBudget(pf, "gross");
|
||||
const cost = sumExpense(pf, "cost");
|
||||
const paid = sumExpense(pf, "paid");
|
||||
sumRev += rev; sumPay += payment; sumCost += cost; sumPaid += paid; sumGross += gross;
|
||||
rows.push(tdRow(pf, rev, payment, cost, paid, gross));
|
||||
});
|
||||
@@ -342,9 +359,11 @@ function renderFinance() {
|
||||
// 总视图
|
||||
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; });
|
||||
const paid = parseFloat(pf.total_paid) || 0;
|
||||
let expense = []; try { expense = JSON.parse(pf.expense_data || "[]"); } catch (e) {}
|
||||
let rev = 0, payment = 0, gross = 0;
|
||||
budget.forEach(b => { rev += parseFloat(b.rev || 0) || 0; gross += parseFloat(b.gross || 0) || 0; payment += parseFloat(b.payment || 0) || 0; });
|
||||
let cost = 0, paid = 0;
|
||||
expense.forEach(e => { cost += parseFloat(e.cost || 0) || 0; paid += parseFloat(e.paid || 0) || 0; });
|
||||
return { rev: Math.round(rev), payment: Math.round(payment), cost: Math.round(cost), paid: Math.round(paid), gross: Math.round(gross) };
|
||||
};
|
||||
const allPfs = pfs.filter(x => x.status === state.finFilter);
|
||||
@@ -371,60 +390,88 @@ window.openFinanceModal = () => {
|
||||
if (dept) dept.value = state.tenant;
|
||||
const pfIdInput = form.querySelector('[name="pf_id"]');
|
||||
if (!pfIdInput || !pfIdInput.value) {
|
||||
initBudgetTable(null);
|
||||
initRevpayTable(null);
|
||||
initCostTable(null);
|
||||
initTaskTable(null);
|
||||
document.querySelector("#financeDeleteBtn").classList.add("hidden");
|
||||
}
|
||||
modal.classList.remove("hidden");
|
||||
};
|
||||
|
||||
window.addBudgetRow = (month = '', rev = '', gross = '', payment = '', cost = '', paid = '') => {
|
||||
const tbody = document.querySelector("#budgetTbody");
|
||||
window.addRevpayRow = (month = '', rev = '', gross = '', payment = '', rev_note = '') => {
|
||||
const tbody = document.querySelector("#revpayTbody");
|
||||
if (!tbody) return;
|
||||
const row = document.createElement("tr");
|
||||
row.innerHTML = `<td><select name="budget_month[]" class="form-ctrl form-ctrl-sm w-full" style="min-width:140px" onchange="updateBudgetSummary()">${monthOptions(month)}</select></td>
|
||||
<td><input name="budget_rev[]" type="number" step="0.01" class="form-ctrl form-ctrl-sm w-full text-right" placeholder="0" value="${rev}" oninput="updateBudgetSummary()"></td>
|
||||
<td><input name="budget_gross[]" type="number" step="0.01" class="form-ctrl form-ctrl-sm w-full text-right" placeholder="0" value="${gross}" oninput="updateBudgetSummary()"></td>
|
||||
<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><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>`;
|
||||
row.innerHTML = `<td><select name="budget_month[]" class="form-ctrl form-ctrl-sm w-full" style="min-width:140px" onchange="updateRevpaySummary()">${monthOptions(month)}</select></td>
|
||||
<td><input name="budget_rev[]" type="number" step="0.01" class="form-ctrl form-ctrl-sm w-full text-right" placeholder="0" value="${rev}" oninput="updateRevpaySummary()"></td>
|
||||
<td><input name="budget_gross[]" type="number" step="0.01" class="form-ctrl form-ctrl-sm w-full text-right" placeholder="0" value="${gross}" oninput="updateRevpaySummary()"></td>
|
||||
<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="updateRevpaySummary()"></td>
|
||||
<td><input name="budget_rev_note[]" class="form-ctrl form-ctrl-sm w-full" placeholder="备注" value="${rev_note}"></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();updateRevpaySummary()"><i data-lucide="trash-2" style="width:14px;height:14px"></i></button></td>`;
|
||||
tbody.appendChild(row);
|
||||
if (window.lucide) window.lucide.createIcons();
|
||||
};
|
||||
|
||||
window.updateBudgetSummary = () => {
|
||||
const revEl = document.querySelector("#budgetTotalRev");
|
||||
const grossEl = document.querySelector("#budgetTotalGross");
|
||||
const paymentEl = document.querySelector("#budgetTotalPayment");
|
||||
const costEl = document.querySelector("#budgetTotalCost");
|
||||
const paidEl = document.querySelector("#budgetTotalPaid");
|
||||
if (!revEl || !grossEl) return;
|
||||
window.addCostRow = (month = '', expense_type = '', cost = '', paid = '', exp_note = '') => {
|
||||
const tbody = document.querySelector("#costTbody");
|
||||
if (!tbody) return;
|
||||
const row = document.createElement("tr");
|
||||
row.innerHTML = `<td><select name="expense_month[]" class="form-ctrl form-ctrl-sm w-full" style="min-width:140px" onchange="updateCostSummary()">${monthOptions(month)}</select></td>
|
||||
<td><input name="expense_type[]" class="form-ctrl form-ctrl-sm w-full" placeholder="费用类型" value="${expense_type}"></td>
|
||||
<td><input name="expense_cost[]" type="number" step="0.01" class="form-ctrl form-ctrl-sm w-full text-right" placeholder="0" value="${cost}" oninput="updateCostSummary()"></td>
|
||||
<td><input name="expense_paid[]" type="number" step="0.01" class="form-ctrl form-ctrl-sm w-full text-right" placeholder="0" value="${paid}" oninput="updateCostSummary()"></td>
|
||||
<td><input name="expense_note[]" class="form-ctrl form-ctrl-sm w-full" placeholder="备注" value="${exp_note}"></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();updateCostSummary()"><i data-lucide="trash-2" style="width:14px;height:14px"></i></button></td>`;
|
||||
tbody.appendChild(row);
|
||||
if (window.lucide) window.lucide.createIcons();
|
||||
};
|
||||
|
||||
window.updateRevpaySummary = () => {
|
||||
const revEl = document.querySelector("#revpayTotalRev");
|
||||
const grossEl = document.querySelector("#revpayTotalGross");
|
||||
const paymentEl = document.querySelector("#revpayTotalPayment");
|
||||
if (!revEl || !paymentEl) 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 paidInputs = document.querySelectorAll('[name="budget_paid[]"]');
|
||||
let totalRev = 0, totalGross = 0, totalPayment = 0, totalCost = 0, totalPaid = 0;
|
||||
let totalRev = 0, totalGross = 0, totalPayment = 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; });
|
||||
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 (paidEl) paidEl.textContent = money(totalPaid);
|
||||
if (grossEl) grossEl.textContent = money(totalGross);
|
||||
paymentEl.textContent = money(totalPayment);
|
||||
};
|
||||
|
||||
window.initBudgetTable = (budgetData) => {
|
||||
const tbody = document.querySelector("#budgetTbody");
|
||||
window.updateCostSummary = () => {
|
||||
const costEl = document.querySelector("#costTotalCost");
|
||||
const paidEl = document.querySelector("#costTotalPaid");
|
||||
if (!costEl || !paidEl) return;
|
||||
const costInputs = document.querySelectorAll('[name="expense_cost[]"]');
|
||||
const paidInputs = document.querySelectorAll('[name="expense_paid[]"]');
|
||||
let totalCost = 0, totalPaid = 0;
|
||||
costInputs.forEach(el => { totalCost += parseFloat(el.value) || 0; });
|
||||
paidInputs.forEach(el => { totalPaid += parseFloat(el.value) || 0; });
|
||||
costEl.textContent = money(totalCost);
|
||||
paidEl.textContent = money(totalPaid);
|
||||
};
|
||||
|
||||
window.initRevpayTable = (budgetData) => {
|
||||
const tbody = document.querySelector("#revpayTbody");
|
||||
if (!tbody) return;
|
||||
tbody.innerHTML = "";
|
||||
const rows = budgetData || [];
|
||||
rows.forEach(r => addBudgetRow(r.month || '', r.rev || '', r.gross || '', r.payment || '', r.cost || '', r.paid || ''));
|
||||
setTimeout(() => updateBudgetSummary(), 50);
|
||||
rows.forEach(r => addRevpayRow(r.month || '', r.rev || '', r.gross || '', r.payment || '', r.rev_note || ''));
|
||||
setTimeout(() => updateRevpaySummary(), 50);
|
||||
};
|
||||
|
||||
window.initCostTable = (expenseData) => {
|
||||
const tbody = document.querySelector("#costTbody");
|
||||
if (!tbody) return;
|
||||
tbody.innerHTML = "";
|
||||
const rows = expenseData || [];
|
||||
rows.forEach(r => addCostRow(r.month || '', r.expense_type || '', r.cost || '', r.paid || '', r.exp_note || ''));
|
||||
setTimeout(() => updateCostSummary(), 50);
|
||||
};
|
||||
|
||||
// ---------- 任务管理 tab ----------
|
||||
@@ -557,7 +604,8 @@ window.editPfSignMonth = (event, pfId) => {
|
||||
window.switchFinanceTab = (tab) => {
|
||||
document.querySelectorAll(".finance-tab").forEach(b => b.classList.toggle("active", b.dataset.tab === tab));
|
||||
document.querySelector("#financeTabInfo").classList.toggle("hidden", tab !== "info");
|
||||
document.querySelector("#financeTabBudget").classList.toggle("hidden", tab !== "budget");
|
||||
document.querySelector("#financeTabRevpay").classList.toggle("hidden", tab !== "revpay");
|
||||
document.querySelector("#financeTabCost").classList.toggle("hidden", tab !== "cost");
|
||||
document.querySelector("#financeTabExec").classList.toggle("hidden", tab !== "exec");
|
||||
document.querySelector("#financeTabTasks").classList.toggle("hidden", tab !== "tasks");
|
||||
document.querySelector("#financeTabActivity").classList.toggle("hidden", tab !== "activity");
|
||||
@@ -662,7 +710,10 @@ window.openPfEditModal = (pfId) => {
|
||||
setVal("other_info", pf.other_info);
|
||||
let budgetData = [];
|
||||
try { budgetData = JSON.parse(pf.budget_data || "[]"); } catch (e) { budgetData = []; }
|
||||
initBudgetTable(budgetData.length ? budgetData : null);
|
||||
initRevpayTable(budgetData.length ? budgetData : null);
|
||||
let expenseData = [];
|
||||
try { expenseData = JSON.parse(pf.expense_data || "[]"); } catch (e) { expenseData = []; }
|
||||
initCostTable(expenseData.length ? expenseData : null);
|
||||
let taskData = [];
|
||||
try { taskData = JSON.parse(pf.task_data || "[]"); } catch (e) { taskData = []; }
|
||||
initTaskTable(taskData.length ? taskData : null);
|
||||
@@ -690,31 +741,50 @@ window.createFinance = async (event) => {
|
||||
const revs = form.querySelectorAll('[name="budget_rev[]"]');
|
||||
const grosses = form.querySelectorAll('[name="budget_gross[]"]');
|
||||
const payments = form.querySelectorAll('[name="budget_payment[]"]');
|
||||
const costs = form.querySelectorAll('[name="budget_cost[]"]');
|
||||
const paids = form.querySelectorAll('[name="budget_paid[]"]');
|
||||
const revNotes = form.querySelectorAll('[name="budget_rev_note[]"]');
|
||||
const budgetRows = [];
|
||||
let totalRev = 0, totalGross = 0, totalPaidFromBudget = 0;
|
||||
let totalRev = 0, totalGross = 0, totalPayment = 0;
|
||||
for (let i = 0; i < months.length; i++) {
|
||||
const m = months[i].value.trim();
|
||||
if (!m) continue;
|
||||
const rev = parseFloat(revs[i].value) || 0;
|
||||
const gross = parseFloat(grosses[i].value) || 0;
|
||||
const payment = parseFloat(payments[i].value) || 0;
|
||||
const cost = parseFloat(costs[i].value) || 0;
|
||||
const paid = parseFloat(paids[i].value) || 0;
|
||||
budgetRows.push({ month: m, rev, gross, payment, cost, paid });
|
||||
const rev_note = (revNotes[i] ? revNotes[i].value : '') || '';
|
||||
budgetRows.push({ month: m, rev, gross, payment, rev_note });
|
||||
totalRev += rev;
|
||||
totalGross += gross;
|
||||
totalPaidFromBudget += paid;
|
||||
totalPayment += payment;
|
||||
}
|
||||
data.budget_data = JSON.stringify(budgetRows);
|
||||
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; }
|
||||
data.total_payment = totalPayment;
|
||||
|
||||
// 费用明细数据
|
||||
const expMonths = form.querySelectorAll('[name="expense_month[]"]');
|
||||
const expTypes = form.querySelectorAll('[name="expense_type[]"]');
|
||||
const expCosts = form.querySelectorAll('[name="expense_cost[]"]');
|
||||
const expPaids = form.querySelectorAll('[name="expense_paid[]"]');
|
||||
const expNotes = form.querySelectorAll('[name="expense_note[]"]');
|
||||
const expenseRows = [];
|
||||
let totalCost = 0, totalPaid = 0;
|
||||
for (let i = 0; i < expMonths.length; i++) {
|
||||
const m = expMonths[i].value.trim();
|
||||
if (!m) continue;
|
||||
const expense_type = (expTypes[i] ? expTypes[i].value : '') || '';
|
||||
const cost = parseFloat(expCosts[i].value) || 0;
|
||||
const paid = parseFloat(expPaids[i].value) || 0;
|
||||
const exp_note = (expNotes[i] ? expNotes[i].value : '') || '';
|
||||
expenseRows.push({ month: m, expense_type, cost, paid, exp_note });
|
||||
totalCost += cost;
|
||||
totalPaid += paid;
|
||||
}
|
||||
data.expense_data = JSON.stringify(expenseRows);
|
||||
data.total_paid = totalPaid;
|
||||
data.total_cost = totalCost;
|
||||
for (const r of budgetRows) { totalPayment += r.payment; }
|
||||
data.total_payment = totalPayment;
|
||||
// 收集任务管理数据
|
||||
const taskTypeInputs = form.querySelectorAll('[name="task_type[]"]');
|
||||
const taskCountInputs = form.querySelectorAll('[name="task_count[]"]');
|
||||
|
||||
Reference in New Issue
Block a user