diff --git a/index.html b/index.html index 9a9a81c..c0a9e6c 100644 --- a/index.html +++ b/index.html @@ -253,43 +253,53 @@ 注意:这里故意用归属制(本月干出来的)而不是现金流。日历页的核心动作是打卡, 用归属制打一天卡数字当场就涨;用现金流则毫无反馈(影响的是下一个发薪日)。 -->
+

- {{ currentMonth + 1 }}月挣得 + {{ currentMonth + 1 }}月总劳动应得

-

+ +

¥ {{ boardMain.int }} {{ boardMain.frac }} - - + + - +{{ money(fullBonusEarned) }} - 满勤奖 + 已达满勤¥{{ settings.fullBonusPerHour }}/h + + + + 出勤 {{ monthStats.workDays }}/{{ settings.fullDays }} + · 差 {{ fullDaysLeft }} 天满勤

- -

- {{ currentMonth + 1 }}月进账 {{ money(cashTotal) }}(含{{ prevLabel.month + 1 }}月结转 {{ money(payoutThisB) }}) -

- -
- - - 工时 {{ fmt(monthStats.totalHours) }}h + +
+ 工时 {{ money(monthStats.basePay) }}({{ fmt(monthStats.totalHours) }}h) + + + 满勤 {{ money(monthStats.fullBonus) }} + + + 奖惩 {{ signed(monthStats.reviewNet) }} + {{ monthStats.finalized ? '已定稿' : '待审核' }}
- -
- 工时收入 {{ money(monthStats.basePay) }} - 满勤奖 {{ money(fullBonusEarned) }} - 已达计入 - 未达不计 - - 奖惩净额 {{ signed(monthStats.reviewNet) }} + +
+

+ 这笔钱什么时候到手 +

+
+ {{ currentMonth + 1 }}/{{ settings.payDayA }} 上半月纯工时({{ payDayAPassed ? '已发' : '待发' }}) + {{ money(earnedSplit.paid) }} +
+
+ {{ nextLabel.month + 1 }}/{{ settings.payDayB }} 下半月+奖惩+满勤(结转) + {{ money(earnedSplit.carry) }} +
@@ -366,9 +376,10 @@ 与下面的「进账」卡严格分区,两者不得再共用「本月到账」这种模糊叫法。 -->
- {{ currentMonth + 1 }}月挣得 + {{ currentMonth + 1 }}月总劳动应得 {{ currentMonth + 1 }}/1–{{ currentMonth + 1 }}/{{ daysInMonth }}
+
工时收入({{ fmt(monthStats.totalHours) }}h) @@ -378,9 +389,17 @@
- 奖惩净额(不含满勤奖) + 满勤奖{{ monthStats.workDays >= settings.fullDays ? '(已达' + settings.fullDays + '天)' : '(未达' + settings.fullDays + '天,不计)' }} - {{ monthStats.finalized ? '已定稿' : '预估·待审核' }} + {{ monthStats.workDays >= settings.fullDays ? '已达标' : monthStats.workDays + '/' + settings.fullDays }} + {{ monthStats.fullBonus > 0 ? '+' + money(monthStats.fullBonus) : '¥0' }} + +
+ +
+ 奖惩净额(查获−漏查−扣款) + + {{ monthStats.finalized ? '已定稿' : '记录·待审核' }} {{ signed(monthStats.reviewNet) }}
@@ -388,6 +407,7 @@ 合计应得 {{ money(earnedTotal) }}
+

查获在本月只作记录;实际奖金需次月初组长审核、扣除漏查与其他扣款后定稿,金额可能与此处累计不同。

@@ -399,7 +419,7 @@ {{ money(earnedSplit.paid) }}
- {{ nextLabel.month + 1 }}/{{ settings.payDayB }} 下半月工时+奖惩(结转) + {{ nextLabel.month + 1 }}/{{ settings.payDayB }} 下半月工时+奖惩+满勤(结转) {{ money(earnedSplit.carry) }}
@@ -490,7 +510,7 @@

以下金额不计入当月工资

- 查获奖励({{ monthStats.foundCount }}件) + 查获记录({{ monthStats.foundCount }}件 · 待审核) {{ monthStats.foundBonus > 0 ? '+' + money(monthStats.foundBonus) : '¥0' }}
@@ -527,7 +547,7 @@
- 奖惩净额(不含满勤奖) + 奖惩净额(查获−漏查−扣款) {{ signed(monthStats.reviewNet) }}
@@ -559,16 +579,18 @@

结转到 {{ nextLabel.month + 1 }}月{{ settings.payDayB }}日

-

{{ currentMonth + 1 }}月挣得里未发的部分

+

{{ currentMonth + 1 }}月总应得里未发的部分 · 与实际到账同额

