// my.js — 我的模块(绩效 + 预算月报 + 已发生月报) window.renderMy = () => { const data = state.data; if (!data) return; // 确保数据已加载 if (!data._myDataLoaded) { loadMyData().then(() => { data._myDataLoaded = true; renderMy(); }); return; } // ── 状态初始化 ── if (state.planYear == null) state.planYear = new Date().getFullYear(); const planYear = state.planYear; if (!['annual','quarterly','monthly'].includes(state.perfView)) state.perfView = 'quarterly'; if (state.perfQuarter === undefined) state.perfQuarter = Math.floor(new Date().getMonth() / 3); const nowD = new Date(); const activeQ = state.perfQuarter; const qRanges = [[1,2,3],[4,5,6],[7,8,9],[10,11,12]]; const activeMonths = qRanges[activeQ]; const qLabels = ['Q1','Q2','Q3','Q4']; let aggMonths = []; if (state.perfView === 'annual') { for (var mi = 1; mi <= 12; mi++) aggMonths.push(mi); } else if (state.perfView === 'quarterly') { aggMonths = qRanges[activeQ]; } else if (state.perfView === 'monthly') { const m = state.perfMonth; aggMonths = m ? [parseInt(m.substring(5))] : []; } if (state.perfView === 'monthly' && !state.perfMonth) { state.perfMonth = planYear + '-' + String(nowD.getMonth() + 1).padStart(2, '0'); } const pfs = (data.projectFinances || []); const planPfs = (data.planFinances || []); // ── 1. 绩效 ── function sumByMonths(items, field, useSign, months) { let total = 0; items.forEach(p => { if (useSign) { const sm = p.sign_month || ''; const smY = parseInt(sm.substring(0, 4)) || 0; const smM = parseInt(sm.substring(5)) || 0; if (!months.includes(smM)) return; if (planYear !== 0 && smY && smY !== planYear) return; total += parseFloat(p.sign_amount || 0); } else { let bd = []; try { bd = JSON.parse(p.budget_data || '[]'); } catch (e) {} bd.forEach(b => { const m = parseInt((b.month || '').substring(5)) || 0; const y = parseInt((b.month || '').substring(0, 4)) || 0; if (!months.includes(m)) return; if (planYear !== 0 && y && y !== planYear) return; total += parseFloat(b[field] || 0); }); } }); return Math.round(total); } const perfRows = [ { key: 'sign', label: '签约', complete: sumByMonths(pfs, '', true, aggMonths), plan: sumByMonths(planPfs, '', true, aggMonths), icon: 'file-text', defWeight: 10 }, { key: 'rev', label: '收入', complete: sumByMonths(pfs, 'rev', false, aggMonths), plan: sumByMonths(planPfs, 'rev', false, aggMonths), icon: 'dollar-sign', defWeight: 20 }, { key: 'gross', label: '毛利', complete: sumByMonths(pfs, 'gross', false, aggMonths), plan: sumByMonths(planPfs, 'gross', false, aggMonths), icon: 'trending-up', defWeight: 50 }, { key: 'payment', label: '回款', complete: sumByMonths(pfs, 'payment', false, aggMonths), plan: sumByMonths(planPfs, 'payment', false, aggMonths), icon: 'coins', defWeight: 20 }, ]; let storageKey = 'opc-perf-t'; if (state.perfView === 'quarterly') storageKey = 'opc-perf-Q' + (activeQ + 1); else if (state.perfView === 'monthly') storageKey = 'opc-perf-M' + (state.perfMonth || ''); const saved = JSON.parse(localStorage.getItem(storageKey) || '{}'); function val(key, field, def) { const r = saved[key] || {}; return r[field] !== undefined && r[field] !== '' ? Number(r[field]) : def; } function moneyWan(v) { if (Math.abs(v) >= 10000) return (v / 10000).toFixed(1) + '万'; return v.toLocaleString(); } function moneyIntL(v) { return Math.round(Number(v || 0)).toLocaleString('zh-CN'); } let totalScore = 0; const perfHTML = perfRows.map(r => { const target = r.plan; const weight = val(r.key, 'weight', r.defWeight); const score = target > 0 ? Math.round((r.complete / target) * weight * 10) / 10 : 0; const rate = target > 0 ? Math.round((r.complete / target) * 100) : 0; totalScore += score; return ` ${r.label} ${moneyWan(target)} ${moneyWan(r.complete)} ${rate}%
${score.toFixed(1)}`; }).join(''); // ── 绩效 Header ── const yearOpts = [2020,2021,2022,2023,2024,2025,2026,2027]; const yearSelect = `年份:`; const viewBtns = ``; const quarterBtns = qLabels.map((ql,i) => ``).join(''); const monthBtns = activeMonths.map(m => { const ms = planYear+'-'+String(m).padStart(2,'0'); return ``; }).join(''); const perfHeader = `
${yearSelect}|${viewBtns}|季度:${quarterBtns}|月度:${monthBtns}
`; // ── 2. 预算月报 ── const planFm = computePlanMonthly(planPfs, planYear); const mLbl = ['1月','2月','3月','4月','5月','6月','7月','8月','9月','10月','11月','12月']; const pLabels = ['合同金额','确收金额','确收毛利','成本','支出','回款','项目现金流']; const pFields = ['sign','revenue','gross','cost','paid','payment',null]; var pAnnual = { sign:0, revenue:0, gross:0, cost:0, paid:0, payment:0 }; for (var i = 0; i < planFm.length; i++) { pAnnual.sign += planFm[i].sign; pAnnual.revenue += planFm[i].revenue; pAnnual.gross += planFm[i].gross; pAnnual.cost += planFm[i].cost; pAnnual.paid += planFm[i].paid; pAnnual.payment += planFm[i].payment; } var planThead = '指标年度累计'; for (var mi2 = 0; mi2 < 12; mi2++) { planThead += '' + mLbl[mi2] + ''; } planThead += ''; var planTbody = ''; for (var ri = 0; ri < pLabels.length; ri++) { var label = pLabels[ri]; var fKey = pFields[ri]; var annualVal = fKey ? pAnnual[fKey] : pAnnual.payment - pAnnual.paid; planTbody += '' + label + '' + moneyIntL(annualVal) + ''; for (var mi3 = 0; mi3 < 12; mi3++) { if (fKey) { planTbody += '' + moneyIntL(planFm[mi3][fKey]) + ''; } else { planTbody += '' + moneyIntL(planFm[mi3].payment - planFm[mi3].paid) + ''; } } planTbody += ''; } var planOverviewHTML = `
${planThead}${planTbody}
`; // ── 3. 已发生月报 ── const finPfs = (data.projectFinances || []); const finFm = computePlanMonthly(finPfs, planYear); var fAnnual = { sign:0, revenue:0, gross:0, cost:0, paid:0, payment:0 }; for (var fi = 0; fi < finFm.length; fi++) { fAnnual.sign += finFm[fi].sign; fAnnual.revenue += finFm[fi].revenue; fAnnual.gross += finFm[fi].gross; fAnnual.cost += finFm[fi].cost; fAnnual.paid += finFm[fi].paid; fAnnual.payment += finFm[fi].payment; } var finThead = '指标年度累计'; for (var mi4 = 0; mi4 < 12; mi4++) { finThead += '' + mLbl[mi4] + ''; } finThead += ''; var finTbody = ''; var fLabels2 = ['合同金额','确收金额','确收毛利','成本','支出','回款','项目现金流']; for (var ri2 = 0; ri2 < fLabels2.length; ri2++) { var fKey2 = pFields[ri2]; var aVal = fKey2 ? fAnnual[fKey2] : fAnnual.payment - fAnnual.paid; finTbody += '' + fLabels2[ri2] + '' + moneyIntL(aVal) + ''; for (var mi5 = 0; mi5 < 12; mi5++) { if (fKey2) { finTbody += '' + moneyIntL(finFm[mi5][fKey2]) + ''; } else { finTbody += '' + moneyIntL(finFm[mi5].payment - finFm[mi5].paid) + ''; } } finTbody += ''; } var finOverviewHTML = `
${finThead}${finTbody}
`; // ── 组装页面 ── document.querySelector("#my").innerHTML = `

