配货单(手机端)前端实现清单

配货单(手机端)前端实现清单

写代码时的备忘。本地文档、不进 git。 配货单 = 销售单子类型(后端 sub_type=1104);前端体验与销售单一致,差异全由后端兜。 分支:在 feature/26.1.5 上做(已含配货入库代码,和配货单一起上线、一个 MR)。 复用 vs 专用 决策依据:大且无分歧 → 复用(编辑器 4514 行、配货单行为相同);小且有分歧 → 写专用(详情 cell,配货单不需要活动逻辑)。


一、后端契约(已读代码确认,bizsspd feature/chain)

事项 结论 代码出处
单据类型判定 前端不传类型;后端按所选客户 relShopId 自动判定:relShopId>0(铺货客户)→ franShopId→subType=1104;否则普通单。覆盖前端传值 SalesBillCombService.initAgentBill / resolveFranShopIdByCust(D15)
提交接口 配货单走销售单同一个 saveSaleBill(非订单分支),后端 save 时自动转 SaleEditorStore.save();后端 initAgentBill 在 save 流程内
列表过滤 subType=1104 只返/只汇总配货单;不传默认 1112 SspdSalesBillAction.findBillWithPayWay:280 putIfAbsent SALES_BILL_SSHOP
铺货客户标记 customer.typeId === 101(=FRAN_AGENT_TYPE_ID)。必修11 同步时按 rel_shop_id>0 CASE 改写下发,库里不动;需全量同步(清缓存)才有,上线会跑全量 必修11 / D28 commit 904f3754a7
券/活动/积分 后端隔离,前端零分支:积分 produceMoney=0、券/活动 agent 过滤 BaseSalesBillScoreAdapter:352-365GrantCouponService / SalesBillApplyService agent 守卫
详情字段 dresBranchSpu 无条件填 {special}(tenantSpu=null 才缺);actId 返回但空;discount 正常返回 getBillFull / detailFillDresBranchSpu

二、看单(查看配货单)

# 文件 改动 性质
1 src/model/bill/BillType.js(BillSubType ~73-116) AGENT:'agent';常量 SUB_TYPE_AGENT=1104FRAN_AGENT_TYPE_ID=101 🟢新增
2 src/module/bill/history/store/BillHistoryCoreStore.js(titleItems SALE 分支 ~180) push 配货单 tab,gate:购买笑铺后台 && PermissionSvc.hasPermission(Features.agentBill)(写法仿 SALE_ORDER/SALE_DETAILS) 🟢新增
3 src/module/bill/history/store/SaleBillHistoryStore.js(loadBill jsonParam :80-84) ...(billSubType===AGENT ? { subType: SUB_TYPE_AGENT } : {}) 🟢复用接口+条件
4 src/module/bill/history/component/BillHistoryScreen.js(render ~650) key===AGENT 分支 → <BillHistoryFragment billSubType={AGENT} .../>(props 仿 PURCHASE) ♻️复用
5 筛选面板 配货单 tab 隐藏"单据类型"筛选项(实现时定位筛选组件) 🟢条件隐藏
6 详情外壳复用 SaleBillDetailsV2ScreenrenderItem(:1506) 按 subType 分发 cell SaleBillDetailsV2Screen.tsx ♻️复用+分支
6a 新建配货单 cell DistributionSKUCell/SPUCell saleBillDetails/components/ 新增 🟢新增
6b 销售单 cell dresBranchSpu?.special(潜在 NPE 修复,独立有效) SaleBillDetailsSKUCell.tsx:140 / SPUCell.tsx:138 已做
搜索框、查询条件、详情外壳 完全复用销售单,零改 ♻️

详情 = cell 专用(不复用)+ 外壳复用(owner 指导 + 思考题13 原则:分支加在 renderItem 外壳层、cell 单一职责)。

  • renderItem 分支:isAgent = store.billItem?.main?.subType === SUB_TYPE_AGENTisAgent ? <Distribution{mode}Cell/> : <SaleBillDetails{mode}Cell/>(mode=SKU/SPU 维度不变,只在外层加 isAgent 分支)。
  • 配货单 cell = 干净精简新写,不是复制销售单 cell。 只渲染:商品图、款名、款号、颜色尺码、数量×单价×折扣%=小计(+ 点击看详情弹窗)。显示折扣%(配货单有折扣)。不含活动角标(BadgeView)、不耦合 rootStore。预计 ~150 行/个。
  • 为什么专用而非复用/复制:销售单 cell 质量中下(352/510 行塞了 Modal+Badge+Store+styles、强耦合 rootStore、活动 8 分支 if-chain),配货单用不到那套 → 写干净专用的,比复用或复制都好。
  • ?.(6b,已做)保留:纯修销售单自身潜在 NPE;配货单走专用 cell 与它无关,对销售单零影响。
  • 健壮性:专用 cell 物理上无活动角标,不赌后端 actId 返空;13 个详情调用方 subType≠1104 → 走老 cell,零影响

三、录单(创建配货单)= 销售单编辑器 + 仅 2 处不同

# 文件 改动 性质
7 BillHistoryScreen.js("+" :359-396) key===AGENT 分支 → navigate('SaleBillEditor', { billMode:'agent' })(权限同 gate) 🟢新增分支
8 saleBillV2/hooks/useSaleBillStarter.ts + SaleEditorStore route.params.billModestore.billMode(新增字段,默认 'sale') 🟢新增字段
9 saleBillV2/views/CustHeader.tsx(:85 navigate CustomerSelection) custScene: store.billMode==='agent' ? 'agent' : 'normal' 🟢加参数
10 选客户取数链:CustomerSelectionScreen.js:254CustomerSvc.fetchCustomerItemsCustomerDao.getCustomerItems 透传 custScene,查询层 .andWhere:agent→typeId=101,normal→typeId!=101,未传→不过滤 🟢可选参数