{{ money(earnedCarry) }}
+
{{ currentMonth + 1 }}月下半月工时 {{ fmt(monthStats.h2) }}h{{ money(monthStats.h2 * settings.rate) }}
+
{{ currentMonth + 1 }}月满勤奖{{ monthStats.fullBonus > 0 ? '+' + money(monthStats.fullBonus) : '¥0' }}
{{ currentMonth + 1 }}月奖惩结算{{ signed(monthStats.reviewNet) }}
奖惩状态 - {{ monthStats.finalized ? '已定稿' : '预估(未申报)' }} + {{ monthStats.finalized ? '已定稿' : '记录·待次月初审核' }}

处罚已超过下半月工时收入,实发按 ¥0 计,差额可能顺延

@@ -1147,9 +1169,10 @@ createApp({ const finalized = !!(adj && adj.status === 'final' && Number.isFinite(Number(adj.finalAmount))); const reviewAmount = finalized ? Number(adj.finalAmount) : rawReview; const reviewDelta = finalized ? reviewAmount - rawReview : 0; - // 奖惩净额(不含满勤奖):用于「挣得」口径——满勤奖月底才确定,不混进已确认的劳动所得。 + // 奖惩净额(不含满勤奖):查获−漏查−其他扣款,需次月初组长审核定稿;页面单列这一项。 const reviewNet = reviewAmount; - // 发放净额(含满勤奖):进账/结转实际会发这笔,满勤奖次月 payDayB 确实到账。 + // 发放净额 = 满勤奖 + 奖惩净额。次月 payDayB 这两笔与下半月工时一起发, + // 「挣得」与「结转」两套口径都复用它,保证同名同数。 const adjustNet = fullBonus + reviewAmount; return { @@ -1181,12 +1204,18 @@ createApp({ // 只含工时 + 已确认奖惩(满勤奖月底才确定,不混入挣得,单独展示)。 // 收付制「进账」:本月卡里实际进的钱,含上月结转部分。 // 两者永远不相等,必须各自具名、分区展示,不能都叫「本月」。 - const earnedTotal = computed(() => monthStats.value.basePay + monthStats.value.reviewNet); + // 决策1:统一「X月挣得」= 本月劳动产生的全部价值(不管哪天发)。 + // = 工时收入 + 满勤奖(未达 27 天为 0)+ 奖惩净额(未定稿取日常累计,已定稿取终稿) + // adjustNet 本身 = fullBonus + reviewAmount,正好是「满勤奖 + 奖惩净额」。 + // 打卡页大字与汇总页「合计应得」共用这一个值,不再有两个不同的「挣得」。 + const earnedTotal = computed(() => monthStats.value.basePay + monthStats.value.adjustNet); const cashTotal = computed(() => payoutThisB.value + payoutThisA.value); // 「挣得」拆成两笔落地时间:上半月工时随本月 payDayA;下半月工时 + 奖惩结转次月 payDayB。 // 这一行是两套口径之间唯一的桥,解释了为何挣得 ≠ 进账。 // 注意:结转那笔只用「已确认的奖惩」,满勤奖月底才确定,不计入挣得(另作预估展示)。 - const earnedCarry = computed(() => Math.max(0, monthStats.value.h2 * cfg.value.rate + monthStats.value.reviewNet)); + // 决策2:结转 = 次月 payDayB 实际到账那一笔,必须含满勤奖(业务规则:满勤奖压到次月 10 号发)。 + // 直接复用 payoutNextB,保证「结转」与「次月实际上账」永远同一个数,不再出现账目缺口。 + const earnedCarry = computed(() => payoutNextB.value); const earnedSplit = computed(() => ({ paid: payoutThisA.value, carry: earnedCarry.value })); // 上半月那笔到没到账,只在看当前月份时才有「待发」说法;翻历史月份一律算已发。 const payDayAPassed = computed(() => { @@ -1198,7 +1227,7 @@ createApp({ return new Date().getDate() >= cfg.value.payDayA; }); // 处罚吃穿下半月工时时,结转那笔被 ¥0 兰住,于是 paid+carry > earnedTotal。页面需要明说。 - const earnedFloored = computed(() => monthStats.value.h2 * cfg.value.rate + monthStats.value.reviewNet < 0); + const earnedFloored = computed(() => monthStats.value.h2 * cfg.value.rate + monthStats.value.adjustNet < 0); const penaltyDetails = computed(() => { const c = cfg.value; @@ -1222,14 +1251,11 @@ createApp({ const recordCount = computed(() => Object.keys(records.value).length); - // 看板主数字(打卡页顶部「X月挣得」):只含「本月工时收入 + 已达标满勤奖」。 - // 满勤奖未达标(< fullDays 天)绝不计入;日常查获/漏查等奖惩也不混入这个大数字 - //(它们有专门的奖惩胶囊/汇总卡展示,避免顶部数字忽高忽低、误导「还在挣」的直觉)。 - // 满勤达标后满勤奖才以「可确认的奖励」身份加入,并在页面上显著标注。 - const fullBonusEarned = computed(() => monthStats.value.workDays >= cfg.value.fullDays - ? monthStats.value.fullBonus : 0); - const boardEarned = computed(() => monthStats.value.basePay + fullBonusEarned.value); - const boardMain = computed(() => moneyParts(boardEarned.value)); + // 看板主数字 = 统一口径的「X月挣得」,与汇总页「合计应得」严格同源。 + // 明细行三项(工时收入 + 满勤奖 + 奖惩净额)相加必须严丝合缝等于这个大字。 + const boardMain = computed(() => moneyParts(earnedTotal.value)); + // 距满勤还差几天(达标后为 0),供顶部进度胶囊使用 + const fullDaysLeft = computed(() => Math.max(0, cfg.value.fullDays - monthStats.value.workDays)); const hasPenalty = (day) => { const r = getRecord(day); @@ -1437,22 +1463,22 @@ createApp({ `出勤 ${s.workDays}/${c.fullDays} 天(白${s.dayShifts}/夜${s.nightShifts}) | 总工时 ${fmt(s.totalHours)}h`, `上半月 ${fmt(s.h1)}h → ${money(s.h1 * c.rate)} 下半月 ${fmt(s.h2)}h → ${money(s.h2 * c.rate)}`, ``, - `【${m}月挣得】${money(earnedTotal.value)}`, - `工时收入 ${money(s.basePay)}(确定) | 奖惩净额 ${signed(s.reviewNet)}(${s.finalized ? '已定稿' : '预估·待审核'},不含满勤奖)`, - `何时到手:${m}/${settings.payDayA} ${money(earnedSplit.value.paid)}(上半月工时) | ${nm}/${settings.payDayB} ${money(earnedSplit.value.carry)}(结转:下半月工时+奖惩)`, + `【${m}月总劳动应得】${money(earnedTotal.value)}`, + `工时收入 ${money(s.basePay)}(确定) | 满勤奖 ${money(s.fullBonus)}(${s.workDays >= settings.fullDays ? '已达标' : '未达' + settings.fullDays + '天不计'}) | 奖惩净额 ${signed(s.reviewNet)}(${s.finalized ? '已定稿' : '记录·待次月初审核'})`, + `何时到手:${m}/${settings.payDayA} ${money(earnedSplit.value.paid)}(上半月工时) | ${nm}/${settings.payDayB} ${money(earnedSplit.value.carry)}(结转:下半月工时+奖惩+满勤奖)`, ``, `【${m}月奖惩明细·次月${settings.payDayB}号结算】`, `满勤奖 ${s.fullBonus > 0 ? '+' + money(s.fullBonus) : '¥0(未满勤)'}`, `查获 +${money(s.foundBonus)}(${s.foundCount}件) | 漏查 -${money(s.missedPenalty)}(${s.missedCount}件) | 其他 -${money(s.otherPenalties)}`, `日常累计(待申报)${signed(s.rawReview)}${s.finalized ? ` → 审核终稿 ${signed(s.reviewAmount)}` : ''}`, - `奖惩净额 ${signed(s.reviewNet)}(不含满勤奖)`, + `奖惩净额 ${signed(s.reviewNet)}(查获−漏查−扣款,${s.finalized ? '已定稿' : '待次月初审核'})`, ``, `【${m}月进账·实际到账】${money(cashTotal.value)}`, `${m}月${settings.payDayB}日 ${money(payoutThisB.value)}(归属${pm}月:下半月 ${fmt(p.h2)}h + 奖惩 ${signed(p.adjustNet)})`, `${m}月${settings.payDayA}日 ${money(payoutThisA.value)}(归属${m}月:上半月 ${fmt(s.h1)}h)`, `合计 ${money(cashTotal.value)}(含${pm}月结转 ${money(payoutThisB.value)})`, ``, - `【${nm}月${settings.payDayB}日预估·结转】${money(earnedCarry.value)}(${m}月下半月 ${fmt(s.h2)}h + 奖惩 ${signed(s.reviewNet)},不含满勤奖)` + `【${nm}月${settings.payDayB}日预估·结转】${money(earnedCarry.value)}(${m}月下半月 ${fmt(s.h2)}h + 满勤奖 ${money(s.fullBonus)} + 奖惩 ${signed(s.reviewNet)})` ].join('\n'); }; @@ -1581,7 +1607,7 @@ createApp({ monthStats, prevStats, prevLabel, nextLabel, payoutThisA, payoutThisB, payoutNextB, penaltyDetails, earnedTotal, cashTotal, earnedSplit, earnedFloored, payDayAPassed, - fullBonusEarned, boardEarned, + fullDaysLeft, fmt, money, signed, moneyParts, boardMain, buildReport, applyImport, isToday, isCurrentMonth, getRecord, hasPenalty, cellClass, isCutEdge, payDayKind, selectedPayInfo,