绩效

${perfHeader}
${perfHTML}
指标 目标(万) 完成情况 完成率 权重 评分
合计100${totalScore.toFixed(1)}

预算月报

${planOverviewHTML}

已发生月报

${finOverviewHTML}

重点项目

${renderMyProjects()}
`; // ── 重点项目列表 ── function renderMyProjects() { const ops = state.data.operations || []; if (!ops.length) return '

暂无项目

'; return '
' + ops.map(o => { return ` ${esc(o.project_name || '')} `; }).join('') + '
'; } if (window.lucide) window.lucide.createIcons(); }; // ── 筛选切换 ── window.setMyYear = (y) => { state.planYear = parseInt(y); renderMy(); }; window.setMyView = (v) => { state.perfView = v; renderMy(); }; window.setMyQuarter = (q) => { state.perfView = 'quarterly'; state.perfQuarter = q; renderMy(); }; window.setMyMonth = (m) => { state.perfView = 'monthly'; state.perfMonth = m; renderMy(); }; window.updateMyScore = (key, field, value) => { let storageKey = 'opc-perf-t'; if (state.perfView === 'quarterly') storageKey = 'opc-perf-Q' + ((state.perfQuarter || 0) + 1); else if (state.perfView === 'monthly') storageKey = 'opc-perf-M' + (state.perfMonth || ''); const saved = JSON.parse(localStorage.getItem(storageKey) || '{}'); saved[key] = saved[key] || {}; saved[key][field] = value; localStorage.setItem(storageKey, JSON.stringify(saved)); renderMy(); }; // ── 数据加载 ── async function loadMyData() { for (var i = 0; i < 3; i++) { var r = i === 0 ? 'planFinances' : i === 1 ? 'projectFinances' : 'operations'; try { state.data[r] = await api("/api/" + r + "/list?tenant=" + encodeURIComponent(state.tenant)); } catch(e) { console.error('loadMyData failed:', r, e.message); } } }