升鲜宝客户信用额度完善解决方案---升鲜宝生鲜配送供应链管理系统源代码服务(客户订货端、分拣端、仓库PDA、供应商协同端、采购端、配送端、溯源端)

客户信用额度完善解决方案

适用系统:升鲜宝 SCM 后端 scm--admin 与前端 scm-admin-ui 源代码

|--------|---------------------------------------------------|
| 项目 | 说明 |
| 文档版本 | V1.0 |
| 文档状态 | 开发评审稿 / 可作为研发落地基线 |
| 编制日期 | 2021-07-04 |
| 覆盖范围 | 客户授信总额、冻结授信、可用授信、逾期状态、订单创建/修改/审核/作废/对账转应收的授信占用与释放 |
| 核心目标 | 统一授信计算口径,保证订单生命周期中的冻结、释放、转应收和逾期拦截准确、可审计、可并发 |

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 核心结论 客户信用额度不是 cus_customer.credit_amount 一个字段可以完成的功能,而是由授信总额、客户欠款、未转应收订单冻结占用、逾期应收与风控策略共同决定。 订单未转应收前占用 frozenCreditAmount;转应收后释放冻结占用,并由 cus_customer_debt.debt_amount 承接风险敞口。 所有新增、修改、审核、反审核、作废、转应收场景必须以服务端授信控制为准,前端预校验只提升体验,不能替代后端校验。 |

目录

    1. 文档目标与边界
    1. 总体方案与核心原则
    1. 业务口径与计算公式
    1. 关键数据模型
    1. 服务分层与代码落点
    1. 授信校验规则
    1. 订单生命周期授信流程
    1. 并发、幂等与事务控制
    1. 客户信息页面与接口方案
    1. 额度下调保护
    1. 风控策略与强制提交审计
    1. 数据巡检、修复与监控
    1. 实施计划与优先级
    1. 测试用例
    1. 验收标准
    1. 风险与后续优化

1. 文档目标与边界

本文档用于规范客户信用额度在升鲜宝 SCM 中的设计、开发、联调和验收口径,重点解决"可用授信如何计算、订单如何占用/释放授信、逾期如何拦截、并发下如何防止超额占用、强制提交如何审计"等问题。

|--------|---------------------------------------------------------------------------------------------------------|------------------------------------------------|
| 范围 | 纳入内容 | 不纳入内容 |
| 客户信息 | creditAmount、frozenCreditAmount、availableCreditAmount、overdueState、overdueStateStr 的展示、编辑、导入导出和 DTO 对齐。 | 不在客户页面直接维护 frozenCreditAmount;冻结授信只能由授信控制服务变更。 |
| 订单流程 | 新增、修改、保存、审核、反审核、作废、对账转应收中的授信校验、占用、调整、释放。 | 不替代订单价格、库存、配送、支付等业务校验。 |
| 财务应收 | 订单转应收后释放冻结占用,并通过 cus_customer_debt.debt_amount 承接欠款。 | 不在本文展开收款、核销、坏账处理的完整财务方案。 |
| 风控策略 | 授信不足、逾期应收的跳过策略、强制提交标记和审计。 | 不定义复杂授信评级模型,仅保留策略接入点。 |
| 数据治理 | 冻结金额与占用日志一致性、逾期数据巡检、历史异常修复。 | 不直接批量修改生产数据,需经过候选统计和业务确认。 |

2. 总体方案与核心原则

目标架构采用"授信快照服务 + 授信控制服务 + 订单生命周期集成 + 风控审计"的方式收口。客户详情、客户列表、订单预校验、订单正式提交都不直接散落计算授信,而是复用统一服务。

|-----------|-------------------------------------------------------------|-----------------------------------------------------|
| 原则 | 说明 | 落地要求 |
| 服务端为准 | 前端保存/审核前可以预校验,但最终是否允许保存、审核、占用和释放必须以后端事务内结果为准。 | save、update、audit 等接口内部必须再次调用授信控制服务。 |
| 冻结与欠款分段承接 | 订单未转应收前占用 frozenCreditAmount;转应收后释放冻结占用,同时欠款 debtAmount 增加。 | 风险敞口连续,不因转应收动作凭空恢复可用授信。 |
| 占用按订单幂等 | 同一订单只保留一条有效占用主日志,修改订单只调整差额。 | 使用 source_type + order_code 或 idempotent_key 唯一约束。 |
| 释放可重复 | 反审核、作废、转应收等释放动作可能被重复触发,重复释放不得导致 frozenCreditAmount 为负。 | 只释放 occupy_state=1 的记录,并使用 greatest 保护。 |
| 逾期独立校验 | creditAmount 为空或小于等于 0 时按不启用额度上限处理,但逾期应收仍需拦截。 | 除非命中风控策略放行,否则 overdue=true 时不允许提交。 |
| 强制提交可审计 | 风控跳过授信不足或逾期限制必须可追溯。 | 记录 policyId、policyName、forceSubmitReason、操作人、时间和场景。 |

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 推荐的目标架构 CustomerCreditSnapshotService:负责客户授信快照、冻结订单列表、逾期状态、可用授信展示口径。 CustomerCreditControlService:负责订单授信校验、占用、调整、释放和日志。 订单服务:只编排业务生命周期,不自行实现授信公式。 风控策略服务:只决定是否允许跳过授信不足或逾期限制,不直接改写授信金额。 |

