This commit is contained in:
@@ -1010,26 +1010,29 @@ function renderPlanOverview() {
|
||||
annualSums.payment += fm[i].payment; annualSums.paid += fm[i].paid;
|
||||
}
|
||||
const mLbl = ['1月','2月','3月','4月','5月','6月','7月','8月','9月','10月','11月','12月'];
|
||||
const LABELS = ['合同金额','确收金额','确收毛利','成本','已付'];
|
||||
const LABELS = ['合同金额','确收金额','确收毛利','成本','已付','回款','项目现金流'];
|
||||
// 每行的年度累计 + 12个月值
|
||||
const annualVals = [
|
||||
annualSums.sign, annualSums.revenue, annualSums.gross, annualSums.cost, annualSums.paid,
|
||||
annualSums.payment, annualSums.payment - annualSums.paid,
|
||||
];
|
||||
// 字段映射: sign, revenue, gross, cost, profit(=gross-cost), payment, paid, cashflow(=payment-paid)
|
||||
const fmFields = ['sign','revenue','gross','cost','paid'];
|
||||
// 字段映射: sign, revenue, gross, cost, paid, payment, cashflow(=payment-paid)
|
||||
const fmFields = ['sign','revenue','gross','cost','paid','payment',null];
|
||||
var thead = '<tr class="border-b border-slate-200"><th class="py-2 text-left text-slate-500 whitespace-nowrap">指标</th><th class="py-2 text-right font-semibold text-slate-700 whitespace-nowrap">年度累计</th>';
|
||||
for (var mi = 0; mi < 12; mi++) { thead += '<th class="py-2 text-right font-semibold text-slate-600 whitespace-nowrap">' + mLbl[mi] + '</th>'; }
|
||||
thead += '</tr>';
|
||||
var tbody = '';
|
||||
for (var ri = 0; ri < 5; ri++) {
|
||||
for (var ri = 0; ri < 7; ri++) {
|
||||
var rowHtml = '<tr class="border-b border-slate-100 last:border-0"><td class="py-2 text-left font-semibold text-slate-700 whitespace-nowrap">' + LABELS[ri] + '</td>';
|
||||
rowHtml += '<td class="py-2 text-right font-semibold text-slate-800 whitespace-nowrap">' + moneyIntL(annualVals[ri]) + '</td>';
|
||||
for (var mi = 0; mi < 12; mi++) {
|
||||
var val = 0;
|
||||
if (fm[mi]) {
|
||||
var fk = fmFields[ri]; if (fk) val = fm[mi][fk] || 0;
|
||||
if (ri === 6) { val = (fm[mi].payment || 0) - (fm[mi].paid || 0); }
|
||||
else { var fk = fmFields[ri]; if (fk) val = fm[mi][fk] || 0; }
|
||||
}
|
||||
var cls = 'text-slate-700';
|
||||
if (ri === 6) cls = val >= 0 ? 'text-green-600' : 'text-red-600';
|
||||
rowHtml += '<td class="py-2 text-right text-sm whitespace-nowrap ' + cls + '">' + moneyIntL(val) + '</td>';
|
||||
}
|
||||
tbody += rowHtml + '</tr>';
|
||||
|
||||
@@ -184,36 +184,35 @@ window.savePlanExpense = async function(event) {
|
||||
if (data.amount === '' || data.amount === undefined) data.amount = 0;
|
||||
if (data.incurred_amount === '' || data.incurred_amount === undefined) data.incurred_amount = 0;
|
||||
if (!data.status) data.status = '计入费用';
|
||||
var isEdit = !!data.expense_id;
|
||||
var id = data.expense_id;
|
||||
delete data.expense_id;
|
||||
data.tenant = state.tenant;
|
||||
|
||||
var id = form.querySelector('[name="expense_id"]').value;
|
||||
var url = isEdit ? "/api/planExpense/" + id : "/api/planExpense";
|
||||
var method = isEdit ? "PUT" : "POST";
|
||||
|
||||
var resp = await fetch(url, { method: method, headers: { "Content-Type": "application/json" }, body: JSON.stringify({ data: Object.assign({}, data, { tenant: state.tenant }) }) });
|
||||
if (!resp.ok) { var err = await resp.text(); alert("保存失败:" + err); return; }
|
||||
|
||||
var bResp = await fetch("/api/bootstrap?tenant=" + encodeURIComponent(state.tenant));
|
||||
if (bResp.ok) {
|
||||
var bd = await bResp.json();
|
||||
state.data = bd;
|
||||
applyUserTenants();
|
||||
renderPlanExpense();
|
||||
try {
|
||||
if (id) {
|
||||
await api("/api/planExpense/" + id, { method: "PUT", body: JSON.stringify({ data }) });
|
||||
} else {
|
||||
await api("/api/planExpense", { method: "POST", body: JSON.stringify({ data }) });
|
||||
}
|
||||
} catch (error) {
|
||||
toast("保存失败:" + error.message, "error");
|
||||
return;
|
||||
}
|
||||
closePlanExpModal();
|
||||
await load();
|
||||
renderPlanExpense();
|
||||
};
|
||||
|
||||
window.deletePlanExpItem = async function() {
|
||||
var id = document.querySelector("#planExpenseModal form [name='expense_id']").value;
|
||||
if (!id || !confirm("确定删除?")) return;
|
||||
var resp = await fetch("/api/planExpense/" + id, { method: "DELETE" });
|
||||
if (!resp.ok) { alert("删除失败"); return; }
|
||||
var bResp = await fetch("/api/bootstrap?tenant=" + encodeURIComponent(state.tenant));
|
||||
if (bResp.ok) {
|
||||
var bd = await bResp.json();
|
||||
state.data = bd;
|
||||
applyUserTenants();
|
||||
renderPlanExpense();
|
||||
try {
|
||||
await api("/api/planExpense/" + id, { method: "DELETE" });
|
||||
} catch (error) {
|
||||
toast("删除失败:" + error.message, "error");
|
||||
return;
|
||||
}
|
||||
closePlanExpModal();
|
||||
await load();
|
||||
renderPlanExpense();
|
||||
};
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
<header class="topbar border-b border-slate-200 bg-white px-8 py-5">
|
||||
<div class="flex items-center gap-3 w-full">
|
||||
<div class="flex-1">
|
||||
<p class="eyebrow text-xs font-semibold uppercase tracking-[0.18em] text-blue-700">OPC Manager <span class="ml-2 text-xs font-normal text-slate-400 tracking-normal">v1.0.0.32</span></p>
|
||||
<p class="eyebrow text-xs font-semibold uppercase tracking-[0.18em] text-blue-700">OPC Manager <span class="ml-2 text-xs font-normal text-slate-400 tracking-normal">v1.0.0.33</span></p>
|
||||
<div class="flex items-center gap-4 mt-1">
|
||||
<h1 class="text-2xl font-semibold" id="workspaceTitle">科普 OPC 工作台</h1>
|
||||
<div class="hidden sm:flex items-center gap-1.5 bg-gradient-to-r from-blue-50 to-indigo-50 border border-blue-100 rounded-full px-4 py-1.5">
|
||||
|
||||
Reference in New Issue
Block a user