依据 2026-10-22 的 V2.3/V2.4 变更记录实施;随附文档虽然标为 V2.2,正文中旧版的六卡、库位地图和五类动态说明不再适用。
categoryLevelId(分类层级 id)打开台账并预选左侧树节点,口径见 D2。weekOverWeekQuantity/weekOverWeekUnit/comparisonBaseAt),仅单一基本单位且两侧快照齐全时才有值;缺基准、跨单位或后端未上线该字段时显示 comparisonReason(缺省“暂无上周同时点可比快照”),差值不得以 0 代替。units 缺省或只有 1 个单位时不渲染小计行。lockedQuantity 为 0 是真实零按“0”显示,为 null 表示口径缺失显示“—”。batchRows/costRows 不得改动,理由写在 A1。当前只做 Vue 模板/脚本、MyBatis XML 与差异静态校验;实际数据对账和浏览器布局仍需在有授权数据的环境核验。
/wms/index/home-summary 的 trend 只返回有作业的日期,缺的日期前端补 null、绝不用 0 冒充。markArea 底纹,hover 提示「该日无作业」,净趋势在该日断线。#9B7DF7,绑右侧第二 Y 轴「单日净差」,与左侧数量轴分开,读数不混用刻度。>= 2 天时提示——连续为正=积压风险(橙)、连续为负=缺货风险(红);0(持平)与中间的无作业日都打断连续段,没有连续段不提示。startTime=00:00:00/endTime=23:59:59 打开 V4 出入库动态;无作业日列表为空态,但日期照带。/warehouseManagement/inboundRequests(出入库申请,独立页签),不是首页内嵌的 ioapp 视图、也不是自建的 V4 动态列表:HomeOverview.vue 发 $emit('navigate', '/warehouseManagement/inboundRequests', { warehouseId }),由 dashboard.vue 的 @navigate="navigate" 走 $router.push。只带当前仓库,状态/时间/页签全部交给目标页自己的默认值(search.vue 的 defaultWhere.status='' 即全部状态,默认入库页签);不得带 dashboard=1,否则会误开看板的批量收货勾选列。V4 仍由「最近作业动态 › 全部动态」、趋势柱体与无作业日、以及调拨行的「查看单据」进入,入口不丢。dashboard-utils.js:TREND_WINDOW_DAYS、trendWindowDates、trendQuantity、isIdleTrendDay、trendNetOf、buildTrendWindow、trendStreak、trendRisk,由 scripts/check-warehouse-dashboard.cjs 静态断言守护。categories:{ id, name, quantity, ratio }。id 是物料分类根层级 t_main_category_level.id(字符串),可直接作为 V2 台账的 categoryLevelId;「其他」聚合行的 id 固定为字符串 'other',不是任何真实分类层级。HomeOverview.categorySelect(row) 发 open('stockLedger', { categoryLevelId: row.id }),dashboard.vue 的 open() 把 extra 经 cleanQuery 合并进路由 query(path 仍是 /warehouseManagement/stockLedger),台账页据此选中左侧树并过滤右侧列表。query.categoryLevelId:树加载完成后在 treeList 中递归查找 id 匹配的节点,$nextTick 后 treeRef.setCurrentKey(id) 并展开祖先;current 置为 { id, data } 的形状,同时满足 item-list 的 current.id(首屏 datasource)与 current.data?.id(reload)两种读取。[wms-stock-ledger] 说明日志;台账页日志前缀不得复用首页的 [wms-home]。props.unit,为空时不加单位后缀)/ 占比%,并提示「点击进入台账」;占比或数量缺失显示「—」,不以 0 冒充。scripts/check-warehouse-dashboard.cjs 覆盖「非 other 行带 categoryLevelId」「other 行不带」「open('stockLedger', extra) 把 query 传给 router」「台账页含 categoryLevelId 处理与递归查找」「tooltip 单位取 props.unit」。本节固化安全库存预警改造后的唯一口径。后端实现在 WmsHomeMapper.xml 的 alertRows 片段;前端只做归一、配色兜底与文案,不复算任何阈值。回归守卫见 scripts/check-warehouse-dashboard.cjs 的 F1 断言块。
alertRows 对每个「(仓库,物料)」行按下列顺序判定,命中即停:
unknown(待核实):没有唯一的有效阈值配置(缺精确仓库配置、同仓同物料存在多条、阈值或基本单位不可核实),或可用量为负(锁定量超过余额)。critical(缺料):可用量 < 安全库存 secure_inventory。warning(偏低):安全库存 ≤ 可用量 < 安全库存 × K1。excess(超储):可用量 > COALESCE(最大库存, 安全库存 × K2)。normal(正常):其余全部。wms.home.alert.warning-ratio(默认 1.2)、K2=超储兜底系数 wms.home.alert.excess-ratio(默认 3)。两者由 WmsHomeAlertRatioConfig 解析后以 Mapper 参数 #{warningRatio}/#{excessRatio} 下发,非法值回落默认并打 WARN。前端不得内置 1.2/3——写死会让改系数后前后端静默分叉。min_inventory 只作展示列,不参与任何触发判定。用最低库存判缺料是本次废弃的旧口径,不得回退。COALESCE 的第二项不会被用到;把 K2 当成「最大库存之外再加一层」是错的。alertRows 内一个独立的 av 子查询里算:SUM(stock_count_base) − SUM(COALESCE(lock_count_base,0)),按(仓库,物料)聚合,批次过滤条件与预警行集一致。含待检、未放行批次,不套 pre_warehousing/quality_control 过滤。batchRows 投影的 availableQuantity,定义是 available_count_base − COALESCE(lock_count_base,0),会把未放行/待检批次减掉。batchRows 与 costRows 片段不得改动。 batchRows 的 availableQuantity 被库存总量、分类分布、库龄、周转等四个既有消费者同时读取,costRows 是库存金额(C2)的单价定义;为了预警去改这两处,会静默改掉四个 KPI 的数字且没有任何报错。预警需要不同口径时只能新增独立子查询,不能复用或改写共享片段。costUnitPriceRows(从 costRows 抽出)与库存金额共享同一定义,缺价时保持 NULL,不兜底成 0。WmsHomeMapper.xml,校验 batchRows 的 available_count_base-COALESCE(b.lock_count_base,0) 与 alertRows 的 SUM(...)−SUM(...) 同时存在,且 alertRows 内不得出现 available_count_base(出现即意味着预警复用了排除未放行的口径)。gapQuantity = GREATEST(安全库存 − 可用量, 0),只在 critical/warning 行有意义,其余状态为 0。gapAmount = gapQuantity × 移动加权不含税单价(单价取 costUnitPriceRows,与库存金额同源)。gapAmount 为 NULL 并下发 gapAmountReason,前端显示「金额待补价」且打一条去重日志,绝不用 0 冒充——0 会被读成「缺口不值钱」,直接误导补货优先级。真实 0 照实显示 0.00 元;底层格式化层对缺失给「—」、对 0 给 0.00。alertGapText 生成:缺口 <数量><单位> / <金额>,缺价/缺口量缺失分别落到「金额待补价」与「—」。ORDER BY gapAmount IS NULL, gapAmount DESC, FIELD(status,'critical','warning','excess','normal','unknown'), id:缺口金额倒序、缺价行(NULL)排最后、再按状态严重度、最后 id 稳定收尾(分页时同额同状态的行不会跳位)。显式写 gapAmount IS NULL 是为了让排序意图不依赖 MySQL DESC 的隐式 NULL 规则。ALERT_TOP_LIMIT = 5 条;超出部分由 buildAlertRows 返回 overflow 并在控制台 warn 一次,total 仍是后端原始条数,右上角「全部 N 项」取 alertCount。dashboard-utils 的比较器只用于核对后端顺序、偏离时 warn,不改变渲染顺序——清单是分页的,页内重排会让「第 2 页第一条」错位。t_mes_work_order;未完工定义固定为 deleted=0 AND status IN (4 待生产, 5 生产中, 7 已延期),已完工(6)/待下达(8)/暂停(9)/终止(10) 都不算影响生产,不得放宽。alertWorkOrderCount=全部缺料(critical)行关联工单跨行去重后的计数(同一张工单被多个缺料物料影响只算一张);算不出时为 null + alertWorkOrderCountReason,前端显示「—」并给原因,绝不当 0。workOrderCount 用 CASE WHEN status='critical' THEN ... END:非缺料行为 NULL 而不是 0(0 会被读成「确定没有工单受影响」)。level=critical 打开缺料清单,保证数字与清单同源。purchaseRequestCode/purchaseRequestId 来自 t_wms_dashboard_command(command_type='PURCHASE',command_key = warehouseId:categoryId),business_id 用 CAST AS CHAR——大整数不得进 JS number。detail.vue 的 alertActionOf 优先级:unknown→「核对阈值」(不给补货入口,阈值不成立时补货只会被后端拒绝);excess→「发起调拨」;有 purchaseRequestCode→「打开采购需求」,绝不出现「生成采购申请」;critical/warning 且无单号→「生成采购申请」;normal→不给动作。DashboardActions.vue 的 purchaseLocked 三重拦截:数量输入禁用、提交按钮禁用并改文案「已生成申请,禁止重复提交」、submit 内再拦一次并打 [wms-home] warn;后端 hasReplenishmentAlert 与新缺料口径同源,是最后一道。gapAmount 降序、缺价行最后,再按 gapQuantity 降序),目标库区=该仓第一个启用库区,并显式标注「默认库区,请核对」。positiveQuantity(row.gapQuantity):非正数留空,不得用安全库存或最大库存冒充缺口量。maxInventory: null;后端 insert/update 均 jdbcType=DECIMAL 无条件写,留空写 NULL(为空即走 ×K2 兜底)。alertCount 只含 critical + warning + excess;alertUnknownCount 单列且不计入 alertCount(卡片显示「待核实 X 项(不计入 N 项)」)。alertGapAmount = Σ gapAmount;全缺价时后端给 null + alertGapAmountReason,前端显示「—」。t_main_category_wms.max_inventory 必须允许 NULL。本口径依赖「最大库存为空 → 按安全库存 × K2 兜底」;列若是 NOT NULL,留空保存会直接失败,超储兜底这条分支永远触发不到。本会话无数据库连接,该列的 NULL 属性未能在本环境验证,上线前必须在目标库核对:
SHOW COLUMNS FROM t_main_category_wms LIKE 'max_inventory';
判读:Null 必须是 YES。若为 NO,先做列可空迁移——用 SHOW CREATE TABLE t_main_category_wms 取现有精度与注释后,再执行形如 ALTER TABLE t_main_category_wms MODIFY COLUMN max_inventory DECIMAL(24,4) NULL COMMENT '最高库存,为空表示未配置(超储判定按安全库存×K2 兜底)'; 的变更,否则「留空保存」整条不可用。
顺带核对:secure_inventory/min_inventory 的存在与可空性(阈值缺失会落到 unknown);t_wms_dashboard_command 上 (tenant_id, command_type, command_key) 是否有可用索引(防重复下单的查询走这条路)。
F1 断言块覆盖:statusLabel/color 优先级与三色兜底(normal 不造色)、缺价「金额待补价」而 0→「0.00 元」、TOP5 截断与 overflow、状态不复算、汇总条全 null→「—」且不自算合计、alertActionOf 五分支(含「有单号绝不出现生成采购申请」「unknown 不给补货入口」)、卡片模板守卫(三色点、影响生产工单、已生成申请、金额待补价)、screens.alerts 文案守卫(含待检/偏低系数/超储兜底,且不得再出现「排除未放行」)、后端 SQL 静态守卫(critical 判定行不得引用 minInventory、必须用 secureInventory、K1/K2 走参数、gapAmount DESC、batchRows 与 av 子查询两条护栏)。后端仓库不在同级目录时该组打印 SKIP 并说明原因,不判失败。
本节固化首页「最近作业动态」卡片的唯一口径。数据由 /wms/index/home-summary 的 activities 下发,取数在后端 WmsHomeMapper.xml 的 recentActivities(WmsHomeMapper.xml:226-248),前端归一在 dashboard-utils.js 的 E3 段(dashboard-utils.js:597-925),模板在 HomeOverview.vue 的 feed 块(HomeOverview.vue:118-157)。
activityType=io,按 direction 1/2 区分,dashboard-utils.js:767-773)、调拨单(activityType=transfer)、盘点任务(activityType=inventory,取自 t_wms_plan_order,WmsHomeMapper.xml:240-246)。入/出/调拨行来自 ioRows 共享片段(WmsHomeMapper.xml:177-202)。后端没给的类型不许在前端补一条。WmsHomeMapper.xml:235-236:出库方向 biz_type=2 或入库方向 biz_type=15),前端只认后端给的 activityType,不得自己按 businessType 再猜一遍——两套判据迟早分叉。WmsHomeMapper.xml:246),入/出/调拨沿用 ioRows 的 30 天滚动窗(WmsHomeMapper.xml:196-197),刷新时由服务层清空 startTime/endTime/unit 免得被首页筛选改写(WmsIndexServiceImpl.java:1547-1550)。ioRows 把「已完成」硬编码成主单审核通过(AND o.verify_status=2,WmsHomeMapper.xml:190)。该片段同时被 D1 趋势(WmsHomeMapper.xml:221)、V4 明细 io 页签(WmsHomeMapper.xml:817)、E3(WmsHomeMapper.xml:237)共用,是「已完成」的唯一定义点(WmsHomeMapper.xml:166-171)。因此入/出/调拨行恒为「已完成/绿」(dashboard-utils.js:748-749),盘点只有 status 0/1/2 → 待执行/进行中/已完成(dashboard-utils.js:740-744)。dashboard-utils.js:611-618)与兜底分支(dashboard-utils.js:751-756),而兜底只在后端新增了未识别 activityType 且同时下发中文状态词时才生效。今天唯一可能被标超时的行是盘点 status=0「待执行」(dashboard-utils.js:829-830)。dashboard-utils.js:608-619、621-626),后端一旦扩展取数即可直接生效;但不得为了凑出橙色/红色在前端伪造状态——伪造的「待审批」会让用户去催一个不存在的审批,比不显示更糟。ioRows。 动共享片段等于静默改掉 D1 的柱子和 V4 明细的数字,且一条报错都不会有;同理调拨剔除只能留在 trend 语句(WmsHomeMapper.xml:212-213),不能下沉进片段。formatActivityTime,dashboard-utils.js:711-720):今日 今日 HH:mm(:717)、昨日 昨日(:718)、更早 MM-DD(:719);时间非法或缺失一律 —(:713)并排到最后(:872),绝不用「现在」兜底——那会把一条陈年流水顶到列表最前面。hover 给完整时间(activityTimeTitle,:724-730)。activityDocTypeOf,dashboard-utils.js:762-775;模板 HomeOverview.vue:142-151):类型名固定「入库单/出库单/调拨单/盘点任务」,未识别回落「作业单」而不是猜成四类之一(猜错会让用户点进错误的单据类型);单号缺失显示「单号待核实」(HomeOverview.vue:150)。单号段单独可点,用 @click.prevent.stop(HomeOverview.vue:148)——少了 .stop 会冒泡到整行的 @click(HomeOverview.vue:138),一次点击触发两次下钻。activityActionTextOf,dashboard-utils.js:785-815):按类型套模板,见下一节。activityStatusOf,dashboard-utils.js:737-757):渲染在 HomeOverview.vue:153,超时角标同格追加。itemCount 写成占位常量 1(WmsHomeMapper.xml:241,该行第 4 个投影值),它既不是盘点明细行数也不是盘点数量;当数量渲染会得到「盘点 1 项」这种伪造数字。前端对 inventory 直接返回 null(dashboard-utils.js:691),盘点只拼「盘点 · 库区(· 进度 xx%)」(:798-814)。盘点行的 description 是状态词(服务层按 status 拼「已完成/执行中/待处理」,WmsIndexServiceImpl.java:1576-1578),属于第四段,绝不能当动作文案(dashboard-utils.js:793-796),否则一行会读成「单号 → 已完成」。activityItemCount 只认有限且 >0 的数(dashboard-utils.js:688-694),activityProgressOf 对 0、负数、非数字一律返回空串(:699-707)。0 会被读成「这一单没有任何明细」「一点都没做」,与「不知道」是两件事;与 C2 缺价、E1 unknown 同一条铁律——不可核实就省略,绝不用 0 冒充。dashboard-theme.scss:155-159,基础色见 :142-144)。文案与颜色解耦:颜色只认 ACTIVITY_STATUS_STYLES(dashboard-utils.js:608-619),改配色不用改文案,模板里也不许判业务状态。dashboard-utils.js:625-626)。为什么不能用 done 或 waiting:done 是绿,会被直接读成「已完成」这个业务结论;waiting 是橙,会被读成「在等谁处理」,还会被套上超时角标(:829-830),等于替后端凭空造出一个待办。灰只表达「这里有个状态,前端不认识」,与 E1 库龄 unknown、F1 预警「待核实」是同一种视觉语言;样式见 dashboard-theme.scss:159。status 0/1/2 → 待执行/进行中/已完成(dashboard-utils.js:740-744);调拨恒「已执行」done(:748);出入库恒「已完成」done(:749)。activityType 分流再判 status。 io/transfer 的 status 恒为 NULL(WmsHomeMapper.xml:234),而 Number(null) === 0,先判 status 会把「已完成」显示成「待执行」(dashboard-utils.js:735-736)。未识别的类型或状态值只 warn 一次并回落中性灰(:745-746、:750、:756),不得静默当成 done。tone === 'waiting')且事件时间早于 now−24h(buildActivityItem,dashboard-utils.js:827-830);阈值 ACTIVITY_OVERDUE_HOURS = 24(:603)。DATE_ADD(a.create_time, INTERVAL 24 HOUR) < NOW()(WmsHomeMapper.xml:509,入总库申请分支同式 :532;盘点行固定不标,:558)。不许在 E3 另立 12h/48h——同一屏上的两个「超时」不能是两个定义。dashboard-utils.js:827):绿/青/红套「超时」没有业务含义,别给已完成的单据戴超时帽子。operateTime 与 now 现算(:828-830),重复求值不会因为「已经标过一次」而漂移;不得做成「标一次就记住」的有状态标记。orderId,缺失回落 id,两者都没有用行下标兜底)只留事件时间最新一条(buildActivityFeed,dashboard-utils.js:853-867);同刻不覆盖(:865-866)。后端已做 GROUP BY orderId(WmsHomeMapper.xml:238),但前端不能假设这个口径永远不变。dashboard-utils.js:868-879),因此顺序确定、可复现——稳定排序才能让刷新后行不跳位。解析不出时间的行排最后(:872)。ACTIVITY_LIMIT = 5,dashboard-utils.js:601;前端截断 :880;后端 LIMIT #{q.limit} 取同一值,WmsHomeMapper.xml:247 与 WmsIndexServiceImpl.java:1546)。双保险的理由:前端还要按 orderId 再去重一次,去重后条数可能变少;后端口径若调整也可能变多,只靠后端 LIMIT 保不住「卡片正好 5 条」这个视觉契约(:598-600)。多出来的条数进 overflow(:881),不静默丢弃。HomeOverview.vue:128 发 open('io'),dashboard.vue:84-101 落到 io 屏(dashboard-screens.js:8)。整行与单号点击走 openActivity(HomeOverview.vue:701-705)→ dashboard.vue:214-216 按 activityType 分流:盘点→盘点工单详情、调拨→V4 带单号、io→入/出库单据详情;缺 orderId 时提示核实,不许硬跳到列表首页。dashboard.vue:170 的 setInterval(() => this.refreshPart('activities'), 30*1000),只刷 activities 这一片,不整页重算(:164-172)。is-new 类做 3 秒渐隐(HomeOverview.vue:135 + dashboard-theme.scss:162-163),JS 只负责到点摘类(HomeOverview.vue:679-682)。不移动 DOM、不调用任何滚动 API:30 秒轮询里主动 scrollIntoView/scrollTop 会把用户正在看的位置顶走(HomeOverview.vue:668-669、dashboard-theme.scss:160-161)。diffActivityIds 在 prev 为空(首次加载)时返回空数组(dashboard-utils.js:919),HomeOverview.vue:670-673 因此天然不触发——整屏闪一下等于什么都没强调。dashboard-utils.js:920-924):activities 是计算属性,每次求值都返回新数组,引用比较会让 30 秒轮询把同一批行全判成新增、整屏反复闪。HomeOverview.vue:675 先 clearTimeout 再起新的 3 秒,只保留最后一轮,避免先到的一批提前被摘掉;组件销毁时同一处清理(:685-688)。HomeOverview.vue:156),禁止空白、禁止只画一个空列表;有 activityReason 时追加「 · 原因」(同处),原因由数据侧给,前端不自造。activityReason(WmsHomeMapper.xml:226-248 无此列,服务层也未拼),该字段实际只在刷新失败时被写入错误信息(dashboard.vue:158、:161)。所以今天的空态基本就是这一句话,不许为它编造「网络异常」之类的原因。[wms-home] E3 最近作业动态已解析(dashboard-utils.js:907):模块级快照去重(:642、:902-906),只在「总条数/去重数/四类分布/待*数/超时数」任一变化时打一条;每次都打会把控制台刷满、反而看不见真实变化。[wms-home] E3 最近作业动态状态未识别(:636):按「类别 + 值」去重,同一句 warn 只打一次(:631-637),是发现后端口径漂移的第一信号。[wms-home] E3 最近作业动态下钻(HomeOverview.vue:703):只记 id / activityType / orderNo / direction。[wms-home] E3 最近作业动态新增事件(HomeOverview.vue:677):只记新增 id 与条数。dashboard-utils.js:641):日志只放聚合数字与关键字段,不 console.log(row)、不打印 summary;长列表会淹掉控制台,单号也是敏感数据。日志前缀必须是 [wms-home],其它页面不得复用。E3 的关键口径由 scripts/check-warehouse-dashboard.cjs 的 E3 断言块守护,写法与同文件里 D1、E1、F1 各块一致(check-warehouse-dashboard.cjs:554、:804、结尾 F1-(5) 段)。被守护的关键点:
activityType 分流再判 status,Number(null)===0 的错位必须锁死;overflow;diffActivityIds 首屏返回空、按 id 比较不按引用;@click.prevent.stop、is-new 类、空态文案;ioRows 的 verify_status=2 唯一门槛、盘点分支 itemCount 常量、LIMIT 与 24h 口径。不写死断言条数:断言块会随口径增补继续加,条数写进文档立刻过期,以脚本实际执行为准。
.feed-doc 是 inline-flex + text-overflow:ellipsis(dashboard-theme.scss:148),而 ellipsis 作用于块容器的行内内容,对 flex 容器里的裸文本节点({{ item.docType }},HomeOverview.vue:143)未必生效;超长单号(.feed-no,dashboard-theme.scss:149-150)在 overflow:hidden 下可能被硬切而没有省略号。当前只有静态断言,真实宽度下的表现必须在浏览器里看一眼。summary.activities 数据未对账:本环境无数据库连接,四类分布、itemCount、status 的实际取值只能靠联调确认,上述推断全部来自 Mapper 与前端代码。activityActionTextOf(dashboard-utils.js:785-814)在 activityType=io 且 direction 既非 1 也非 2 时把动作词兜底成「入库」,而 activityDocTypeOf(:762-774)对同一情况兜底成「出入库单」并 warn 一次。ioRows 的 direction 取自 t_wms_snapshot.out_type,正常数据非空,只有历史脏值可能命中;命中时同一行会读成「出入库单 RK… · 入库 N 项」。若以后要覆盖,应让两处共用一个方向判定助手,不要各自兜底。progressText 生产环境恒为空:后端 recentActivities 不下发进度字段(WmsHomeMapper.xml:226-248),前端是防御性读取(dashboard-utils.js:696-707)。因此「进度 xx%」今天不会出现,只有后端补了进度列才生效——不得为了让它显示而在前端算一个进度。