3. 业务口径与计算公式

|-----------------------|----------------------------------------------------|--------------------------------------------------|----------------------------------------|
| 指标 | 数据来源 | 业务含义 | 维护方式 |
| creditAmount | cus_customer.credit_amount | 客户授信总额。为空或 <=0 时,后端按"不启用额度上限"处理。 | 客户新增、编辑、导入维护;下调受保护。 |
| currentDebt | cus_customer_debt.debt_amount | 客户当前欠款,应收生成、收款、核销等财务动作影响。 | 财务应收与收款链路维护。 |
| frozenCreditAmount | cus_customer.frozen_credit_amount | 未转应收订单对授信的冻结占用。 | 只能由 CustomerCreditControlService 原子增减。 |
| availableCreditAmount | creditAmount - currentDebt - frozenCreditAmount | 当前可用授信。用于客户列表、详情和订单提示。 | 由授信快照服务实时计算,不建议落库。 |
| nextOrderAmount | 订单金额;已支付时按 0 | 本次订单将新增的风险敞口。 | 订单保存/审核前动态计算。 |
| exposureAmount | currentDebt + frozenCreditAmount + nextOrderAmount | 校验本次订单后的总风险敞口。 | 授信校验时计算。 |
| overdue | oms_order_bill_debt | 存在 still_debt_amount > 0 且 due_date < 当前时间的应收。 | 应收快照与账期到期日共同决定。 |

3.1 核心公式

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| availableCreditAmount = creditAmount - currentDebt - frozenCreditAmount nextOrderAmount = paymentState == 1 ? 0 : orderAmount exposureAmount = currentDebt + frozenCreditAmount + nextOrderAmount creditEnough = creditAmount == null || creditAmount <= 0 || exposureAmount <= creditAmount allowed = !overdue && creditEnough |

3.2 信用额度为空或小于等于 0 的推荐口径

  • **额度控制:**creditAmount 为空、0 或负数时,不按额度上限拦截订单。
  • **逾期控制:**额度上限不启用不代表放开逾期;逾期应收仍然拦截,除非风控策略允许跳过逾期。
  • **页面展示:**建议 availableCreditAmount 保持后端计算值,同时增加 availableCreditAmountStr。当 creditAmount <= 0 时展示"不限制"或"未启用额度上限",避免用户误解为可用额度为负。
  • **报表口径:**授信总额未启用的客户可单独分组统计,避免与正常授信客户混合计算额度使用率。

3.3 与客户账期/逾期状态的关系

授信控制中的 overdue 来源于 oms_order_bill_debt 的 due_date 与 still_debt_amount。由于 due_date 由客户账期快照生成,客户账期方案必须保证订单应收、期初应收、客户账单生成时写入准确的到期日。授信模块只消费应收快照,不应重新解析客户账期主数据。

4. 关键数据模型

|------------------------------|----------------------------------------------------------------------------------------------------------------------|------------------|----------------------------------------------------------------------|
| 表/对象 | 关键字段 | 作用 | 开发要求 |
| cus_customer | credit_amount、frozen_credit_amount | 保存客户授信总额和冻结授信占用。 | frozen_credit_amount 默认 0;普通客户编辑接口不得直接覆盖冻结金额。 |
| cus_customer_debt | customer_id、debt_amount | 保存客户当前欠款。 | customer_id 建议唯一;应收、收款、核销必须保证欠款同步准确。 |
| oms_order_bill_debt | customer_id、still_debt_amount、due_date、overdue_state、settlement_state | 逾期应收判断基础。 | 建立 customer_id + due_date + still_debt_amount 的组合查询索引。 |
| cus_credit_occupy_log | customer_id、order_code、source_type、idempotent_key、occupy_amount、release_amount、occupy_state、occupy_time、release_time | 订单级授信占用主日志。 | source_type + order_code 或 idempotent_key 必须唯一;释放只处理 occupy_state=1。 |
| cus_credit_occupy_detail_log | occupy_log_id、stage、scene、before_amount、after_amount、delta_amount、payment_state | 授信占用变化明细日志。 | 新增、修改、审核、释放、转应收均记录明细,便于追溯。 |

4.1 索引与约束建议

|------------------------------|--------------------------------------------------------------------|----------------------|
| 对象 | 建议索引/约束 | 目的 |
| cus_credit_occupy_log | UNIQUE(source_type, order_code) | 防止同一来源订单重复生成占用记录。 |
| cus_credit_occupy_log | UNIQUE(idempotent_key) | 统一幂等标识,支持后续多来源扩展。 |
| cus_credit_occupy_log | INDEX(customer_id, occupy_state, source_type) | 客户详情查询冻结占用订单列表。 |
| cus_credit_occupy_detail_log | INDEX(occupy_log_id, create_time) | 追溯单个订单授信变化过程。 |
| cus_customer_debt | UNIQUE(customer_id) | 确保一个客户只有一条当前欠款记录。 |
| oms_order_bill_debt | INDEX(customer_id, due_date, still_debt_amount, del_flag, enabled) | 快速判断客户是否存在逾期未结应收。 |
| cus_customer | CHECK/默认值 frozen_credit_amount >= 0 | 避免异常释放或手工修复导致冻结金额为负。 |

5. 服务分层与代码落点

5.1 现有关键代码位置

