fix: F1-5b MP-3 + MP-5 沙箱业务日小程序适配 (W1)
MP-3 customer-detail coachTasks.lastService 业务日上界裁剪:
- apps/backend/app/services/customer_service.py
- import as_runtime_today_param 从 late import 提至模块顶部
- _build_coach_tasks 开头取 ref_date,供两段 SQL 共用
- 第一条直查 biz.coach_tasks 加 `AND updated_at < (%s::date + INTERVAL '1 day')::timestamptz`
- 删除原方法内重复 ref_date 调用
- 业务影响:sandbox=2026-04-20 时,customer-detail 的"上次服务"
时间不再展示 sandbox 业务日之后的助教任务更新(沙箱不读未来)
- 测试:apps/backend/tests/test_customer_detail_mp3_lastservice.py
本地通过,因 .gitignore:71 不入仓(同 T1 / af02446 处理方式)
MP-5 coach-service-records 接入 getBusinessClock:
- apps/miniprogram/miniprogram/pages/coach-service-records/coach-service-records.ts
- import getBusinessClock + data 加 clockYear/clockMonth/clockDay 字段
- onLoad 改 async,await getBusinessClock() 取 business_year/month/date
- loadData / switchMonth 4 处 new Date() → clockYear/Month/Day
- 业务影响:sandbox=2026-04-20 时,coach-service-records 默认显示
"2026 年 4 月"业绩(而非 today 月),canGoNext=false 阻止翻到 5 月,
"前 5 日预估金额"规则按 sandbox business_date 判断
双口径验证(weixin-devtools-mcp + DB 直查):
- MP-3 4a live: lastService 最大 04-19(无未来时间)
- MP-3 4b sandbox=4-20: 5-01 任务 task_id=8348/8347 完全消失
- MP-5 4a live: clockYear/Month/Day=2026/5/5,monthLabel="2026年5月"
- MP-5 4b sandbox=4-20: monthLabel="2026年4月" + 35 笔/¥4,657
first group=2026-04-20(后端 SQL 上界裁剪生效)
审计:
- docs/audit/changes/2026-05-05__wave1_f1_5b_mp3_lastservice_upper_bound.md
- docs/audit/changes/2026-05-05__wave1_f1_5b_mp5_coach_service_records_clock.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -24,6 +24,7 @@ from fastapi import HTTPException
|
||||
from decimal import Decimal
|
||||
|
||||
from app.services import fdw_queries
|
||||
from app.services.runtime_context import as_runtime_today_param
|
||||
from app.services.task_generator import compute_heart_icon
|
||||
from app.trace.decorators import trace_service
|
||||
|
||||
@@ -458,7 +459,12 @@ def _build_coach_tasks(
|
||||
构建 coachTasks 模块。
|
||||
|
||||
CHANGE 2026-03-29 | 性能优化:所有助教信息改为批量查询,消除 N+1
|
||||
CHANGE 2026-05-05 | F1-5b MP-3: lastService (updated_at) 加 business_date 上界,沙箱不读未来
|
||||
"""
|
||||
# 业务日:sandbox 取 sandbox_business_date,live 取 CURRENT_DATE。
|
||||
# 该值同时用于第一条 SQL(coach_tasks 上界)和后续 60 天统计(ref_date)。
|
||||
ref_date = as_runtime_today_param(site_id, conn=conn)
|
||||
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
@@ -466,9 +472,10 @@ def _build_coach_tasks(
|
||||
FROM biz.coach_tasks
|
||||
WHERE member_id = %s
|
||||
AND status IN ('active', 'inactive')
|
||||
AND updated_at < (%s::date + INTERVAL '1 day')::timestamptz
|
||||
ORDER BY created_at DESC
|
||||
""",
|
||||
(customer_id,),
|
||||
(customer_id, ref_date),
|
||||
)
|
||||
rows = cur.fetchall()
|
||||
|
||||
@@ -502,8 +509,7 @@ def _build_coach_tasks(
|
||||
|
||||
# 批量查询 60 天统计(一次 FDW 查询)
|
||||
# CHANGE 2026-05-02 | 用 business_date 替代 CURRENT_DATE,沙箱不读「未来」
|
||||
from app.services.runtime_context import as_runtime_today_param
|
||||
ref_date = as_runtime_today_param(site_id, conn=conn)
|
||||
# ref_date 已在方法开头取得,此处直接复用
|
||||
stats_map: dict = {}
|
||||
try:
|
||||
with fdw_queries._fdw_context(conn, site_id) as cur:
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
* - 后端 /api/xcx/performance/records?coach_id=xxx,权限码 view_board_coach
|
||||
*/
|
||||
import { checkPageAccess } from '../../utils/auth-guard'
|
||||
// CHANGE 2026-05-05 | F1-5b MP-5: 业务时钟接入,sandbox 模式按 sandbox_date 判断"当前月/预估"
|
||||
import { getBusinessClock } from '../../utils/runtime-clock'
|
||||
import { fetchPerformanceRecords, fetchCoachBanner } from '../../services/api'
|
||||
import { nameToAvatarColor } from '../../utils/avatar-color'
|
||||
import { formatMoney, formatCount } from '../../utils/money'
|
||||
@@ -61,13 +63,18 @@ Page({
|
||||
/** Banner 主标题:用助教名生成"<助教名>的业绩" */
|
||||
pageTitle: '业绩明细',
|
||||
|
||||
/** 月份切换 */
|
||||
/** 月份切换(onLoad 内会用 getBusinessClock 覆盖) */
|
||||
currentYear: new Date().getFullYear(),
|
||||
currentMonth: new Date().getMonth() + 1,
|
||||
monthLabel: '',
|
||||
canGoPrev: true,
|
||||
canGoNext: false,
|
||||
|
||||
/** 业务时钟缓存(onLoad 内由 getBusinessClock 写入,sandbox 模式取 sandbox_date) */
|
||||
clockYear: new Date().getFullYear(),
|
||||
clockMonth: new Date().getMonth() + 1,
|
||||
clockDay: new Date().getDate(),
|
||||
|
||||
/** 当月预估判断 */
|
||||
isCurrentMonth: false,
|
||||
|
||||
@@ -86,7 +93,7 @@ Page({
|
||||
hasMore: false,
|
||||
},
|
||||
|
||||
onLoad(options: Record<string, string | undefined>) {
|
||||
async onLoad(options: Record<string, string | undefined>) {
|
||||
const coachIdNum = Number(options?.coachId)
|
||||
const coachId = Number.isFinite(coachIdNum) && coachIdNum > 0 ? coachIdNum : 0
|
||||
if (coachId === 0) {
|
||||
@@ -95,12 +102,20 @@ Page({
|
||||
setTimeout(() => wx.navigateBack({ fail: () => wx.switchTab({ url: '/pages/board-finance/board-finance' }) }), 1000)
|
||||
return
|
||||
}
|
||||
const now = new Date()
|
||||
// CHANGE 2026-05-05 | 业务时钟取代 new Date(),sandbox 模式下显示 sandbox_date 所在月
|
||||
const clock = await getBusinessClock()
|
||||
const clockYear = clock.business_year
|
||||
const clockMonth = clock.business_month
|
||||
// business_date 形如 "2026-04-20",取 day 用于"当月前 5 日预估"判断
|
||||
const clockDay = Number(clock.business_date.slice(8, 10)) || new Date().getDate()
|
||||
this.setData({
|
||||
coachId,
|
||||
currentYear: now.getFullYear(),
|
||||
currentMonth: now.getMonth() + 1,
|
||||
monthLabel: `${now.getFullYear()}年${now.getMonth() + 1}月`,
|
||||
currentYear: clockYear,
|
||||
currentMonth: clockMonth,
|
||||
monthLabel: `${clockYear}年${clockMonth}月`,
|
||||
clockYear,
|
||||
clockMonth,
|
||||
clockDay,
|
||||
})
|
||||
this.loadBanner()
|
||||
this.loadData()
|
||||
@@ -149,11 +164,11 @@ Page({
|
||||
}
|
||||
wx.showLoading({ title: '加载中...', mask: true })
|
||||
|
||||
const now = new Date()
|
||||
const { currentYear, currentMonth } = this.data
|
||||
const isCurrentMonth = currentYear === now.getFullYear()
|
||||
&& currentMonth === now.getMonth() + 1
|
||||
&& now.getDate() <= 5
|
||||
// CHANGE 2026-05-05 | 业务时钟替代 new Date(),sandbox 模式下"当月预估"按 sandbox_date 判断
|
||||
const { currentYear, currentMonth, clockYear, clockMonth, clockDay } = this.data
|
||||
const isCurrentMonth = currentYear === clockYear
|
||||
&& currentMonth === clockMonth
|
||||
&& clockDay <= 5
|
||||
|
||||
try {
|
||||
const res = await fetchPerformanceRecords({
|
||||
@@ -253,11 +268,10 @@ Page({
|
||||
if (currentMonth > 12) { currentMonth = 1; currentYear++ }
|
||||
}
|
||||
|
||||
const now = new Date()
|
||||
const nowYear = now.getFullYear()
|
||||
const nowMonth = now.getMonth() + 1
|
||||
const canGoNext = currentYear < nowYear || (currentYear === nowYear && currentMonth < nowMonth)
|
||||
const isCurrentMonth = currentYear === nowYear && currentMonth === nowMonth && now.getDate() <= 5
|
||||
// CHANGE 2026-05-05 | 业务时钟替代 new Date(),sandbox 模式下"未来月"按 sandbox_date 判断
|
||||
const { clockYear, clockMonth, clockDay } = this.data
|
||||
const canGoNext = currentYear < clockYear || (currentYear === clockYear && currentMonth < clockMonth)
|
||||
const isCurrentMonth = currentYear === clockYear && currentMonth === clockMonth && clockDay <= 5
|
||||
|
||||
this.setData({
|
||||
currentYear,
|
||||
|
||||
Reference in New Issue
Block a user