// expense.js — 费用管理模块 function renderExpense() { var records = state.data.expense || []; var money = function(v) { return '¥ ' + (Number(v || 0)).toLocaleString('zh-CN', { minimumFractionDigits: 0, maximumFractionDigits: 2 }); }; var esc = function(s) { return (s || '').replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); }; var EXPENSE_TYPES = ['通用费用','平台采购','人力成本','研发费用','奖金','其他']; // 筛选状态 var view = state.expenseView || 'total'; var typeFilter = state.expenseFilter || '全部'; var filtered = typeFilter === '全部' ? records : records.filter(function(r) { return r.expense_type === typeFilter; }); // 视图数据预处理 var now = new Date(); var thisYear = now.getFullYear(); var defaultMonth = thisYear + '-' + String(now.getMonth() + 1).padStart(2, '0'); if (!state.expenseMonth) state.expenseMonth = defaultMonth; if (state.expenseQuarter === '') state.expenseQuarter = String(Math.floor(now.getMonth() / 3)); // 月度筛选 if (view === 'monthly') { filtered = filtered.filter(function(r) { return r.expense_month === state.expenseMonth; }); } // 季度筛选 if (view === 'quarterly') { var q = parseInt(state.expenseQuarter) || 0; var qRanges = [[1,2,3],[4,5,6],[7,8,9],[10,11,12]]; var qRange = qRanges[q]; filtered = filtered.filter(function(r) { var m = parseInt((r.expense_month || '0').substring(5)) || 0; return qRange.indexOf(m) >= 0; }); } var totalAmount = filtered.reduce(function(s, r) { return s + (r.amount || 0); }, 0); var totalIncurred = filtered.reduce(function(s, r) { return s + (r.incurred_amount || 0); }, 0); // 月份下拉选项(去年/今年/明年) var monthOpts = ''; for (var yr = thisYear - 1; yr <= thisYear + 1; yr++) for (var m = 1; m <= 12; m++) { var mv = yr + '-' + String(m).padStart(2, '0'); monthOpts += ''; } // 季度下拉 var qLabels = ['Q1 (1-3月)','Q2 (4-6月)','Q3 (7-9月)','Q4 (10-12月)']; var quarterOpts = ''; for (var qi = 0; qi < 4; qi++) { quarterOpts += ''; } // 月份/季度日期选择器 var dateSelect = ''; if (view === 'monthly') { dateSelect = '月份:'; } else if (view === 'quarterly') { dateSelect = '季度:'; } // Toolbar var toolbar = '视图:类型:' + dateSelect; // 表格行 var tableRows = ''; if (filtered.length) { filtered.forEach(function(r) { tableRows += '' + esc(r.expense_type) + '' + (r.expense_month || '—') + '' + money(r.amount) + '' + money(r.incurred_amount) + '' + (esc(r.notes) || '—') + ''; }); } else { var emptyMsg = view === 'monthly' ? '该月份暂无费用记录' : view === 'quarterly' ? '该季度暂无费用记录' : '暂无费用记录'; tableRows = '' + emptyMsg + ''; } // 模态框 var modal = ''; document.querySelector("#expense").innerHTML = '
' + '
' + toolbar + '
' + '
' + '
费用记录' + filtered.length + '
' + '
总费用' + money(totalAmount) + '
' + '
已发生金额' + money(totalIncurred) + '
' + '
' + modal + '
' + tableRows + '
费用类型月份金额已发生金额费用说明
' + '
'; if (window.lucide) window.lucide.createIcons(); } window.setExpenseView = function(v) { state.expenseView = v; renderExpense(); }; window.setExpenseFilter = function(v) { state.expenseFilter = v; renderExpense(); }; window.setExpenseMonth = function(v) { state.expenseMonth = v; renderExpense(); }; window.setExpenseQuarter = function(v) { state.expenseQuarter = v; renderExpense(); }; window.openExpenseModal = function() { var form = document.querySelector("#expenseModal form"); form.reset(); form.querySelector('[name="expense_id"]').value = ""; document.querySelector("#expenseModalTitle").textContent = "新增费用"; document.querySelector("#expenseDeleteBtn").classList.add("hidden"); document.querySelector("#expenseModal").classList.remove("hidden"); }; window.closeExpenseModal = function() { document.querySelector("#expenseModal").classList.add("hidden"); }; window.openExpenseEdit = function(id) { var r = (state.data.expense || []).find(function(x) { return x.id === id; }); if (!r) return; var form = document.querySelector("#expenseModal form"); form.reset(); var setVal = function(name, val) { var el = form.querySelector('[name="' + name + '"]'); if (el) el.value = val || ""; }; setVal("expense_id", r.id); setVal("expense_type", r.expense_type); setVal("expense_month", r.expense_month); setVal("amount", r.amount); setVal("incurred_amount", r.incurred_amount); setVal("notes", r.notes); document.querySelector("#expenseModalTitle").textContent = "编辑费用"; document.querySelector("#expenseDeleteBtn").classList.remove("hidden"); document.querySelector("#expenseModal").classList.remove("hidden"); }; window.saveExpense = async function(event) { event.preventDefault(); var form = event.target; var data = Object.fromEntries(new FormData(form).entries()); var isEdit = !!data.expense_id; delete data.expense_id; var id = form.querySelector('[name="expense_id"]').value; var url = isEdit ? "/api/expense/" + id : "/api/expense"; 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) { alert("保存失败"); return; } var bResp = await fetch("/api/bootstrap?tenant=" + encodeURIComponent(state.tenant)); if (bResp.ok) { var bd = await bResp.json(); state.data = bd; applyUserTenants(); renderExpense(); } closeExpenseModal(); }; window.deleteExpenseItem = async function() { var id = document.querySelector("#expenseModal form [name='expense_id']").value; if (!id || !confirm("确定删除?")) return; var resp = await fetch("/api/expense/" + 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(); renderExpense(); } };