|-----------|--------------------------------------------------------------------------|------------------------------|
| 模块 | 代码位置 | 职责 |
| 授信控制 | com.loveinway.modules.cus.service.CustomerCreditControlService | 授信校验、占用、调整、释放的统一接口。 |
| 授信实现 | com.loveinway.modules.cus.service.impl.CustomerCreditControlServiceImpl | 读取授信、欠款、冻结占用和逾期,执行规则判断与日志记录。 |
| 授信 Mapper | mapper/cus/CusCreditOccupyLogDao.xml | 行锁查询、原子增加/减少冻结金额、查询客户有效占用订单。 |
| 客户详情 | com.loveinway.modules.shop.service.impl.MallShopServiceImpl | 客户详情授信快照、信用额度下调保护。 |
| 客户控制器 | com.loveinway.modules.cus.controller.CusMallShopController | 客户端客户详情、冻结占用订单列表接口。 |
| 订单保存 | com.loveinway.modules.oms.service.impl.OmsOrderSubmitServiceImpl | 新增、修改订货单时授信校验和冻结。 |
| 订单审核 | com.loveinway.modules.oms.service.impl.OmsOrderAuditServiceImpl | 审核、反审核、作废订货单时授信处理。 |
| 对账转应收 | OmsOrderReconciliationServiceImpl / CustomerReceivablePostingServiceImpl | 订单转应收后释放冻结授信。 |

5.2 推荐新增/抽取 CustomerCreditSnapshotService

当前 CusMallShopController 和 MallShopServiceImpl 均存在授信快照计算逻辑,建议抽取到统一服务,客户列表、客户详情、订单授信校验共用该服务,减少重复实现和口径差异。

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| public interface CustomerCreditSnapshotService { RespCustomerCreditSnapshotDTO getSnapshot(Long customerId); RespCustomerCreditSnapshotDTO getSnapshot(Long customerId, String excludeOrderCode); List<RespCreditOccupyOrderDTO> listActiveOccupyOrders(Long customerId); boolean hasOverdueReceivable(Long customerId); } |

|-------------------------------------------|-------------------|-------------------------------------------------------------------------------|------------------------------|
| 方法 | 输入 | 输出/行为 | 说明 |
| getSnapshot | customerId | creditAmount、debtAmount、frozenCreditAmount、availableCreditAmount、overdueState | 客户详情、客户列表、订单校验统一使用。 |
| getSnapshot(customerId, excludeOrderCode) | customerId、当前订单编码 | 扣除当前订单已有占用后的授信快照 | 订单编辑/审核场景避免重复计算本订单冻结占用。 |
| listActiveOccupyOrders | customerId | 未释放订单占用列表 | 客户详情冻结占用订单明细。 |
| hasOverdueReceivable | customerId | boolean | 基于 oms_order_bill_debt 判断逾期。 |

6. 授信校验规则

checkOrderAllowed(customerId, orderAmount, excludeOrderCode, paymentState, scene) 只做校验,不改变冻结授信。任何会改变授信占用的动作必须调用 reserveOrderCredit、adjustOrderCredit 或 releaseOrderCredit。

6.1 校验输入

|------------------|-------------|--------------------------------------------------|
| 参数 | 含义 | 处理规则 |
| customerId | 客户 ID | 必填,校验客户存在且未删除。 |
| orderAmount | 订单目标金额 | 小于 0 直接拒绝;为空按 0 或业务默认处理,建议后端统一 BigDecimal.ZERO。 |
| excludeOrderCode | 编辑/审核当前订单编码 | 读取冻结占用时扣除当前订单已有占用,避免重复计算。 |
| paymentState | 支付状态 | paymentState=1 表示已支付,本次 nextOrderAmount 按 0 处理。 |
| scene | 业务场景 | SAVE、UPDATE、AUDIT、FORCE_SUBMIT 等,用于日志、风控策略和提示文案。 |

6.2 校验流程

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| snapshot = customerCreditSnapshotService.getSnapshot(customerId, excludeOrderCode) nextOrderAmount = paymentState == 1 ? 0 : orderAmount exposureAmount = snapshot.debtAmount + snapshot.frozenCreditAmount + nextOrderAmount creditEnough = snapshot.creditAmount == null || snapshot.creditAmount <= 0 || exposureAmount <= snapshot.creditAmount overdue = customerCreditSnapshotService.hasOverdueReceivable(customerId) allowed = !overdue && creditEnough if (!allowed) { forceSubmitAllowed = riskPolicyService.canSkip(overdue, !creditEnough, customerId, scene) } return RespOrderCreditCheckDTO(...) |

6.3 返回 DTO

|-------------------------------|---------------|------------------------------------|
| 字段 | 类型 | 说明 |
| allowed | Boolean | 是否允许直接提交。 |
| creditEnough | Boolean | 授信额度是否足够;creditAmount<=0 时为 true。 |
| overdue | Boolean | 是否存在逾期未结应收。 |
| forceSubmitAllowed | Boolean | 不允许直接提交时,是否允许在风控策略下强制提交。 |
| message | String | 用户提示文案。 |
| creditAmount | BigDecimal | 授信总额。 |
| currentDebt | BigDecimal | 当前欠款。 |
| frozenCreditAmount | BigDecimal | 冻结授信。 |
| orderAmount | BigDecimal | 本次订单金额。 |
| exposureAmount | BigDecimal | 本次提交后的风险敞口。 |
| riskPolicyId / riskPolicyName | Long / String | 命中的风控策略。 |

