-
本月工时
+
{{ currentMonth + 1 }}月工时
上半月 1–{{ settings.cutoff }}日
{{ fmt(monthStats.h1) }}h
{{ money(monthStats.h1 * settings.rate) }}
+
+
本月{{ settings.payDayA }}号发
下半月 {{ settings.cutoff + 1 }}日–月底
{{ fmt(monthStats.h2) }}h
{{ money(monthStats.h2 * settings.rate) }}
+
结转{{ nextLabel.month + 1 }}月{{ settings.payDayB }}号发
@@ -404,7 +472,7 @@
-
本月奖惩
+
{{ currentMonth + 1 }}月奖惩
次月{{ settings.payDayB }}号结算
以下金额不计入当月工资
@@ -473,21 +541,25 @@
-
+
-
{{ nextLabel.month + 1 }}月{{ settings.payDayB }}日预估
-
{{ money(payoutNextB) }}
+
+
结转到 {{ nextLabel.month + 1 }}月{{ settings.payDayB }}日
+
{{ currentMonth + 1 }}月挣得里未发的部分
+
+
{{ money(payoutNextB) }}
-
本月下半月工时 {{ fmt(monthStats.h2) }}h{{ money(monthStats.h2 * settings.rate) }}
-
本月奖惩结算{{ signed(monthStats.adjustNet) }}
+
{{ currentMonth + 1 }}月下半月工时 {{ fmt(monthStats.h2) }}h{{ money(monthStats.h2 * settings.rate) }}
+
{{ currentMonth + 1 }}月奖惩结算{{ signed(monthStats.adjustNet) }}
奖惩口径
{{ monthStats.finalized ? '已定稿' : '预估(未申报)' }}
-
处罚已超过下半月工时收入,实发按 ¥0 计,差额可能顺延
+
处罚已超过下半月工时收入,实发按 ¥0 计,差额可能顺延
@@ -1071,6 +1143,27 @@ createApp({
const payoutThisB = computed(() => Math.max(0, prevStats.value.h2 * cfg.value.rate + prevStats.value.adjustNet));
const payoutNextB = computed(() => Math.max(0, monthStats.value.h2 * cfg.value.rate + monthStats.value.adjustNet));
+ // ===== 两套记账口径(这是页面之前最主要的歧义源) =====
+ // 归属制「挣得」:本月 1–月底干出来的钱,不管什么时候发。打一天卡当天就涨。
+ // 收付制「进账」:本月卡里实际进的钱,含上月结转部分。
+ // 两者永远不相等,必须各自具名、分区展示,不能都叫「本月」。
+ const earnedTotal = computed(() => monthStats.value.basePay + monthStats.value.adjustNet);
+ const cashTotal = computed(() => payoutThisB.value + payoutThisA.value);
+ // 「挣得」拆成两笔落地时间:上半月工时随本月 payDayA;下半月工时 + 奖惩结转次月 payDayB。
+ // 这一行是两套口径之间唯一的桥,解释了为何挣得 ≠ 进账。
+ const earnedSplit = computed(() => ({ paid: payoutThisA.value, carry: payoutNextB.value }));
+ // 上半月那笔到没到账,只在看当前月份时才有「待发」说法;翻历史月份一律算已发。
+ const payDayAPassed = computed(() => {
+ if (!isCurrentMonth.value) {
+ const n = new Date();
+ return currentYear.value < n.getFullYear() ||
+ (currentYear.value === n.getFullYear() && currentMonth.value < n.getMonth());
+ }
+ return new Date().getDate() >= cfg.value.payDayA;
+ });
+ // 处罚吃穿下半月工时时,结转那笔被 ¥0 兰住,于是 paid+carry > earnedTotal。页面需要明说。
+ const earnedFloored = computed(() => monthStats.value.h2 * cfg.value.rate + monthStats.value.adjustNet < 0);
+
const penaltyDetails = computed(() => {
const c = cfg.value;
const prefix = monthPrefix(currentYear.value, currentMonth.value);
@@ -1094,7 +1187,7 @@ createApp({
const recordCount = computed(() => Object.keys(records.value).length);
// 看板主数字:本月两笔到账合计
- const boardMain = computed(() => moneyParts(payoutThisB.value + payoutThisA.value));
+ const boardMain = computed(() => moneyParts(earnedTotal.value));
const hasPenalty = (day) => {
const r = getRecord(day);
@@ -1296,22 +1389,28 @@ createApp({
const buildReport = () => {
const s = monthStats.value, p = prevStats.value, c = cfg.value;
const m = currentMonth.value + 1, nm = nextLabel.value.month + 1, pm = prevLabel.value.month + 1;
+ // 报告也按「挣得 / 进账」两套口径分段,与页面用词保持一致,否则复制出去又是一笔糊涂账。
return [
`${currentYear.value}年${m}月工时薪酬复盘(时薪 ${c.rate} 元/h)`,
`出勤 ${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)}`,
``,
- `【本月奖惩·次月${settings.payDayB}号结算】`,
+ `【${m}月挣得·干活口径】${money(earnedTotal.value)}`,
+ `工时收入 ${money(s.basePay)}(确定) | 奖惩净额 ${signed(s.adjustNet)}(${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.adjustNet)}`,
``,
- `【本月到账】`,
- `${m}月${settings.payDayB}日 ${money(payoutThisB.value)}(${pm}月下半月 ${fmt(p.h2)}h + ${pm}月奖惩 ${signed(p.adjustNet)})`,
- `${m}月${settings.payDayA}日 ${money(payoutThisA.value)}(本月上半月 ${fmt(s.h1)}h)`,
- `合计 ${money(payoutThisB.value + payoutThisA.value)}`,
+ `【${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(payoutNextB.value)}(本月下半月 ${fmt(s.h2)}h + 奖惩 ${signed(s.adjustNet)})`
+ `【${nm}月${settings.payDayB}日预估·结转】${money(payoutNextB.value)}(${m}月下半月 ${fmt(s.h2)}h + 奖惩 ${signed(s.adjustNet)})`
].join('\n');
};
@@ -1439,6 +1538,7 @@ createApp({
copied, dataCopied, toast,
monthStats, prevStats, prevLabel, nextLabel,
payoutThisA, payoutThisB, payoutNextB, penaltyDetails,
+ earnedTotal, cashTotal, earnedSplit, earnedFloored, payDayAPassed,
fmt, money, signed, moneyParts, boardMain, buildReport, applyImport,
isToday, isCurrentMonth, getRecord, hasPenalty, cellClass, isCutEdge,
payDayKind, selectedPayInfo,