编辑器其余(商品/数量/价格/收银/储值/提交)100% 复用销售单,零分支。提交走 saveSaleBill,后端自动转 1104。

⚠️ 编辑器质量警告 + 复用纪律SaleEditorStore.js4514 行 god objectisOrder 散落 23 处rootStore 耦合 100 处、39 @computed)。配货单行为和销售单完全一样(差异全后端兜)→ 重写不现实,必须复用。但触碰降到最小:

  • billMode 字段只用在 CustHeader 一处(决定选客户过滤),绝不进那 23 个 isOrder 分支——别让它变成第二个 isOrder;
  • 对 god object 的改动只有:+1 个 billMode @observable(加法)、useSaleBillStarter 读一次、CustHeader 读一次。不碰任何现有业务逻辑。

四、双向选客户过滤(核心,详)

控制流:入口 billMode → CustHeader 传 custScene → 查询层按 typeId 过滤。

销售单(含新增/编辑)custScene='normal' → WHERE customer.typeId != 101  // 排除铺货,防误转配货单
配货单           custScene='agent'  → WHERE customer.typeId == 101  // 只铺货客户
其它调用方(全局搜索/AutoComplete)  不传 → 不过滤,行为不变
  • 为什么放查询层不放客户端:配货单只要铺货客户(少),客户端过滤分页会很稀疏/翻很多页;查询层 WHERE 直接返铺货客户,分页正确高效。
  • 为什么安全:可选参数、默认不过滤 → 老调用方(GlobalSearch / AutoComplete)不传就零影响。≠ 无条件改公共函数。
  • 注意:销售单排除铺货客户是对现有行为的有意改动(必要,防 D15 静默转配货单)。
  • 待确认:客户实体属性名是否 typeId(mapper 读 item.typeId,应是它)。

五、显示条件 & 待后端/产品的 key(⏳ 阻塞 tab 显示,不阻塞开发)

状态 处理
配货单 tab = 购买笑铺后台 && 配货单菜单权限 已定
Features.agentBill 权限码 后端还没加菜单权限,等它给码 先占位 + TODO;本地自测临时放开权限判断(否则 perm=false,tab 不显示、没法测)
"购买笑铺后台"确切判定 key ⏳ 待确认(候选:commonStore.isSaas / 版本 / businessParam) 参照 CommonStore.js:1483 productCode / isSaas

六、开发顺序 & 影响范围

  • 重要厘清(V1/V2 不是新旧之争,是竖屏/横屏)
    • 竖屏 V1 = editor/component/screen/SaleBillEditor.js(手机所有"+"开单入口 navigate('SaleBillEditor') 都进它,共 15 处)。
    • 横屏 V2 = pages/saleBillV2/(从 landscapeRoute.tsx 进、SideTab 侧栏,平板/横屏开单)。
    • 配货单对标销售单:两屏都要支持。共享同一 store(billMode 字段两屏通用)+ 共享选客户查询层(CustomerDao)。
  • 进度
    • 看单 1-6 + i18n + 6b ?. ✅ 完成
    • 录单竖屏 V1 ✅ 完成:入口"+"(billMode='agent')、SaleBillEditor.js 设 billMode、CustomerDao/CustomerSvc/CustomerSelectionScreen 加 custScene 查询层过滤(双向)、SaleBillEditor 选客户传 custScene。typecheck 零报错。
    • 录单横屏 V2 ⏳:useSaleBillStarter 设 billMode + CustHeader 传 custScene(易,镜像 V1);横屏的配货单入口待查(横屏是 SideTab 常驻,不走 navigate 传参)。
  • 自测:临时放开权限 gate;配货单选客户前需在 app 内"系统设置→清缓存"让 typeId=101 同步下来。
  • 影响范围:改的老代码全是加法/带条件(不进配货单 tab、不传 billMode/custScene 时老流程不变);唯一有意改动既有行为 = 销售单选客户排除铺货客户。

七、实测 QA 发现(竖屏 V1,2026-06-03)—— 复用 SALE 的收尾

核心已验证通过:配货单 Tab/列表/选客户过滤/建单/成功页 端到端跑通(#438/439/440 建出)。 以下是「复用销售单基础设施」带出的收尾,分两类。

A 类:写死 === BillSubType.SALE 把 AGENT 挡了(配货单要跟着 SALE 走的地方加 AGENT)

  • ✅ 列表点详情 BillHistoryFragment.js:474(已加 AGENT)
  • ✅ 建单页 title SaleBillEditor.js:1523(agent→配货单)
  • ⏳ #5 建单后列表不刷新(需下拉)—— 找建单成功→reload 路径的 SALE 门
  • ⏳ 详情内操作(作废/退款/修改后刷新)SaleBillDetailsV2Screen/Store 里的 reloadBillWithBillSubType(SALE)
  • ⏳ 逐个过 BillHistoryStore.js:246/298/640/707 等 ~25 处 SALE 判断(有些是查询过滤/业绩过滤,按需加 AGENT)

B 类:配货单该隐藏的零售 UI(编辑器 + 收款页,按 billMode==='agent' 隐藏)

隐藏:积分(客户积分显示 + 积分抵扣 + 产生积分)、优惠券/券(赠送优惠券 + 用券)、营销活动。 保留(铺货客户适用):余额(显示)、余额结算(用余额付款)、充值、现金/银行账户、抹零、其他费用、折扣%。

待确认/已确认

  • ✅ 余额、余额结算、充值 → 保留(铺货客户有余额、可余额结算)
  • ✅ 积分、优惠券、活动 → 隐藏