6.4 拦截提示文案建议

|----------|-----------------------------------------------------------------------------------------------------------------|
| 场景 | 提示文案 |
| 授信不足 | 客户授信额度不足:授信总额 {creditAmount},当前欠款 {debtAmount},冻结授信 {frozenAmount},本次订单 {orderAmount},提交后风险敞口 {exposureAmount}。 |
| 存在逾期 | 客户存在逾期未结应收,暂不允许提交订单。请先完成收款/核销,或按风控策略发起强制提交。 |
| 授信不足且逾期 | 客户存在逾期未结应收,且授信额度不足。需处理逾期或由具备权限的人员按风控策略强制提交。 |
| 额度未启用但逾期 | 客户未启用额度上限,但存在逾期未结应收,暂不允许提交订单。 |
| 强制提交通过 | 已根据风控策略【{policyName}】强制提交,请在业务日志中确认原因。 |

7. 订单生命周期授信流程

|----------|-------------------------------------------------|----------------------------------------------------|---------------------|---------------------------------|
| 业务动作 | 前端动作 | 后端授信动作 | 冻结授信变化 | 日志要求 |
| 新增订单 | 保存前调用 /oms/omsorderbill/checkCreditBeforeSave | 保存接口内部再次 reserveOrderCredit | 未支付且金额>0 时增加占用 | 主日志创建/更新,明细记录 SAVE 场景。 |
| 修改订单 | 按目标金额重新预校验 | reserveOrderCredit 读取已有占用并调整差额 | 金额增加则增冻,金额减少则释放差额 | 明细记录 UPDATE 场景和 deltaAmount。 |
| 审核订单 | 审核前调用 /oms/omsorderbill/checkCreditBeforeSubmit | 审核流程再次 checkOrderAllowed 并 reserveOrderCredit | 确保审核时订单金额和支付状态仍满足规则 | 记录 AUDIT 场景;强制提交记录原因。 |
| 反审核 | 无需前端计算冻结金额 | releaseOrderCredit(orderCode, 反审核订货单释放授信占用) | 释放该订单有效占用 | 明细记录 RELEASE_REVERSE_AUDIT。 |
| 作废订单 | 作废确认 | releaseOrderCredit(orderCode, 作废订货单释放授信占用) | 释放该订单有效占用 | 明细记录 RELEASE_CANCEL。 |
| 对账转应收 | 对账确认 | 插入应收后 releaseOrderCredit(orderCode, 订单对账转应收释放授信占用) | 冻结减少,欠款增加 | 明细记录 POST_TO_RECEIVABLE,风险敞口连续。 |
| 已支付订单 | 支付状态传入校验接口 | nextOrderAmount 按 0;如已有占用则应释放或调整为 0 | 不占用授信 | 记录 paymentState=1。 |
| 金额为 0 | 正常保存/审核 | 不新增占用;如原有占用则调整为 0 | 释放原占用 | 记录 deltaAmount。 |

7.1 生命周期示意

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 订单新增/修改 -> checkCreditBeforeSave 仅预校验 -> save/update 事务内 reserveOrderCredit -> cus_credit_occupy_log 生效,cus_customer.frozen_credit_amount 增加/调整 订单审核 -> checkCreditBeforeSubmit 预校验 -> audit 事务内再次校验并 reserveOrderCredit -> 审核通过,冻结占用保持准确 反审核 / 作废 -> releaseOrderCredit -> 释放有效占用,重复释放无影响 对账转应收 -> 插入 oms_order_bill_debt -> cus_customer_debt.debt_amount 增加 -> releaseOrderCredit -> 风险敞口从冻结授信转为应收欠款 |

7.2 reserveOrderCredit / adjustOrderCredit 推荐逻辑

  • **读取当前占用:**按 source_type + order_code 查询主日志,并使用 selectByOrderCodeForUpdate 加行锁。
  • **计算目标占用:**未支付且订单金额大于 0 时 targetOccupyAmount=orderAmount;已支付或金额为 0 时 targetOccupyAmount=0。
  • **计算差额:**deltaAmount = targetOccupyAmount - currentActiveOccupyAmount。
  • **差额大于 0:**调用 increaseFrozenCredit 原子增加冻结金额;SQL 条件必须校验额度足够。
  • **差额小于 0:**调用 decreaseFrozenCredit 释放差额;使用 greatest 防止出现负数。
  • **差额等于 0:**不更新 cus_customer,仅记录必要的审计日志或直接返回。
  • **更新主日志:**保持 occupy_amount 为目标占用金额,release_amount 累加释放金额,occupy_state 与占用金额一致。
  • **写入明细日志:**每次差额调整均记录前后金额、delta、场景、支付状态、操作人。

8. 并发、幂等与事务控制

|----------|-------------------------------------------------------------------------------------------------|-----------------------------|
| 控制点 | 现有/推荐实现 | 防护目标 |
| 同一订单并发修改 | selectByOrderCodeForUpdate 对 cus_credit_occupy_log 按 source_type + order_code 加行锁。 | 避免同一订单重复冻结或重复释放。 |
| 幂等标识 | idempotent_key = OMS_ORDER:{orderCode}。 | 同一订单重复请求不会生成多条有效占用。 |
| 冻结增加 | increaseFrozenCredit 使用 SQL 条件校验 credit_amount >= debt_amount + frozen_credit_amount + 本次新增占用。 | 并发订单下防止超额授信。 |
| 冻结释放 | decreaseFrozenCredit 使用 greatest(frozen_credit_amount - amount, 0)。 | 释放动作重复调用不导致负数。 |
| 释放状态 | 只处理 occupy_state=1 的有效占用记录。 | 已释放或不存在的记录直接返回,保证释放幂等。 |
| 事务边界 | 订单状态变更、占用日志、cus_customer 冻结金额变更应在同一事务内完成。 | 避免订单已审核但授信未冻结、或冻结已释放但订单未作废。 |
| 异常处理 | SQL 更新行数为 0 时抛出授信不足或并发冲突异常。 | 调用方明确失败原因并回滚订单动作。 |

8.1 原子增加冻结授信的伪 SQL

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| UPDATE cus_customer s LEFT JOIN cus_customer_debt d ON d.customer_id = s.id SET s.frozen_credit_amount = IFNULL(s.frozen_credit_amount, 0) + :deltaAmount WHERE s.id = :customerId AND ( s.credit_amount IS NULL OR s.credit_amount <= 0 OR s.credit_amount >= IFNULL(d.debt_amount, 0) + IFNULL(s.frozen_credit_amount, 0) + :deltaAmount ); |

说明:如果更新行数为 0,表示在当前并发状态下额度不足或客户状态异常,应抛出业务异常并回滚订单动作。

8.2 一致性巡检公式

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 客户冻结授信一致性: cus_customer.frozen_credit_amount == SUM(cus_credit_occupy_log.occupy_amount - cus_credit_occupy_log.release_amount) WHERE occupy_state = 1 AND customer_id = cus_customer.id 风险敞口: exposureAmount = cus_customer_debt.debt_amount + cus_customer.frozen_credit_amount 可用授信: availableCreditAmount = cus_customer.credit_amount - exposureAmount |

9. 客户信息页面与接口方案

9.1 客户信息页面展示

|-------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------|
| 页面 | 字段/功能 | 要求 |
| cus/mallshop.vue 客户列表 | creditAmount、frozenCreditAmount、availableCreditAmount、overdueStateStr | 列表展示授信总额、冻结授信、可用授信、逾期状态;额度未启用时展示明确文案。 |
| cus/mallshopview.vue 客户详情 | creditAmount 可编辑;frozenCreditAmount、availableCreditAmount、overdueStateStr 只读 | 编辑保存时走额度下调保护;冻结授信不可手工输入。 |
| 冻结占用订单明细 | orderCode、billStateName、creditOccupyAmount、creditOccupyStageName、orderAmount、sendoutAmount、affireAmount、deliveryDateTime、occupyTime | 调用 /cus/mallshop/creditOccupyOrders;只展示 occupy_state=1 的有效占用。 |
| shop/mallshop.vue / shop/mallshopview.vue | creditAmount 展示或编辑 | 如果仍保留编辑能力,必须复用后端同一额度下调保护;冻结/可用展示建议逐步统一到客户模块。 |

9.2 接口清单

|--------|--------------------------------------------------|----------------|-----------------------------------------------|
| 方法 | 接口 | 用途 | 关键说明 |
| GET | /cus/mallshop/{id} | 客户详情 | 返回授信总额、冻结授信、可用授信、逾期状态。 |
| GET | /cus/mallshop/page | 客户列表 | 列表展示授信字段,建议统一走快照服务。 |
| GET | /cus/mallshop/creditOccupyOrders?customerId={id} | 冻结占用订单列表 | 返回未释放订单占用明细。 |
| POST | /oms/omsorderbill/checkCreditBeforeSave | 订货单新增/修改保存前预校验 | 只校验,不改变冻结授信。 |
| POST | /oms/omsorderbill/checkCreditBeforeSubmit | 订货单审核前预校验 | 只校验,返回是否允许强制提交。 |
| POST | /oms/omsorderbill/save | 新增订货单 | 服务端仍会 reserveOrderCredit。 |
| PUT | /oms/omsorderbill/update | 修改订货单 | 服务端按目标金额调整占用。 |
| 现有接口 | 审核、反审核、作废、对账转应收 | 订单生命周期动作 | 内部集成 reserveOrderCredit 或 releaseOrderCredit。 |

9.3 前端交互建议

  • **保存/审核前预校验:**订单保存前调用 checkCreditBeforeSave,审核前调用 checkCreditBeforeSubmit。
  • **二次确认弹窗:**授信不足或逾期时展示 creditAmount、currentDebt、frozenCreditAmount、orderAmount、exposureAmount 和具体原因。
  • **强制提交入口:**仅当 forceSubmitAllowed=true 且当前用户具备权限时显示强制提交按钮,并要求填写 forceSubmitReason。
  • **客户详情:**冻结占用订单列表可点击订单号跳转订单详情,便于业务人员处理占用。
  • **只读字段保护:**frozenCreditAmount、availableCreditAmount、overdueStateStr 前端只读,后端也不接受普通编辑接口覆盖。

10. 额度下调保护

客户信用额度允许上调或保持不变;当新额度低于旧额度时,必须校验新额度不能低于当前已发生的风险敞口。该规则避免把客户授信调低到欠款与冻结占用以下,造成可用授信口径失真。

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| lowerLimit = currentDebt + frozenCreditAmount if (newCreditAmount < oldCreditAmount && newCreditAmount < lowerLimit) { throw new BusinessException( "授信额度不能低于当前欠款与冻结授信占用合计" ); } |

|-------------|---------|--------|--------|---------|-----------------|
| 场景 | 旧额度 | 欠款 | 冻结 | 新额度 | 结果 |
| 上调额度 | 1000 | 600 | 200 | 1500 | 允许。 |
| 保持额度 | 1000 | 600 | 200 | 1000 | 允许。 |
| 下调但不低于风险敞口 | 1000 | 600 | 200 | 800 | 允许,最低可设额度为 800。 |
| 下调低于风险敞口 | 1000 | 600 | 200 | 700 | 拒绝。 |
| 额度未启用改为启用 | 0/null | 600 | 200 | 1000 | 允许;新额度 >= 800。 |
| 额度未启用改为过低额度 | 0/null | 600 | 200 | 500 | 拒绝。 |

11. 风控策略与强制提交审计

当授信校验不通过时,系统可以通过 B2bRiskControlPolicyService 判断是否允许跳过额度不足或逾期限制。强制提交不是绕过规则,而是在有策略、有权限、有原因、有日志的前提下进行受控放行。

|---------|--------------------------------------------------------------------------------------------|
| 控制项 | 要求 |
| 策略范围 | 明确策略可跳过的是"额度不足""逾期限制"还是两者都可跳过。 |
| 权限控制 | 强制提交按钮和后端接口均校验权限,不能仅靠前端隐藏。 |
| 原因填写 | forceSubmitReason 必填,并限制长度、过滤空白。 |
| 审计字段 | policyId、policyName、forceSubmitReason、operatorId、operatorName、operateTime、scene、orderCode。 |
| 报表追溯 | 强制提交订单可在报表中筛选,便于风控复盘。 |
| 二次校验 | 正式提交接口仍需重新计算当前授信快照,避免预校验后金额或欠款发生变化。 |

11.1 强制提交结果处理

|----------|------------|--------------|
| 校验状态 | 策略结果 | 最终结果 |
| 额度不足,未逾期 | 策略允许跳过额度不足 | 允许强制提交并记录审计。 |
| 逾期,额度足够 | 策略允许跳过逾期 | 允许强制提交并记录审计。 |
| 逾期且额度不足 | 策略同时允许两项跳过 | 允许强制提交并记录审计。 |
| 逾期且额度不足 | 策略只允许其中一项 | 不允许强制提交。 |
| 策略不存在或失效 | 不允许跳过 | 拒绝提交。 |

12. 数据巡检、修复与监控

12.1 上线前数据库确认

  1. 确认 cus_customer.credit_amount、cus_customer.frozen_credit_amount 字段存在,frozen_credit_amount 默认值为 0,历史空值可统一回填为 0。

  2. 确认 cus_customer_debt.customer_id 唯一,debt_amount 与应收/收款链路一致。

  3. 确认 cus_credit_occupy_log、cus_credit_occupy_detail_log 字段完整,source_type + order_code 或 idempotent_key 有唯一约束。

  4. 确认 oms_order_bill_debt 的 due_date、still_debt_amount、del_flag、enabled 等字段可支撑逾期查询。

  5. 确认已有订单状态与占用日志不存在明显不一致,例如已作废订单仍处于 occupy_state=1。

12.2 巡检脚本方向

|---------------------------------------------|-----------------|--------------------------------------|
| 巡检项 | 异常含义 | 处理建议 |
| frozen_credit_amount < 0 | 重复释放或手工改库异常。 | 立即修正为 0,并追查释放日志。 |
| 客户冻结金额 != 有效占用日志汇总 | 占用日志与客户冻结金额不一致。 | 以有效占用日志为准生成候选修复清单,业务确认后修复。 |
| occupy_state=1 但订单已作废/已反审核/已转应收 | 订单生命周期释放缺失。 | 补调用 releaseOrderCredit 或按日志生成修复 SQL。 |
| 订单未转应收且未作废但没有占用日志 | 历史订单未接入授信占用。 | 根据订单状态和金额评估是否补冻结,避免直接批量冻结影响客户。 |
| creditAmount>0 且 debt+frozen>creditAmount | 客户已超授信。 | 可能来自历史数据或强制提交,纳入风控报表。 |
| 逾期应收 due_date 为空 | 授信逾期判断可能漏拦截。 | 先修客户账期/应收到期日快照,再启用严格逾期控制。 |

12.3 监控指标

  • 每日统计冻结授信不一致客户数、金额差异合计。
  • 每日统计强制提交订单数、涉及金额、命中策略分布。
  • 每日统计逾期拦截次数、授信不足拦截次数、并发额度不足异常次数。
  • 对 frozen_credit_amount 负数、占用日志重复、释放后仍有效等严重异常建立告警。
  • 定期核对 cus_customer_debt.debt_amount 与应收明细未结金额汇总差异。

13. 实施计划与优先级

|---------|----------|-------------------------------------------------------------------------------|-------------------|
| 优先级 | 任务 | 主要内容 | 交付物 |
| P0 | 数据库与约束确认 | 确认字段、默认值、唯一约束、索引;补齐必要 DDL。 | 数据库检查清单 / DDL 脚本。 |
| P0 | 统一授信快照服务 | 抽取 CustomerCreditSnapshotService,统一客户详情、列表、订单校验口径。 | 服务类、DTO、单元测试。 |
| P0 | 授信控制服务闭环 | 完善 checkOrderAllowed、reserveOrderCredit、adjustOrderCredit、releaseOrderCredit。 | 核心服务实现与异常文案。 |
| P0 | 订单生命周期接入 | 保存、修改、审核、反审核、作废、转应收全部接入授信控制。 | 订单服务改造与回归用例。 |
| P0 | 额度下调保护 | 客户编辑、导入更新均校验新额度不低于 debt+frozen。 | 后端校验与前端提示。 |
| P1 | 客户页面展示 | 客户列表/详情展示授信总额、冻结、可用、逾期与冻结占用订单。 | 前端页面与接口联调。 |
| P1 | 风控强制提交 | 接入策略、权限、原因、日志和报表。 | 策略联调与审计日志。 |
| P1 | 导入导出对齐 | 客户导入导出保留 creditAmount,导入不得直接覆盖 frozenCreditAmount。 | 模板、校验、错误行提示。 |
| P1 | 数据巡检脚本 | 冻结金额一致性、有效占用日志、逾期应收、超授信客户巡检。 | 只读巡检 SQL 与修复候选清单。 |
| P2 | 历史数据治理 | 按候选清单分批修复历史冻结/日志/逾期数据。 | 修复记录与后置校验报告。 |
| P2 | 代码治理 | 清理乱码注释、复核 MallShopDao.updateCreditAmount 语义、抽象 source_type。 | 代码清理 PR。 |

13.1 推荐开发顺序

  1. 先完成数据库字段、索引、唯一约束确认,避免后续服务无法保证幂等。

  2. 抽取授信快照服务,统一客户详情、列表、订单校验的数据来源。

  3. 完善授信控制服务的校验、占用、调整、释放逻辑和异常文案。

  4. 改造订单新增、修改、审核、反审核、作废、对账转应收链路。

  5. 改造客户信息页面和额度下调保护。

  6. 接入风控策略、强制提交审计和报表字段。

  7. 补充自动化/手工测试,并执行上线前数据巡检。

  8. 上线后观察巡检指标,再按审批结果处理历史修复。

14. 测试用例

|--------|------------------------------------------|---------------------------------|-----------------------|----------------------------------------|
| 编号 | 场景 | 前置条件 | 操作 | 期望结果 |
| TC01 | 授信充足 | credit=1000,debt=200,frozen=100 | 新订单 600 | 允许提交,exposure=900。 |
| TC02 | 授信不足 | credit=1000,debt=200,frozen=100 | 新订单 800 | 拒绝提交,exposure=1100,creditEnough=false。 |
| TC03 | 额度未启用 | credit=0 或 null,无逾期 | 新订单任意金额 | 额度上限不拦截,仍返回 creditEnough=true。 |
| TC04 | 额度未启用但逾期 | credit=0 或 null,存在逾期应收 | 新订单提交 | 拒绝提交,除非风控策略允许跳过逾期。 |
| TC05 | 订单金额调增 | 原订单占用 300 | 修改为 500 | frozenCreditAmount 只增加 200。 |
| TC06 | 订单金额调减 | 原订单占用 500 | 修改为 300 | frozenCreditAmount 释放 200。 |
| TC07 | 订单修改为已支付 | 原订单占用 500 | paymentState=1 | 目标占用为 0,释放原占用。 |
| TC08 | 重复保存 | 同一订单重复调用 reserveOrderCredit | 并发/重复请求 | 不重复冻结,主日志保持一条有效记录。 |
| TC09 | 审核前预校验 | 订单已有占用 300 | 审核传入 excludeOrderCode | 校验时扣除本订单已有占用,避免重复计算。 |
| TC10 | 反审核释放 | 订单占用有效 | 反审核 | 释放占用,重复反审核释放无影响。 |
| TC11 | 作废释放 | 订单占用有效 | 作废订单 | 释放占用,occupy_state 更新为已释放。 |
| TC12 | 转应收 | 订单占用 500 | 对账转应收 | frozen 减少 500,debt 增加 500,可用授信风险口径连续。 |
| TC13 | 额度下调保护 | debt=600,frozen=200 | 额度从 1000 调到 700 | 拒绝,最低可设置额度为 800。 |
| TC14 | 额度下调刚好等于风险敞口 | debt=600,frozen=200 | 额度从 1000 调到 800 | 允许。 |
| TC15 | 并发占用 | 客户剩余额度只够一笔订单 | 两个订单同时提交 | 只有满足 SQL 条件的请求成功,另一请求返回授信不足。 |
| TC16 | 强制提交审计 | 存在授信不足,策略允许跳过 | 填写原因强制提交 | 订单成功,记录 policyId、policyName、原因、操作人、时间。 |
| TC17 | 策略不足 | 逾期且授信不足,策略只允许跳过额度不足 | 强制提交 | 仍拒绝,因为逾期未被策略放行。 |
| TC18 | 客户冻结订单列表 | 客户存在多笔有效占用 | 打开客户详情 | 列表展示订单号、状态、占用金额、占用时间等字段。 |
| TC19 | 冻结一致性巡检 | 人工制造 frozen 与日志汇总不一致 | 执行巡检 SQL | 输出异常客户和差异金额。 |
| TC20 | cus_customer.frozen_credit_amount 不可直接编辑 | 调用客户编辑接口传入 frozenCreditAmount | 保存客户 | 后端忽略或拒绝该字段,不允许普通接口覆盖冻结金额。 |

15. 验收标准

  • 客户列表和详情能正确展示授信总额、冻结授信、可用授信和逾期状态。
  • availableCreditAmount、overdueState、frozenCreditAmount 的计算口径在客户列表、详情、订单校验中一致。
  • 新增、修改、审核订单均会在服务端执行授信校验和冻结占用,不能只依赖前端预校验。
  • 同一订单多次保存、审核或并发修改不会重复冻结;订单调额只调整差额。
  • 反审核、作废、对账转应收可以释放冻结占用,且重复释放不影响结果。
  • 订单转应收后 frozenCreditAmount 减少,cus_customer_debt.debt_amount 增加,风险敞口连续。
  • creditAmount 为空或小于等于 0 时不启用额度上限,但逾期应收仍然拦截。
  • 额度下调不能低于 debtAmount + frozenCreditAmount。
  • 授信不足、逾期、强制提交均有明确提示和审计日志。
  • cus_credit_occupy_log 具备幂等约束,cus_credit_occupy_detail_log 能追溯每次变化。
  • 上线前巡检无严重异常;如存在历史异常,必须形成候选清单、业务确认和修复记录。

16. 风险与后续优化

|---------------------------------------|-------------------------------|-------------------------------------------|
| 风险/问题 | 影响 | 建议处理 |
| 授信快照计算逻辑重复 | 客户详情、客户列表、订单校验结果可能不一致。 | 抽取 CustomerCreditSnapshotService 并统一调用。 |
| MallShopDao.updateCreditAmount 语义疑似不清 | 方法名像更新额度,SQL 却可能是扣减额度,存在误用风险。 | 复核所有调用点,必要时重命名为 decreaseCreditAmount 或废弃。 |
| 冻结金额与日志汇总不一致 | 客户可用授信展示错误,订单可能误拦截或误放行。 | 上线前后执行一致性巡检,修复必须留痕。 |
| 逾期 due_date 依赖账期快照 | 到期日缺失会导致逾期拦截失效。 | 先确保应收明细到期日完整,再启用严格逾期控制。 |
| 强制提交缺少审计 | 风控放行不可追溯,后续责任不清。 | 强制提交必须记录策略、原因、人员、时间和订单。 |
| 多订单来源扩展 | 不同来源 orderCode 可能冲突。 | source_type 必须纳入唯一键,idempotent_key 按来源扩展。 |
| 中文注释/Swagger 乱码 | 影响接口文档和后续维护。 | 作为 P2 技术债治理,不与核心授信逻辑混改。 |
| 历史订单未接入授信 | 上线初期冻结金额可能低估。 | 以巡检报告和业务确认决定是否补冻结,不建议直接盲目回填。 |

附录 A:开发检查清单

|--------------------------------------------|----------|--------------------------------------------------------------------------------------|
| 检查项 | 是否完成 | 备注 |
| 数据库字段与唯一约束已确认 | □ | cus_customer、cus_customer_debt、cus_credit_occupy_log、detail_log、oms_order_bill_debt。 |
| CustomerCreditSnapshotService 已抽取 | □ | 客户列表、详情、订单校验共用。 |
| CustomerCreditControlService 支持校验/占用/调整/释放 | □ | 含异常文案和事务控制。 |
| 订单新增、修改、审核接入 reserveOrderCredit | □ | 服务端二次校验。 |
| 反审核、作废、转应收接入 releaseOrderCredit | □ | 释放幂等。 |
| 客户额度下调保护已接入 | □ | 编辑与导入均生效。 |
| 风控强制提交审计已完成 | □ | 策略、原因、操作人、时间。 |
| 前端授信提示和占用订单列表已完成 | □ | 客户详情可追溯冻结来源。 |
| 自动化/手工测试用例已执行 | □ | 至少覆盖本文 TC01-TC20。 |
| 上线前数据巡检已执行 | □ | 异常已确认或修复。 |

附录 B:建议保留的核心方法清单

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| CustomerCreditControlService - checkOrderAllowed(customerId, orderAmount, excludeOrderCode, paymentState, scene) - reserveOrderCredit(customerId, orderCode, orderAmount, paymentState, scene) - adjustOrderCredit(customerId, orderCode, targetOccupyAmount, scene) - releaseOrderCredit(orderCode, releaseReason) CustomerCreditSnapshotService - getSnapshot(customerId) - getSnapshot(customerId, excludeOrderCode) - listActiveOccupyOrders(customerId) - hasOverdueReceivable(customerId) |

相关推荐
升鲜宝供应链及收银系统源代码服务24 天前
升鲜宝业务功能导入模块PRD 开发文档(一)---升鲜宝生鲜配送供应链管理系统源代码服务
生鲜配送系统·升鲜宝生鲜配送源代码·后端app与手机端·b2b订货商城·客户订货系统源代码·升鲜宝·生鲜配送系统架构设计
升鲜宝供应链及收银系统源代码服务1 个月前
【竞品与研究】森果开票助手功能模块开发方案及数据库结构说明(一)---升鲜宝生鲜配送供应链管理系统源代码服务
生鲜供应链源代码·供应链源代码出售·生鲜配送源代码服务·门店连锁系统源代码·猪肉生产加工系统源代码·生鲜配送系统排名·生鲜配送架构设计