105 Commits
Author SHA1 Message Date
rcx 25a0e5f259 Merge remote-tracking branch 'origin/test' into test 2026-09-15 16:54:43 +08:00
rcx 6f43bed5d4 fix(oms,wms): 下单冻结推送OMS镜像丢loginUser致NPE静默失败——O侧库存查询冻结不同步根因修复
- 根因: WMS pushOmsMirror 用 MaterialInventoryParamDO(无loginUser字段)copyProperties到OMS参数, loginUser=null 致 OMS kctzjl 写调整记录时 NPE(先于xgkc执行)→镜像不更新+无调整记录, 异常被 Feign @Async+fallback(return null) 完全吞掉, WMS 侧静默无感知
- 修复1(WMS): pushOmsMirror 显式 setLoginUser, 3 个调用点传入登录态兜底后的 loginUser
- 修复2(OMS): kctzjl 补三个文案分支——下单冻结(并入库存冻结:可用-冻结+库存不变, adjustType=3)、下单解冻(并入取消分配:可用+冻结-库存不变)、出库反审-还原冻结(新增:实物+冻结+可用不变, 合并快照两步推送)
- 修复3(OMS): omsEdit try-catch 失败 log.error 带 type/kctzType/relateNo/全部匹配键后原样抛出, 推送失败不再无痕(此前连错误日志都没有)
- 存量: 在途冻结单 R260915004 镜像需按 16.7 节 SQL 补同步; R260915003 因 RQZ260723005 入库镜像行缺失需先补建镜像行(存量对账问题单独排查)
2026-09-15 16:54:02 +08:00
王奎兴 38a81fe189 Merge branch 'dev-bms-260826' into test
# Conflicts:
#	mhd_wms/src/main/java/com/mhd/wms/domain/materialInventory/repository/mapper/MaterialInventoryMapper.java
#	mhd_wms/src/main/resources/mapper/materialInventory/MaterialInventoryMapper.xml
2026-09-15 16:44:42 +08:00
王奎兴 5892efccaf 定时任务添加日志; 2026-09-15 16:42:02 +08:00
王奎兴 ae4e0e129b 定时任务逻辑修改; 2026-09-15 16:21:28 +08:00
王奎兴 5c4e460cac 定时任务添加散租过滤条件; 2026-09-15 14:52:28 +08:00
rcx a473387574 fix(oms): 老预约单(客户预约)下发生成的业务单补下单冻结钩子, 消除最后一个不冻结入口
- issueOrder 路径原直存业务单绕过 insert 冻结钩子, 模式1仓库下不冻结; 排查确认入口活跃在用(App端/下发链路近期均有功能提交), 予以补齐
- 新增 resolveStockLockMode/buildStockLockModeJsonByEntities/tryFreezeOnLegacyIssue 公开复用方法, compensateUnfreeze 改 public
- 下发顺序重排: 明细真实判定stamp → 单头JSON真实值 → 冻结(失败阻断下发, R2无批次同insert语义) → try-catch落库(失败补偿解冻, 与insert"冻结先行+补偿"模式一致)
- 业务单号沿用老预约单号, 冻结幂等/取消下发/删除/反审解冻均以此单号闭环
- 至此模式1仓库下所有业务单入口(新增/批量/Excel单工作簿/多工作簿导入/编辑/老预约单下发)全部冻结
2026-09-15 13:37:02 +08:00
rcx bb0cd350ad fix(wms): 完成上架回写OMS入库业务单入库数量被跳过——getBusinessOrderNumber带ISSUE_STATUS!=4过滤, Feign回调先置执行单完成态后必查空, 致业务单in_quantity恒0 2026-09-15 11:31:09 +08:00
rcx 24f5dd683f Merge remote-tracking branch 'origin/test' into dev-ty1.2 2026-09-15 09:53:04 +08:00
rcx be5e3fc5ee feat(oms,wms): 出库全链路物料明细/单头增加库存锁定模式字段(存储+回显)
- 出库业务单明细/执行单明细/WMS出库明细/拣货明细/复核明细新增stockLockMode; 业务单单头新增JSON数组字段、执行单单头新增单值字段
- 下单/编辑时按明细行仓库实时判定(1-下单冻结 2-分配库存冻结默认), 下发后沿 执行单→WMS推单→拣货→复核 逐级复制继承, 交接单回显内存组装
- 业务单单头JSON两段式: 下单按仓写入(无执行单号), 下发覆盖为执行单级(带CK执行单号)
- WMS自建/通知/调拨单明细固定2; TZPD外部推入/老预约单下发路径固定2
- 达梦SQL脚本(OMS库4表+WMS库3表), 明细列DEFAULT 2存量自动回填
2026-09-15 09:30:54 +08:00
王奎兴 ab26e6f130 打印返回值 2026-09-14 20:26:42 +08:00
rcx 50423b4e4b feat(oms,wms): 入库/出库业务单执行单与WMS出入库各页面列表返回计划数量与实际数量总和(全量不分页合计)
- OMS入库业务单/执行单: list/list1返回totalPlanQuantity/totalInQuantity(与行quantity/in_quantity列同源)
- WMS入库/收货/上架: 返回totalPlanQuantity/totalInQuantity(上架计划数量=明细应上架量; 入库数量=收货/上架数量, 与行列同口径)
- WMS出库/拣货/复核/交接: 返回totalPlanQuantity/totalOutboundQuantity(各页实际量列同口径; 复核明细聚合不滤del_flag兼容已反审tab)
- 合计SQL与queryList同条件片段/同JOIN结构, 分页查询后同一DO查询, 响应与total同层级追加字段, 前端纯增量兼容
2026-09-14 18:02:57 +08:00
王奎兴 134ab4116c 发测试; 2026-09-14 17:04:04 +08:00
王奎兴 29cbf80b7b Merge branch 'dev-ty1.2' into test
# Conflicts:
#	mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/WmsServiceFeign.java
2026-09-14 17:00:41 +08:00
王奎兴 0314d80bca Merge branch 'dev-bms-260826' into test 2026-09-14 16:57:12 +08:00
王奎兴 0d9ea32f85 后端查询账单金额,已收未收等数据; 2026-09-14 16:48:12 +08:00
王奎兴 6cb70805f1 批量收款接口事务; 2026-09-14 16:26:37 +08:00
rcx e59173b3f9 feat(oms): 出库业务单/出库执行单列表计划数量与出库数量分开计算, 返回全量不分页的计划数量总数/出库数量总数
- 新增queryListTotal: 复用queryList同条件片段, LEFT JOIN一级明细汇总SUM(quantity)/SUM(outbound_quantity), 计划数量与出库数量分开计算
- list/list1响应在data/total/code/msg同层级追加totalPlanQuantity/totalOutboundQuantity(与出库排名页合计行模式一致, 前端兼容)
- 执行单列表顺带修复每行totalOutboundQuantity恒为null(补关联子查询+resultMap映射, 对齐业务单178b7697口径)
2026-09-14 16:24:53 +08:00
王奎兴 81630339e4 批量收款接口事务; 2026-09-14 16:13:40 +08:00
rcx b1d92fdda8 feat(oms,wms,system): OMS下单冻结库存(防超卖)——仓库级库存锁定模式
- 仓库新增stock_lock_mode开关(1-下单冻结 2-分配库存冻结默认),查询/独立切换接口+在途冻结单切换守卫
- WMS新增out_order_freeze_record冻结记录表:下单冻结/编辑原子重锁/取消解冻/分配承接/交接扣减/反审还原全状态机,条件更新防并发超卖
- OMS下单即冻结(失败阻断+落库失败补偿解冻)、已下发禁编辑、cancelIssueOrder补调WMS清理(修存量不同步)、cancelAuditState镜像还原修复
- 达梦SQL脚本3个(WAREHOUSE加列/冻结记录建表/主键列名修复),测试库已执行并全流程实测通过
2026-09-14 15:41:32 +08:00
王奎兴 ed809e82d4 批量收款接口开发; 2026-09-14 15:10:29 +08:00
王奎兴 74dbf674b0 Merge branch 'dev-bms-260826' into test
# Conflicts:
#	mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/XxlJobServiceFeign.java
#	mhd-common/mhd-common-core/src/main/java/com/mhd/common/core/feign/ThirdPartyServiceFeign.java
#	mhd_oms/src/main/resources/bootstrap.yml
2026-09-14 14:44:29 +08:00
王奎兴 ec5afcc5bd Merge branch 'dev-ty1.2' into test
# Conflicts:
#	mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/BmsServiceFeign.java
#	mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/OmsServiceFeign.java
#	mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/WlhyServiceFeign.java
#	mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/WmsServiceFeign.java
#	mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/YmsServiceFeign.java
#	mhd-common/mhd-common-core/src/main/java/com/mhd/common/core/feign/ThirdPartyServiceFeign.java
#	mhd_oms/src/main/java/com/mhd/oms/infrastructure/feign/ThirdPartyServiceFeign.java
2026-09-14 14:43:19 +08:00
rcx 888a293ebd feat(oms): 出/入仓委托单新增导入接口(委托单模板与W出入库管理导入一致)
出仓: POST /outEntrustOrderApi/import, EasyExcel两遍解析(前11行表头+第12行起明细), 客户名称解析货主, 明细按料号>SKU>品名匹配货主物料档案(料号不存在/规格/SKU不一致报错), 柜号/运输方式/监管方式字典转码, 校验通过复用新增链路落库(生成CKWT单号, 待审核/未下发); 入仓: POST /reservationStockInOrderApi/importData, 恢复原注释W式导入并按现行类型重写——POI标签扫描前12行表头+ExcelUtil读13行列名明细, 按委托编号NO分组建单, 料号必填且须在该货主下存在, 规格/SKU与档案一致性校验, 数量空行/容器行跳过, 计划数量=入库数量之和, 订单来源=数据导入; OutEntrustOrderMapper新增跨库物料直查(NGWL_TEST_WMS.MATERIAL_BASE_INFO, 货主+一级组织过滤)
2026-09-14 13:55:10 +08:00
王奎兴 8324d8dc31 发31 2026-09-14 11:09:46 +08:00
王奎兴 6ce35b58e4 发30 2026-09-14 11:05:55 +08:00
王奎兴 700556ea63 发31 2026-09-14 10:58:00 +08:00
王奎兴 100a8d87ac 上架过滤已逻辑删除数据; 2026-09-14 10:54:02 +08:00
王奎兴 b0c9a41f07 Merge remote-tracking branch 'origin/dev' into dev 2026-09-14 09:33:14 +08:00
王奎兴 82d5eda139 上架过滤已逻辑删除数据; 2026-09-14 09:33:01 +08:00
王奎兴 c2e3de244b feign地址修改; 2026-09-12 17:50:48 +08:00
王奎兴 9453aea60f feign地址修改; 2026-09-12 17:36:09 +08:00
王奎兴 c877696eca Merge branch 'dev' into dev-bms-260826
# Conflicts:
#	mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/BmsServiceFeign.java
#	mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/OmsServiceFeign.java
#	mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/WlhyServiceFeign.java
#	mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/WmsServiceFeign.java
#	mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/YmsServiceFeign.java
#	mhd-common/mhd-common-core/src/main/java/com/mhd/common/core/feign/ThirdPartyServiceFeign.java
#	mhd_oms/src/main/java/com/mhd/oms/infrastructure/feign/ThirdPartyServiceFeign.java
2026-09-12 17:28:12 +08:00
王奎兴 edcfa0b510 月度汇总明细接口; 2026-09-12 14:52:03 +08:00
王奎兴 03b168abc0 token参数名 2026-09-12 14:02:47 +08:00
王奎兴 c9bd81c8f2 返回值判断 2026-09-12 11:46:01 +08:00
rcx e60f22979c fix(wms): 补提交收货/上架单已反审归档详情接口(此前遗漏未入库)
getInfo 增加 archive 重载(归档主单已软删用getById放行+校验已反审标记,明细放行软删行)+ getReverseAuditInfo 接口,补齐对接文档承诺的4个归档详情接口中缺失的收货/上架两个;随附上架默认库位注释微调
2026-09-12 11:21:33 +08:00
rcx 84ff45b43d fix(oms): 批量审核运输业务单号重复(goods_number取号三层防线)
根因: 取号为「COUNT当日单量+1+查重后移」, 非原子、无锁、无唯一索引。
批量审核并发创建运输业务单时, 多请求在同一查重窗口取到同一序号并全部
插入成功, 且将同一单号回填到出库单business_number, 页面所见单号重复。

- GoodsNumberGenerator: 取号改为当日MAX+1+存在性校验后移; 按yyMMdd分段的
  JVM锁串行化"取号+插单"; 插单后自检同号自动改号自愈; 兼容唯一索引撞号重试
- BusinessDocumentOrderDomainService: 主创建入口改走原子"取号+插单",
  出库/入库单号回填移至插单成功之后
- BusinessDocumentOrderImpl: 审核拆单子单路径同改
- Mapper: countByGoodsNumberPrefix改为selectGoodsNumbersByPrefix
2026-09-12 10:09:11 +08:00
rcx 892cc30999 fix(oms): 出入库业务单下发执行单时订单来源继承业务单(数据导入/手动创建/系统生成)不再写死"客户预约"; 手动创建业务单来源为空兜底"手动创建"; 出库下发明细过滤del_flag, 修复编辑后软删旧明细参与仓库分组导致多生成执行单且数量叠加 2026-09-12 10:00:45 +08:00
rcx da76816533 fix(oms): 反审运输链路按拆单子单全量软删——执行单挂子单下(businessDocumentId=子单id), 主单+子单+执行单+TMS链全删; 执行单列表默认过滤归档行 2026-09-12 09:53:13 +08:00
王奎兴 9ac60e446a 客户月结报告; 2026-09-11 17:39:29 +08:00
王奎兴 5ba179d29d 客户月结报告; 2026-09-11 17:36:38 +08:00
王奎兴 6613eeb3fb 批量红冲; 2026-09-11 16:36:23 +08:00
rcx b86ab75dcf fix(oms,wms): WMS出库明细uniqueId撞键修复+推单返回值校验
- WMS反审/取消下发软删出库明细时置空uniqueId(表唯一约束不含del_flag)
- omsOrderAddDetail推单前自愈: 按本次推送uniqueId值跨执行单清归档行, 反审重发不再撞键
- OMS推单三处(单头/明细/配送明细)校验返回值, 失败报WMS真实原因, 不再静默吞掉后伪装成可用库存为0
- WmsServiceFeign三方法void改AjaxResult, 控制器与降级工厂同步
2026-09-11 16:07:21 +08:00
王奎兴 0ca8548c06 查询参数; 2026-09-11 15:56:48 +08:00
王奎兴 3653c37914 查询参数; 2026-09-11 15:52:02 +08:00
王奎兴 c1bbda9f9a 撤销付款逻辑修改; 2026-09-11 15:33:59 +08:00
rcx 796bb99a8b fix(wms): 出库异常列表不再默认过滤已软删出库单, 反审生成的异常记录可见 2026-09-11 14:28:55 +08:00
rcx f9a50ae353 fix(oms): 出库反审核心改动(下发自愈/运输链路软删/issueStatus已驳回/uniqueId置空/仓库兜底切换) 2026-09-11 14:17:17 +08:00
rcx 2094ea31cd fix(oms): 仓库信息本地兜底查询去掉STOCK_LOCK_MODE列, 兜底不依赖冻结功能列, 修复无该列环境的下发报错 2026-09-11 13:52:28 +08:00
rcx 788bae9a16 fix(oms,wms): 出库反审补全——重新下发自愈/仓库信息本地兜底/运输链路反审联动/归档查询修复
- 反审后 issueStatus 置 3-已驳回(与入库侧同口径), 清空执行单号/货源单号
- 反审/取消下发软删执行明细时置空 uniqueId, 修复重新下发撞唯一约束
- 新增 WarehouseInfoApplicationService(Feign优先+本地跨库兜底), 出库侧8处调用点接入
- 反审联动软删运输链路: 运输业务单/执行单本地软删 + TMS deleteChainByExecuteOrderNumber
- WMS归档查询修复: 交接归档详情丢行/拣货归档数量恒0/复核列表归档汇总/OMS执行单已反审tab
2026-09-11 11:42:42 +08:00
rcx a65cca1cfa fix(oms,wms): 入库反审三项修复:反审记录不可见、反审后重新下发无法收货、异常列表JOIN过滤
1. 入库异常三个生成器(收货异常/上架异常/反审记录)插入时未显式设置delFlag,MyBatis-Plus insert不含该列,列无默认值落库为NULL,被列表del_flag=1过滤导致反审记录永远不可见;现显式setDelFlag(1)

2. 反审重置业务单status=1:首轮完成上架经WMS回传把业务单status写成5(已入库),不重置则重新下发时copy给执行单→W入库单→收货单,收货单带已完成状态导致前端不给收货按钮

3. 入库异常列表LEFT JOIN stock_in_order固定b.DEL_FLAG=1,反审记录关联的入库单已软删导致匹配不上,仓库等基于b表的过滤把行全部过滤掉、列表恒为空;改为按a.TYPE切换关联状态(type=3关联已软删入库单);收货/上架已反审tab同号多张入库单JOIN出重复行,限定只关联同状态最新一张(IN_ORDER_ID取max)
2026-09-11 09:41:21 +08:00
秦鸿展 0077e5d673 30 2026-09-10 23:29:33 +08:00
秦鸿展 4b5ed4d0e1 调整同步TMS修改 2026-09-10 23:07:59 +08:00
秦鸿展 3b8c0ffe8f 调整同步TMS修改 2026-09-10 22:53:22 +08:00
秦鸿展 21db0bc5da 调整同步TMS修改 2026-09-10 22:43:45 +08:00
秦鸿展 eaeb100b22 调整同步TMS修改 2026-09-10 22:21:41 +08:00
秦鸿展 8d6c252eb4 调整同步TMS修改 2026-09-10 22:16:47 +08:00
秦鸿展 86c4c07708 调整修改 2026-09-10 21:55:02 +08:00
秦鸿展 db31711ad0 驳回订单修改 2026-09-10 21:20:09 +08:00
秦鸿展 6282849468 驳回订单修改 2026-09-10 21:15:14 +08:00
秦鸿展 60381ad7c9 OMS订单费用修改 2026-09-10 20:42:16 +08:00
秦鸿展 cd4ceecf98 OMS调整字段映射 2026-09-10 20:17:05 +08:00
王奎兴 cf3299165d 定时任务方法; 2026-09-10 17:29:09 +08:00
rcx cae62e83a5 fix(oms,wms): 入库反审修复:已反审tab列表查不到数据、未下发单反审补写反审记录
1. W收货单/W上架单【已反审】tab查不到数据:queryList 的 LEFT JOIN stock_in_order 固定带 a/b.DEL_FLAG=1,归档数据(反审后双方均已软删del_flag=2)关联不上b表,仓库/货主等基于b表的过滤条件把行全部过滤掉;改为按是否查反审归档动态切换关联条件(归档按del_flag=2关联,正常列表行为不变)

2. 未下发WMS的执行单反审不产生反审记录:reverseExecutionOrder 幂等跳过分支直接return导致入库异常无留痕;现补写1条反审记录(abnormalType=6,回退库存0,备注未下发WMS),同单号已有有效记录时防重,重试/重复反审幂等

3. OMS反审Feign参数透传组织三字段(organizationId/organizationName/topOrganizationId),供WMS跳过分支写反审记录使用(该场景无W入库单可取组织信息)
2026-09-10 17:25:07 +08:00
王奎兴 432f71de06 json解析修改; 2026-09-10 17:11:12 +08:00
王奎兴 6efd37fe0e json解析修改; 2026-09-10 17:00:48 +08:00
rcx e2da7177f0 fix(oms/wms): 出库明细出库时间过滤严格按出仓日期outbound_date(去除建档时间兜底),所见即所筛 2026-09-10 16:38:34 +08:00
王奎兴 65fdac5354 定时任务生成发票; 2026-09-10 16:18:32 +08:00
rcx 0b45ad181f feat(oms/wms): 出库明细单据类型回显/查询条件/导出列补齐
- 出库明细查询新增orderTypeCode(精确)/orderTypeName(模糊)查询条件
- OMS出库明细SQL补查b.order_type_code/name,响应回显orderTypeCode/orderTypeName(原恒为null)
- WMS出库明细导出新增"业务单据类型"列(第31列)
2026-09-10 16:18:11 +08:00
王奎兴 3387d1712d 红冲接口 2026-09-10 15:33:51 +08:00
rcx 260ec1f369 fix(oms): 出入库业务单反审补充清空执行单号/下发执行时间,修复反审后前端是否执行仍显示已执行 2026-09-10 14:24:43 +08:00
rcx b28575c34d fix(wms): 入库明细导出客户编号/客户名称改取货主编码与货主名称(与页面显示一致) 2026-09-10 14:04:06 +08:00
rcx 8af8197901 feat(oms/wms): 出入库明细货物种类改显物料分类名称、单据类型回显与查询条件
一、货物种类
- O/W出入库明细查询新增返回materialClassifyName(物料分类名称,多个逗号分隔)
- WMS入库导出"货物种类"列、出库导出"种类"列改为物料分类名称(原为goodsType编码转文字)
- OMS出入库导出新增"货物种类"列,货物类型列加字典转换不再显示0/1/2编码

二、单据类型
- O/W入库明细查询新增orderTypeCode/orderTypeName回显与查询条件(编码精确/名称模糊)
- WMS入库明细导出新增"业务单据类型"列(第31列)

三、时间字段
- OMS出库明细查询返回增加completeTime完成时间
- OMS orderOutboundDate出仓日期格式恢复为yyyy-MM-dd HH:mm:ss
- OMS出库时间过滤改为三级兜底:出库时间OUTBOUND_TIME→出仓日期→建档时间

四、修复
- WMS出库导出SQL修复TOTAL_PRICE列不存在(总价改用TOTAL_AMOUNT),总价值暂无来源占位空列
2026-09-10 11:35:30 +08:00
王奎兴 76ded7ed01 Merge branch 'dev' into dev-bms-260826 2026-09-10 11:19:16 +08:00
王奎兴 bbde788aa4 撤销收款接口 2026-09-10 11:17:58 +08:00
王奎兴 10012b3437 Merge remote-tracking branch 'origin/dev-bms-260826' into dev-bms-260826 2026-09-10 10:32:09 +08:00
王奎兴 7a5e84453c xxljob 2026-09-10 10:31:56 +08:00
rcx 12f1d99e02 fix(wms): 出入库明细查询status字段改用独立SQL别名映射,避免同列双映射在增量构建下不可靠 2026-09-09 21:53:02 +08:00
秦鸿展 698a8ea9a6 OMS调整修改 2026-09-09 20:34:24 +08:00
秦鸿展 f991485ba3 OMS调整接口修改 2026-09-09 19:04:01 +08:00
rcx 244fbf9759 feat(wms): 出入库明细查询返回增加status字段(与outOrderStatus/inOrderStatus同值,便于前端绑定) 2026-09-09 18:20:44 +08:00
rcx e3826d6333 fix(oms): 出入库明细查询状态过滤口径修正-改为issue_status=4
问题:OMS执行出库单status不随WMS出库完成流转(回写代码被注释),推送创建的单据status也是默认值,
原过滤条件 status in(9,12,13)/status=5 会导致明细页查不到数据。

修复:
- 出库明细SQL:status in(9,12,13) 改为 issue_status=4(issueStatus: 0-已创建 2-已审核 3-已驳回 4-已出库)
- 入库明细SQL:status=5 改为 issue_status=4(WMS回写status=5时同步issueStatus=4,推送创建直接置4)
- 两个明细SQL新增返回 b.issue_status,前端状态列绑定issueStatus
- WithOrderPO新增issueStatus字段(导出"单据状态"列自动转文字),outOrderStatus/inOrderStatus保留返回但不再导出
2026-09-09 17:23:54 +08:00
王奎兴 5a3791e334 Merge branch 'dev' into dev-bms-260826 2026-09-09 16:44:07 +08:00
rcx f217d6ec5c 发测试 2026-09-09 15:51:57 +08:00
rcx e58b6cb9ce feat(oms/wms): 出入库明细查询-已入库/已出库状态过滤、WMS新增出入库明细查询页面及专用导出
一、OMS 入库/出库明细查询(修复+导出)
- 出库明细SQL状态过滤由=9改为in(9,12,13):已出库/已复核/已交接均算已出库,与出库排名统计口径一致
- 入库明细SQL固定只统计已入库(status=5)单据
- 入/出库明细SQL新增实际入仓/出仓日期区间过滤(IFNULL(实际日期,建档时间)兜底),支持统计当天
- 新增Excel导出接口 /executionInMaterialDetailApi/exportWithOrder、/executionOutMaterialDetailApi/exportWithOrder(全量不分页)
- 退货回仓统计:通过入库单类型 inOrderTypeCode=tui_huo_ru_ku(退货入库)过滤

二、WMS 新增入库/出库明细查询页面
- 入库明细查询:GET /materialDetailApi/listWithOrder(明细关联入库单,固定已入库(5),level=1防父子行重复计数)
- 出库明细查询:GET /outMaterialDetailApi/listWithOrder(新建OutMaterialDetailApi,关联出库单,固定in(9,12,13))
- 返回单头字段:单据状态/审核状态/货主/供应商/客户/单据类型/实际入仓出仓日期/保额/货物类型等
- 入库客户编号/名称/客户预约名称经通知单号关联notice_order取得(入库单表无客户字段)

三、WMS 明细导出专用列集
- 出库导出/exportWithOrder:按客户要求30列导出,含出库单号/出库日期/客户信息/LOT号/费用组(单价、总价、折扣、仓租、合同收费)等
- 入库导出/exportWithOrder:按客户要求30列导出,含客户信息/入库日期/每件保额/收费标准/有效期等
- 单据状态、货物种类导出时自动转文字;导出列顺序与客户清单一致
- 火险保费、入库单件净重(KG)暂无数据来源,导出空列占位,待确认数据来源后补充

四、其他
- 查询条件不传日期时默认查全部日期,统计当天由前端传当天日期区间实现
- 数据权限:非平台超管按登录人一级组织过滤
2026-09-09 15:49:09 +08:00
rcx b7cacaeb8d feat(oms,wms): 入库业务单反审——业务单改已驳回+已反审tab,执行单/W入库/收货/上架软删打标归档,库存还原扣减,W入库异常反审记录
【需求】已入库/已出库的单子客户通知需要回退的场景,单子回退到最原始状态。反审入口在 OMS-入库业务单页面【反審單據】按钮,一次操作全链路回退。

【反审规则】
1. 按页面状态 issueStatus 判断:进行中(1)/已审核(2)/已入库(4)/部分完成(5) 可反审;已创建(0)无需反审、已驳回(3)可直接编辑;已取消/已关闭(status 6/7)兜底拦截
2. 已推送关务/NC(pushStatus=3)直接拦截,避免关务与仓内数据不一致
3. W入库单有出库记录就不能反审:按入库单号+动作(分配库存/出库)查询库存调整记录>0 即拦截,另校验库存冻结量>0
4. WMS无该单数据时幂等成功(未下发单反审),反审失败可安全重试

【反审动作】
1. 入库业务单:状态改为已驳回(issueStatus=3,与审核不通过同语义,可编辑→重新审核→重新下发),打已反审标记落入【已反审】tab;清空入库数量/入库时间/执行单号,明细 INBOUND_ITEMS/TOTAL_NET_WEIGHT 归零
2. 入库执行单+物料明细:软删(del_flag=2)+打已反审标记,【已反审】tab 按「已软删+已反审」查询归档数据,避免归档单被误审核/下发
3. WMS侧(逐执行单Feign omsReverseInOrder):库存还原扣减(校验库存行合计=已完成上架量后整行对称扣回,GREATEST(x-n,0)兜底)→ W入库单/W收货单/W上架单及明细、收货/上架作业单/任务/质检单整链软删+打已反审标记 → 写1条入库异常【反审记录】(type=3/abnormalType=6,FSYC单号)用于记录跟踪

【接口】
- POST /reservationStockInOrderApi/reverseAudit 反审(inOrderIds + reverseAuditRemark,支持批量)
- 已反审tab:/reservationStockInOrderApi/list?reverseAuditStatus=1(执行单/W入库单/W收货单/W上架单同参数,后端固定限定 del_flag=2+标记=1 归档数据)
- 归档详情:/executionStockInOrderApi、/stockInOrderApi、/stockReceiptOrderApi、/stockShelfOrderApi 各增 getReverseAuditInfo/{id}(原 getInfo 行为不变)
- 反审记录筛选:/inOrderAbnormalApi/list?abnormalType=6

【DDL】sql/20260908_oms_inbound_reverse_audit.sql:RESERVATION_STOCK_IN_ORDER / EXECUTION_STOCK_IN_ORDER / STOCK_IN_ORDER / STOCK_RECEIPT_ORDER / STOCK_SHELF_ORDER 各新增 REVERSE_AUDIT_STATUS/REMARK/BY/BY_NAME/TIME 五列,发服务前先执行

【其他】
- 库存扣减走 batchSubtractInventoryQuantity 并显式传更新人,规避 Feign 链路无登录态时 SecurityUtils 抛异常
- omsReverseInOrder 的 Feign 方法与降级已随出库反审提交(715883f1)入库,本次不再重复提交
- 归档行只读,详情走 getReverseAuditInfo;正常数据与归档数据互不干扰
2026-09-09 15:38:21 +08:00
rcx 715883f1d4 feat(oms,wms): 出库业务单反审——已出库/已审核单据回退已创建
- 新增 POST /reservationStockOutOrderApi/reverseAudit,按issueStatus(2-已审核/4-已出库)判断可反审
- 主单回退已创建并打reverseStatus标记,进【已反审】tab(list status=-2),重新审核通过自动复位
- 执行单/明细软删+reverseFlag=1,预约库存调整记录软删
- WMS新增/stockOutOrderApi/omsReverseExecutionOrder:已出库还原实物库存+推OMS,未出库仅还原分配占用,WMS无数据幂等成功
- W出库/拣货/复核/交接单软删+reverseFlag=1,列表支持delFlag=2&reverseFlag=1【已反审】tab
- 四页面新增归档详情接口getReverseInfo*
- 生成出库异常记录【反审记录】(abnormalType=3,前缀FSYC)
- 需先执行SQL: 20260908两个反审脚本(OMS库+WMS库)
2026-09-09 15:20:13 +08:00
王奎兴 e2bee47aac 月度汇总视图查询接口; 2026-09-09 14:31:56 +08:00
王奎兴 910c7b1701 月度汇总视图查询接口; 2026-09-09 14:19:38 +08:00
rcx 139a3a82da feat(wms): 盘点单详情investigetionManageDetailList按物料编码排序,相同编码相邻展示(仅排序不聚合) 2026-09-09 14:18:48 +08:00
rcx fcc8102d9e feat(wms): 库存调整记录列表新增按入库单号(inOrderNumber)查询 2026-09-09 14:16:32 +08:00
rcx 7649585715 发测试 2026-09-09 09:27:57 +08:00
rcx 7511d211c6 fix(wms): 修复盘点详情批次属性attributeValue为空的问题
1. 标签映射表改LinkedHashMap固定优先级,补全批次号/入库日期/生产日期/过期日期及英文标签映射
2. fieldName反射取不到值时回退按batchLabels映射再取,修复fieldName配置失效导致值永远为空
3. batchDetailId为空的批次属性配置行不再丢弃,改为batchDetailId+标签双重去重
2026-09-09 09:14:01 +08:00
王奎兴 f118c7ff29 组织信息; 2026-09-07 17:58:16 +08:00
王奎兴 cdc8396b57 金额参数; 2026-09-07 17:45:25 +08:00
王奎兴 aad54545f1 获取不到登录人 2026-09-07 16:27:58 +08:00
王奎兴 49fb27f273 流水金额取值修改; 2026-09-07 16:08:51 +08:00
王奎兴 1367a1d225 登录用户拿不到问题处理; 2026-09-07 15:45:03 +08:00
王奎兴 590e840a7b 汇率数据类型转换; 2026-09-07 15:33:53 +08:00
王奎兴 a326af15f7 查询汇率; 2026-09-07 15:28:47 +08:00
王奎兴 494f5f8999 计费单据仓库类型; 2026-09-07 15:24:59 +08:00
王奎兴 9990701c5e 冻仓生成应收帐单; 2026-09-07 15:10:41 +08:00
王奎兴 9a15dc4978 查询结算配置 2026-09-07 15:05:41 +08:00
王奎兴 e55840c485 表别名; 2026-09-07 14:41:36 +08:00
305 changed files with 13331 additions and 671 deletions
@@ -20,7 +20,7 @@ import java.util.Set;
* bms财务结算系统feign调用接口
*/
// 目标地址由 nacos 配置项 feign.url.mhd-bms-service 指定,未配置时按服务名走服务发现
@FeignClient(contextId = "BmsService", value = ServiceNameConstants.BMS_SERVICE, url = "http://10.102.192.32:8019",fallbackFactory = RemoteBmsFeignFallbackFactory.class, configuration = FeignAutoConfiguration.class)
@FeignClient(contextId = "BmsService", value = ServiceNameConstants.BMS_SERVICE, url = "http://10.33.0.109:8019",fallbackFactory = RemoteBmsFeignFallbackFactory.class, configuration = FeignAutoConfiguration.class)
public interface BmsServiceFeign {
@@ -18,7 +18,7 @@ import java.util.List;
import java.util.concurrent.CompletableFuture;
// 目标地址由 nacos 配置项 feign.url.mhd-oms-service 指定,未配置时按服务名走服务发现
@FeignClient(contextId = "OmsService", value = ServiceNameConstants.OMS_SERVICE,url = "http://10.102.192.33:8017",fallbackFactory = RemoteOmsFeignFallbackFactory.class, configuration = FeignAutoConfiguration.class)
@FeignClient(contextId = "OmsService", value = ServiceNameConstants.OMS_SERVICE,url = "http://10.33.0.99:8017",fallbackFactory = RemoteOmsFeignFallbackFactory.class, configuration = FeignAutoConfiguration.class)
public interface OmsServiceFeign {
@@ -27,7 +27,7 @@ import java.util.List;
import java.util.Map;
// 目标地址由 nacos 配置项 feign.url.mhd-wlhy-service 指定,未配置时按服务名走服务发现
@FeignClient(contextId = "commonWlhyServiceFeign",value = ServiceNameConstants.WLHY_SERVICE,url = "http://10.102.192.31:8014",configuration = FeignAutoConfiguration.class)
@FeignClient(contextId = "commonWlhyServiceFeign",value = ServiceNameConstants.WLHY_SERVICE,url = "http://10.33.0.129:8014",configuration = FeignAutoConfiguration.class)
public interface WlhyServiceFeign {
/**
@@ -536,10 +536,22 @@ public interface WlhyServiceFeign {
*/
@PostMapping("/feign/order/add")
public AjaxResult add(@RequestBody TmsOrderAddDTO tmsOrderAddDTO);
/**
* 修改运单(OMS调整业务单后同步修改TMS订单)
* @param tmsOrderAddDTO 需携带 TMS 订单 id 定位
*/
@PostMapping("/feign/order/update")
public AjaxResult update(@RequestBody TmsOrderAddDTO tmsOrderAddDTO);
@GetMapping("/feign/order/cancelIssueDocuments")
public AjaxResult cancelIssueDocuments(@RequestParam(name="executeOrderId") String executeOrderId);
/**
* OMS出库业务单反审联动: 按执行单货源编号软删TMS整条链路(货源单->运单->运输作业单, is_delete=1), 幂等,
* 与 cancelIssueDocuments(仅关闭货源单且有有效运单即拒绝)不同, 反审口径不留TMS数据
*/
@PostMapping("/feign/order/deleteChainByExecuteOrderNumber")
public AjaxResult deleteChainByExecuteOrderNumber(@RequestParam(name="executeOrderNumber") String executeOrderNumber);
@GetMapping(value = "/ntocc/tmsSettlementExchangeRate/queryList")
public AjaxResult<?> queryList(@RequestParam(name="pageNo", defaultValue="1") Integer pageNo,
@RequestParam(name="pageSize", defaultValue="10") Integer pageSize,
@@ -21,8 +21,7 @@ import java.util.Map;
* @description: TODO
* @date 2024/5/10 8:58
**/
// 目标地址由 nacos 配置项 feign.url.mhd-wms-service 指定,未配置时按服务名走服务发现
@FeignClient(contextId = "remoteWmsServiceFeign",value = ServiceNameConstants.WMS_SERVICE,url = "http://10.102.192.32:8016",fallbackFactory = RemoteWmsFallbackFactory.class, configuration = FeignAutoConfiguration.class)
@FeignClient(contextId = "remoteWmsServiceFeign",value = ServiceNameConstants.WMS_SERVICE,url = "http://10.33.0.109:8016",fallbackFactory = RemoteWmsFallbackFactory.class, configuration = FeignAutoConfiguration.class)
public interface WmsServiceFeign {
/**
@@ -75,21 +74,21 @@ public interface WmsServiceFeign {
*/
@ApiOperation("oms下发单保存")
@PostMapping(value = "/stockOutOrderApi/omsOutOrderAdd")
public void omsOutOrderAdd(@RequestBody StockOutOrder stockOutOrder);
public AjaxResult omsOutOrderAdd(@RequestBody StockOutOrder stockOutOrder);
/**
* oms下发单明细保存
*/
@ApiOperation("oms下发单明细保存")
@PostMapping(value = "/stockOutOrderApi/omsOutOrderAddDetail")
public void omsOutOrderAddDetail(@RequestBody List<OutMaterialDetail> outMaterialDetails);
public AjaxResult omsOutOrderAddDetail(@RequestBody List<OutMaterialDetail> outMaterialDetails);
/**
* oms下发单明细保存
*/
@ApiOperation("oms下发单明细保存")
@PostMapping(value = "/stockOutOrderApi/omsOutOrderAddDeliveryDetailsLink")
public void omsOutOrderAddDeliveryDetailsLink(@RequestBody List<DeliveryDetailsLink> deliveryDetailsLinks);
public AjaxResult omsOutOrderAddDeliveryDetailsLink(@RequestBody List<DeliveryDetailsLink> deliveryDetailsLinks);
/**
@@ -113,11 +112,46 @@ public interface WmsServiceFeign {
/**
* oms下发前库存校验:可用库存不足时返回错误,OMS据此在推送前拦截下发,
* 避免单据先落库、分配才失败而在WMS残留"已审核"状态的垃圾单
* 避免单据先落库、分配才失败而在WMS残留"已审核"状态的垃圾单
* businessOrderNumber(可空):OMS出库业务单号,下单冻结模式下校验可用量=库存可用+本单冻结记录量
*/
@ApiOperation("oms下发前库存校验")
@PostMapping(value = "/stockOutOrderApi/checkOmsAssignInventory")
public AjaxResult checkOmsAssignInventory(@RequestBody List<OutMaterialDetail> outMaterialDetails);
public AjaxResult checkOmsAssignInventory(@RequestBody List<OutMaterialDetail> outMaterialDetails,
@RequestParam(value = "businessOrderNumber", required = false) String businessOrderNumber);
/**
* oms出库业务单下单冻结库存(防超卖):WMS按明细冻结库存(可用-、冻结+,库存数量不变),
* 条件更新防并发超卖,任一行可用不足整体失败并返回错误;同业务单号幂等。
* 冻结失败必须阻断OMS下单(返回非200由OMS整体回滚)。
*/
@ApiOperation("oms出库业务单下单冻结库存")
@PostMapping(value = "/stockOutOrderApi/omsFreezeInventory")
public AjaxResult omsFreezeInventory(@RequestBody OmsFreezeInventoryParam param);
/**
* oms出库业务单取消/删除-释放冻结库存:可用+、冻结-(库存数量不变);按业务单号幂等;
* 冻结已被执行单承接时拒绝(须先取消下发)
*/
@ApiOperation("oms出库业务单释放冻结库存")
@PostMapping(value = "/stockOutOrderApi/omsUnfreezeInventory")
public AjaxResult omsUnfreezeInventory(@RequestBody OmsFreezeInventoryParam param);
/**
* oms出库业务单编辑-原子重锁:WMS同一事务内先释放原冻结再按新明细重新冻结,
* 任一新明细可用不足整体回滚(旧冻结同样还原),满足"先释放再重新锁定"原子性要求
*/
@ApiOperation("oms出库业务单编辑重锁库存")
@PostMapping(value = "/stockOutOrderApi/omsRelockInventory")
public AjaxResult omsRelockInventory(@RequestBody OmsFreezeInventoryParam param);
/**
* 仓库是否存在活跃冻结记录(status in 1-业务单冻结 2-执行单承接):
* 仓库"库存锁定模式"(stock_lock_mode)切换守卫,存在在途冻结单时禁止切换
*/
@ApiOperation("仓库是否存在活跃冻结记录")
@GetMapping(value = "/stockOutOrderApi/hasActiveFreezeByWarehouse/{warehouseId}")
public AjaxResult hasActiveFreezeByWarehouse(@PathVariable("warehouseId") Long warehouseId);
/**
* oms下发单明细保存(返回执行结果,明细保存含自动审核链路,失败时OMS据此回滚)
@@ -134,6 +168,27 @@ public interface WmsServiceFeign {
@PostMapping(value = "/stockInOrderApi/omsCancelInOrder")
public AjaxResult omsCancelInOrder(@RequestBody StockInOrder stockInOrder);
/**
* oms出库执行单反审(已出库/已复核单据客户通知回退场景):WMS侧还原实物库存并推送OMS库存、
* 逻辑删除出库链路单据、生成1条出库异常【反审记录】;反审原因经 remark 透传。
* 已产生下游不可逆数据时返回错误,OMS据此整体回滚。
*/
@ApiOperation("oms出库执行单反审WMS回退")
@PostMapping(value = "/stockOutOrderApi/omsReverseExecutionOrder")
public AjaxResult omsReverseExecutionOrder(@RequestBody StockOutOrder stockOutOrder);
/**
* oms入库业务单反审:WMS侧一个事务内回退已入库/收货中/上架中单据——
* 校验未产生出库(按入库单号+动作"分配库存/出库"查库存调整记录>0即有出库) ->
* 按库存行整行回退库存扣减 -> 软删W入库单/W收货单/W上架单全链并打已反审标记
* (各页面【已反审】tab按"已软删+已反审"查询归档数据)-> 写1条入库异常【反审记录】(type=3)。
* 入参: inOrderNumber=执行入库单号, remark=业务单号+反审原因, updateBy/updateByName=反审人;
* WMS无该单数据时幂等返回成功(未下发单反审不产生WMS数据)。
*/
@ApiOperation("oms入库单反审WMS回退")
@PostMapping(value = "/stockInOrderApi/omsReverseInOrder")
public AjaxResult omsReverseInOrder(@RequestBody StockInOrder stockInOrder);
@ApiOperation("oms查询物料列表")
@GetMapping("/materialBaseInfoApi/queryList")
public TableDataInfo getMaterialBaseInfoList(MaterialInfoDTO materialInfoDTO);
@@ -14,7 +14,7 @@ import org.springframework.web.bind.annotation.*;
import java.util.Map;
// 目标地址由 nacos 配置项 feign.url.xxl-job-service 指定,未配置时按服务名走服务发现
@FeignClient(contextId = "XxlJobService", value = ServiceNameConstants.XXL_JOB_SERVICE,url = "http://10.102.192.32:8020",fallbackFactory = RemoteXxlJobFeignFallbackFactory.class, configuration = FeignAutoConfiguration.class)
@FeignClient(contextId = "XxlJobService", value = ServiceNameConstants.XXL_JOB_SERVICE,url = "http://10.33.0.99:8020",fallbackFactory = RemoteXxlJobFeignFallbackFactory.class, configuration = FeignAutoConfiguration.class)
public interface XxlJobServiceFeign {
@@ -10,7 +10,7 @@ import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
// 目标地址由 nacos 配置项 feign.url.mhd-yms-service 指定,未配置时按服务名走服务发现
@FeignClient(contextId = "YmsService", value = ServiceNameConstants.YMS_SERVICE,url = "http://10.102.192.32:8018",fallbackFactory = RemoteBmsFeignFallbackFactory.class, configuration = FeignAutoConfiguration.class)
@FeignClient(contextId = "YmsService", value = ServiceNameConstants.YMS_SERVICE,url = "http://10.33.0.99:8018",fallbackFactory = RemoteBmsFeignFallbackFactory.class, configuration = FeignAutoConfiguration.class)
public interface YmsServiceFeign {
/**
@@ -0,0 +1,42 @@
package com.mhd.system.api.domain;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* OMS出库业务单冻结库存请求参数
*
* @description OMS下单冻结库存模式(防超卖):OMS出库业务单「下单」即冻结WMS库存
* (可用-、冻结+,库存数量不变,库存数量=可用数量+冻结数量);
* 「编辑」原子释放旧冻结+按新明细重锁;「取消」释放冻结。
* 冻结/解冻以业务单号为幂等键。
* @date 2026-09-12
*/
@Data
public class OmsFreezeInventoryParam implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("OMS出库业务单号(幂等键)")
private String outOrderNumber;
@ApiModelProperty("执行出库单号(预留,冻结/解冻/重锁时不使用)")
private String executionOrderNumber;
@ApiModelProperty("库存行ID集合(预留)")
private List<Long> materialInventoryIds;
@ApiModelProperty("出库明细列表(须携带 materialInventoryId 定位库存行)")
private List<OutMaterialDetail> details;
@ApiModelProperty("操作人ID(Feign调用可能无登录上下文时的兜底)")
private Long operatorId;
@ApiModelProperty("操作人姓名(Feign调用可能无登录上下文时的兜底)")
private String operatorName;
@ApiModelProperty("备注")
private String remark;
}
@@ -169,6 +169,9 @@ public class OutMaterialDetail extends BaseVOEntity {
@ApiModelProperty("分配仓库id")
private Long warehouseId;
@ApiModelProperty("库存锁定模式: 1-下单冻结 2-分配库存冻结(默认)")
private Integer stockLockMode;
@ApiModelProperty("分配仓库编码")
@Excel(name = "分配仓库编码")
private String warehouseCode;
@@ -124,6 +124,10 @@ public class WarehouseFeignPO extends BaseVOEntity{
@Excel(name = "作业模式")
private String workMode;
@ApiModelProperty("库存锁定模式: 1-下单冻结 2-分配库存冻结")
@Excel(name = "库存锁定模式: 1-下单冻结 2-分配库存冻结")
private Integer stockLockMode;
@ApiModelProperty("订单号生成规则(0-日期累加 1-历史累加)")
@Excel(name = "单号生成规则")
private String orderNoGenerateRule;
@@ -546,11 +546,22 @@ public class RemoteWlhyFallbackFactory implements FallbackFactory<WlhyServiceFei
return AjaxResult.error(cause.getMessage());
}
@Override
public AjaxResult update(TmsOrderAddDTO tmsOrderAddDTO) {
return AjaxResult.error(cause.getMessage());
}
@Override
public AjaxResult cancelIssueDocuments(String executeOrderId) {
return AjaxResult.error(cause.getMessage());
}
@Override
public AjaxResult deleteChainByExecuteOrderNumber(String executeOrderNumber) {
//返回错误而非静默成功, 让OMS反审事务能感知失败并整体回滚
return AjaxResult.error("TMS反审联动软删链路失败(服务降级):" + cause.getMessage());
}
@Override
public AjaxResult<?> queryList(Integer pageNo, Integer pageSize, String pushStatus, String organizationId) {
return AjaxResult.error(cause.getMessage());
@@ -57,16 +57,19 @@ public class RemoteWmsFallbackFactory implements FallbackFactory<WmsServiceFeign
}
@Override
public void omsOutOrderAdd(StockOutOrder stockOutOrder) {
public AjaxResult omsOutOrderAdd(StockOutOrder stockOutOrder) {
//返回错误而非静默成功: 降级吞掉会让OMS继续走分配, 残留"有单无明细"的垃圾单
return AjaxResult.error("WMS下发单保存失败(服务降级):" + cause.getMessage());
}
@Override
public void omsOutOrderAddDetail(List<OutMaterialDetail> outMaterialDetails) {
public AjaxResult omsOutOrderAddDetail(List<OutMaterialDetail> outMaterialDetails) {
return AjaxResult.error("WMS下发单明细保存失败(服务降级):" + cause.getMessage());
}
@Override
public void omsOutOrderAddDeliveryDetailsLink(List<DeliveryDetailsLink> deliveryDetailsLinks) {
public AjaxResult omsOutOrderAddDeliveryDetailsLink(List<DeliveryDetailsLink> deliveryDetailsLinks) {
return AjaxResult.error("WMS下发单配送明细保存失败(服务降级):" + cause.getMessage());
}
@Override
@@ -82,6 +85,12 @@ public class RemoteWmsFallbackFactory implements FallbackFactory<WmsServiceFeign
return AjaxResult.error("WMS取消下发清理失败:" + cause.getMessage());
}
@Override
public AjaxResult omsReverseExecutionOrder(StockOutOrder stockOutOrder) {
//返回错误而非静默成功,让OMS反审事务能感知失败并整体回滚
return AjaxResult.error("WMS出库执行单反审失败(服务降级):" + cause.getMessage());
}
@Override
public void omsAutoAssign(StockOutOrderDTO stockOutOrderDTO) {
@@ -93,11 +102,35 @@ public class RemoteWmsFallbackFactory implements FallbackFactory<WmsServiceFeign
}
@Override
public AjaxResult checkOmsAssignInventory(List<OutMaterialDetail> outMaterialDetails) {
public AjaxResult checkOmsAssignInventory(List<OutMaterialDetail> outMaterialDetails, String businessOrderNumber) {
//校验接口本身不可用时返回错误,OMS会阻止下发(不能在WMS状态未知时盲目推送)
return AjaxResult.error("WMS库存校验服务不可用:" + cause.getMessage());
}
@Override
public AjaxResult omsFreezeInventory(OmsFreezeInventoryParam param) {
//返回错误而非静默成功:下单冻结失败必须阻断OMS下单落库(冻结成功与否状态未知时不允许继续)
return AjaxResult.error("WMS下单冻结库存失败(服务降级):" + cause.getMessage());
}
@Override
public AjaxResult omsUnfreezeInventory(OmsFreezeInventoryParam param) {
//返回错误而非静默成功:解冻失败让OMS取消/删除感知(解冻可重试,WMS解冻幂等)
return AjaxResult.error("WMS释放冻结库存失败(服务降级):" + cause.getMessage());
}
@Override
public AjaxResult omsRelockInventory(OmsFreezeInventoryParam param) {
//返回错误而非静默成功:编辑重锁是原子操作,失败必须让OMS编辑整体回滚
return AjaxResult.error("WMS编辑重锁库存失败(服务降级):" + cause.getMessage());
}
@Override
public AjaxResult hasActiveFreezeByWarehouse(Long warehouseId) {
//返回错误(fail-closed):模式切换守卫校验不可用时拒绝切换,避免在途冻结单语义错乱
return AjaxResult.error("WMS冻结记录校验服务不可用:" + cause.getMessage());
}
@Override
public AjaxResult omsInOrderAddDetail(List<InMaterialDetail> inMaterialDetails) {
//返回错误而非静默成功:明细保存(含自动审核)失败必须让OMS感知并回滚下发标记
@@ -110,6 +143,12 @@ public class RemoteWmsFallbackFactory implements FallbackFactory<WmsServiceFeign
return AjaxResult.error("WMS取消下发清理失败:" + cause.getMessage());
}
@Override
public AjaxResult omsReverseInOrder(StockInOrder stockInOrder) {
//返回错误而非静默成功,让OMS侧反审能感知回退失败并整体回滚(反审可重试,WMS回退幂等)
return AjaxResult.error("WMS入库单反审回退失败(服务降级):" + cause.getMessage());
}
@Override
public TableDataInfo getMaterialBaseInfoList(MaterialInfoDTO materialInfoDTO) {
TableDataInfo tableDataInfo = new TableDataInfo();
@@ -35,4 +35,7 @@ public class WarehousePO implements Serializable {
@Excel(name = "是否启用: 1-否 2-是")
private Integer isEnable;
@ApiModelProperty("库存锁定模式: 1-下单冻结 2-分配库存冻结")
private Integer stockLockMode;
}
@@ -27,7 +27,7 @@ import java.util.List;
import java.util.Map;
// 目标地址由 nacos 配置项 feign.url.mhd-third-party-service 指定,未配置时按服务名走服务发现
@FeignClient(value = "mhd-third-party-service",url = "http://10.102.192.31:8012", configuration = FeignAutoConfiguration.class)
@FeignClient(value = "mhd-third-party-service",url = "http://10.33.0.129:8012", configuration = FeignAutoConfiguration.class)
public interface ThirdPartyServiceFeign {
//查询当前组织所有三方接口信息
@@ -12,7 +12,9 @@ import com.mhd.basic.domain.warehouse.repository.todo.WarehouseDO;
import com.mhd.basic.domain.warehouse.service.WarehouseDomainService;
import com.mhd.common.core.enums.DictCode;
import com.mhd.common.core.exception.ServiceException;
import com.mhd.common.core.web.domain.AjaxResult;
import com.mhd.common.security.utils.SecurityUtils;
import com.mhd.system.api.WmsServiceFeign;
import com.mhd.system.api.domain.SysDictData;
import com.mhd.system.api.model.LoginUser;
import com.mhd.system.service.ISysDictTypeService;
@@ -43,6 +45,8 @@ public class WarehouseApplicationService {
private ISysDictTypeService dictTypeService;
@Autowired
private IAssociationWarehouseService associationWarehouseService;
@Autowired
private WmsServiceFeign wmsServiceFeign;
/**
@@ -239,12 +243,78 @@ public class WarehouseApplicationService {
/**
* 修改仓库
*/
/**
* @description 查询仓库库存锁定模式(OMS下单冻结库存/防超卖功能的仓库级开关)
* 1-下单冻结:OMS出库业务单「下单」即冻结WMS库存(可用-、冻结+,库存数量不变)
* 2-分配库存冻结:现有模式,WMS「分配库存」时才冻结
* 仓库不存在或未配置(null)时返回默认值 2(与代码侧降级口径一致)
* @author gen
* @date 2026-09-12
*/
public Integer getStockLockMode(Long warehouseId) {
WarehousePO warehousePO = warehouseDomainService.getInfo(warehouseId);
return warehousePO == null ? 2
: cn.hutool.core.util.ObjectUtil.defaultIfNull(warehousePO.getStockLockMode(), 2);
}
public Boolean update(WarehouseDO warehouseDO) {
// 库存锁定模式切换守卫:模式发生变化时,存在在途冻结单(OMS下单冻结未释放)则禁止切换,
// 否则在途单的解冻/承接逻辑会按新模式错乱。fail-closed:校验服务不可用时拒绝切换
if (warehouseDO.getWarehouseId() != null && warehouseDO.getStockLockMode() != null) {
WarehousePO existingPO = warehouseDomainService.getInfo(warehouseDO.getWarehouseId());
if (existingPO != null) {
checkStockLockModeSwitchable(warehouseDO.getWarehouseId(), warehouseDO.getStockLockMode(), existingPO);
}
}
//设置数据字典键值
setDataDict(warehouseDO);
return warehouseDomainService.update(warehouseDO);
}
/**
* @description 单独切换仓库库存锁定模式(不改动仓库其他信息)
* 1-下单冻结:OMS出库业务单「下单」即冻结WMS库存
* 2-分配库存冻结(默认):WMS「分配库存」时才冻结
* 切换守卫与通用编辑一致:模式变化且存在在途冻结单时禁止切换,fail-closed
* @author gen
* @date 2026-09-14
*/
public Boolean switchStockLockMode(Long warehouseId, Integer stockLockMode) {
if (warehouseId == null) {
throw new ServiceException("仓库ID不能为空");
}
if (stockLockMode == null || (stockLockMode.intValue() != 1 && stockLockMode.intValue() != 2)) {
throw new ServiceException("库存锁定模式不合法,仅支持 1-下单冻结 2-分配库存冻结");
}
WarehousePO existingPO = warehouseDomainService.getInfo(warehouseId);
if (existingPO == null) {
throw new ServiceException("仓库不存在");
}
checkStockLockModeSwitchable(warehouseId, stockLockMode, existingPO);
return warehouseDomainService.switchStockLockMode(warehouseId, stockLockMode);
}
/**
* @description 库存锁定模式切换守卫:模式未变化直接放行;模式变化时查询WMS是否存在
* 在途下单冻结单(freeze_status 1/2),存在则禁止切换。
* fail-closed:WMS服务不可用/返回异常时一律拒绝切换,防止切换后冻结语义错乱
* @author gen
* @date 2026-09-14
*/
private void checkStockLockModeSwitchable(Long warehouseId, Integer newMode, WarehousePO existingPO) {
if (newMode.compareTo(cn.hutool.core.util.ObjectUtil.defaultIfNull(existingPO.getStockLockMode(), 2)) == 0) {
return;
}
AjaxResult freezeCheck = wmsServiceFeign.hasActiveFreezeByWarehouse(warehouseId);
if (freezeCheck == null || !"200".equals(String.valueOf(freezeCheck.get("code")))) {
throw new ServiceException("库存锁定模式切换校验失败,请稍后重试:"
+ (freezeCheck == null ? "WMS服务不可用" : freezeCheck.get("msg")));
}
if (Boolean.TRUE.equals(freezeCheck.get("data"))) {
throw new ServiceException("该仓库存在下单冻结中的出库单,不可切换库存锁定模式");
}
}
/**
* @description 设置数据字典键值
* @author ZhouGY
@@ -38,13 +38,13 @@ public class XxlJobOpenApiUtil {
try {
Map<String, Object> formMap = BeanUtil.beanToMap(addReq);
HttpResponse response = HttpRequest.post(url)
.header("Cookie", cookie)
.header("Authorization", cookie)
.form(formMap)
.execute();
int status = response.getStatus();
log.info("[XxlJob] addJob status:{}", status);
// 新增成功200重定向到列表页
return status == 200;
return status == 200 || status == 302;
} catch (Exception e) {
log.error("[XxlJob] addJob异常", e);
return false;
@@ -128,10 +128,10 @@ public class XxlJobOpenApiUtil {
String url = adminUrl + "/jobinfo/pause";
try {
HttpResponse response = HttpRequest.post(url)
.header("Cookie", cookie)
.header("Authorization", cookie)
.form("id", jobId)
.execute();
return response.getStatus() == 200;
return response.getStatus() == 200 || response.getStatus() == 302;
} catch (Exception e) {
log.error("[XxlJob] pauseJob jobId:{}异常", jobId, e);
return false;
@@ -150,10 +150,10 @@ public class XxlJobOpenApiUtil {
String url = adminUrl + "/jobinfo/start";
try {
HttpResponse response = HttpRequest.post(url)
.header("Cookie", cookie)
.header("Authorization", cookie)
.form("id", jobId)
.execute();
return response.getStatus() == 200;
return response.getStatus() == 200 || response.getStatus() == 302;
} catch (Exception e) {
log.error("[XxlJob] startJob jobId:{}异常", jobId, e);
return false;
@@ -172,10 +172,10 @@ public class XxlJobOpenApiUtil {
String url = adminUrl + "/jobinfo/remove";
try {
HttpResponse response = HttpRequest.post(url)
.header("Cookie", cookie)
.header("Authorization", cookie)
.form("id", jobId)
.execute();
return response.getStatus() == 200;
return response.getStatus() == 200 || response.getStatus() == 302;
} catch (Exception e) {
log.error("[XxlJob] removeJob jobId:{}异常", jobId, e);
return false;
@@ -130,6 +130,10 @@ public class Warehouse extends BaseVOEntity{
@Excel(name = "作业模式")
private String workMode;
@ApiModelProperty("库存锁定模式: 1-下单冻结 2-分配库存冻结")
@Excel(name = "库存锁定模式: 1-下单冻结 2-分配库存冻结")
private Integer stockLockMode;
@ApiModelProperty("订单号生成规则0日期累加1历史累加")
@Excel(name = "单号生成规则")
private String orderNoGenerateRule;
@@ -132,6 +132,10 @@ public class WarehousePO extends BaseVOEntity{
@Excel(name = "作业模式")
private String workMode;
@ApiModelProperty("库存锁定模式: 1-下单冻结 2-分配库存冻结")
@Excel(name = "库存锁定模式: 1-下单冻结 2-分配库存冻结")
private Integer stockLockMode;
@ApiModelProperty("订单号生成规则")
@Excel(name = "单号生成规则")
@@ -140,6 +140,10 @@ public class WarehouseDO extends BaseVOEntity{
@Excel(name = "作业模式")
private String workMode;
@ApiModelProperty("库存锁定模式: 1-下单冻结 2-分配库存冻结")
@Excel(name = "库存锁定模式: 1-下单冻结 2-分配库存冻结")
private Integer stockLockMode;
@ApiModelProperty("订单号生成规则")
@Excel(name = "单号生成规则")
@@ -142,6 +142,29 @@ public class WarehouseDomainService {
.eq(Warehouse::getDelFlag, 1));
}
/**
* @description 单独切换仓库库存锁定模式(仅更新 STOCK_LOCK_MODE 与审计字段,不触碰仓库其他数据)
* 1-下单冻结:OMS出库业务单「下单」即冻结WMS库存
* 2-分配库存冻结(默认):WMS「分配库存」时才冻结
* @author gen
* @date 2026-09-14
*/
public Boolean switchStockLockMode(Long warehouseId, Integer stockLockMode) {
Long updateBy = null;
try {
LoginUser loginUser = SecurityUtils.getLoginUser();
updateBy = loginUser == null ? null : loginUser.getUserid();
} catch (Exception ignore) {
}
Long finalUpdateBy = updateBy;
return warehouseService.update(new UpdateWrapper<Warehouse>().lambda()
.set(Warehouse::getStockLockMode, stockLockMode)
.set(finalUpdateBy != null, Warehouse::getUpdateBy, finalUpdateBy)
.set(Warehouse::getUpdateTime, new Date())
.eq(Warehouse::getWarehouseId, warehouseId)
.eq(Warehouse::getDelFlag, 1));
}
/**
* @description 获取指定仓库下的所有库区及库区下所有库位
* @author ZhouGY
@@ -145,6 +145,10 @@ public class WarehouseDTO extends BaseVOEntity{
@Excel(name = "作业模式")
private String workMode;
@ApiModelProperty("库存锁定模式: 1-下单冻结 2-分配库存冻结")
@Excel(name = "库存锁定模式: 1-下单冻结 2-分配库存冻结")
private Integer stockLockMode;
@ApiModelProperty("订单号生成规则")
@Excel(name = "单号生成规则")
@@ -104,6 +104,33 @@ public class WarehouseApi extends BaseController{
return AjaxResult.success(warehouseApplicationService.getInfo(warehouseId));
}
/**
* 查询仓库库存锁定模式OMS下单冻结库存/防超卖功能的仓库级开关
* 1-下单冻结OMS出库业务单下单即冻结WMS库存可用-冻结+库存数量不变
* 分配不动数量编辑=原子重锁取消=释放交接=库存-冻结-
* 2-分配库存冻结默认现有行为WMS分配库存时才 可用-冻结+
* 仓库不存在或未配置时返回 2 OMS/WMS 代码侧降级口径一致
*/
@ApiOperation("查询仓库库存锁定模式: 1-下单冻结 2-分配库存冻结")
@GetMapping(value = "/getStockLockMode/{warehouseId}")
public AjaxResult getStockLockMode(@PathVariable("warehouseId") Long warehouseId)
{
return AjaxResult.success(warehouseApplicationService.getStockLockMode(warehouseId));
}
/**
* 单独切换仓库库存锁定模式不改动仓库其他信息
* 模式变化且该仓库存在在途下单冻结出库单freeze_status 1/2时拒绝切换
* WMS校验服务不可用时同样拒绝fail-closed
*/
@ApiOperation("切换仓库库存锁定模式: 1-下单冻结 2-分配库存冻结")
@PostMapping(value = "/switchStockLockMode/{warehouseId}/{stockLockMode}")
public AjaxResult switchStockLockMode(@PathVariable("warehouseId") Long warehouseId,
@PathVariable("stockLockMode") Integer stockLockMode)
{
return toAjax(warehouseApplicationService.switchStockLockMode(warehouseId, stockLockMode));
}
@ApiOperation("批量作废仓库")
@GetMapping("/nullifyByIds/{warehouseIds}")
@@ -13,16 +13,16 @@ spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
# server-addr: 10.33.0.129:6010
# server-addr: 127.0.0.1:8848
server-addr: 10.33.0.129:6010
# server-addr: 10.102.192.31:6848
username: nacos
password: manhuoda@2023
#线上正式环境
# server-addr: 10.102.192.105:6848
config:
server-addr: 127.0.0.1:8848
# server-addr: 10.33.0.129:6010
# server-addr: 127.0.0.1:8848
server-addr: 10.33.0.129:6010
# server-addr: 10.102.192.31:6848
username: nacos
password: manhuoda@2023
@@ -43,6 +43,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="orderNoGenerateRule" column="ORDER_NO_GENERATE_RULE" />
<result property="inOrderNoPrefix" column="IN_ORDER_NO_PREFIX" />
<result property="outOrderNoPrefix" column="OUT_ORDER_NO_PREFIX" />
<result property="stockLockMode" column="STOCK_LOCK_MODE" />
</resultMap>
@@ -51,7 +52,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
warehouse_type, warehouse_type_name, warehouse_structure, warehouse_nature, warm_layer_code, warm_layer_name,
director_name, director_phone, region_code, region_name, detail_address, longitude, latitude, building_region,
usage_region, remark, opening_up, nullify, create_time, create_by, create_by_name, update_time, update_by,
update_by_name, del_flag, IS_ENABLE ,WORK_MODE,TASK_MODE,SAMPLE_OUT_ORDER_NO_PREFIX,ORDER_NO_GENERATE_RULE,IN_ORDER_NO_PREFIX,
update_by_name, del_flag, IS_ENABLE ,WORK_MODE,TASK_MODE,STOCK_LOCK_MODE,SAMPLE_OUT_ORDER_NO_PREFIX,ORDER_NO_GENERATE_RULE,IN_ORDER_NO_PREFIX,
OUT_ORDER_NO_PREFIX
from warehouse
</sql>
@@ -18,14 +18,14 @@ spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
# server-addr: 10.33.0.129:6010
# server-addr: 127.0.0.1:8848
server-addr: 10.33.0.129:6010
# server-addr: 10.102.192.31:6848
username: nacos
password: manhuoda@2023
config:
server-addr: 127.0.0.1:8848
# server-addr: 10.33.0.129:6010
# server-addr: 127.0.0.1:8848
server-addr: 10.33.0.129:6010
# server-addr: 10.102.192.31:6848
username: nacos
password: manhuoda@2023
@@ -33,6 +33,7 @@ import com.mhd.bms.infrastructure.constant.BillLogsConstants;
import com.mhd.bms.interfaces.assembler.billManage.BillManageAssembler;
import com.mhd.bms.interfaces.dto.billManage.BillDetailDTO;
import com.mhd.bms.interfaces.dto.billManage.BillManageDTO;
import com.mhd.bms.interfaces.dto.billManage.CustomerMonthBillVO;
import com.mhd.bms.interfaces.dto.billingStatement.BillingStatementDTO;
import com.mhd.common.core.exception.ServiceException;
import com.mhd.common.core.exception.digitalLogisticsException.DigitalLogisticsException;
@@ -138,6 +139,74 @@ public class BillManageApplicationService {
String billNumber = billManagePO.getBillNumber(); // 获取账单编号
// 原有 - 前端列表用不变
List<BillDetail> billDetails = billDetailMapper.selectList(new LambdaQueryWrapper<BillDetail>().eq(BillDetail::getBillNumber, billNumber));
BigDecimal totalTaxAmount = billDetails.stream().map(BillDetail::getTaxAmount).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal totalTaxFreeFee = billDetails.stream().map(BillDetail::getTaxFreeFee).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add);
billManagePO.setTaxAmount(totalTaxAmount);
billManagePO.setTaxFreeFee(totalTaxFreeFee);
billManagePO.setBillDetailList(billDetails);
// 新增 - 导出用LEFT JOIN billing_statement 拿到费用科目
List<BillDetailPO> billDetailPOs = billDetailMapper.getDetailsByBillManageId(billManagePO.getBillManageId());
billManagePO.setBillDetailPOList(billDetailPOs);
}
return billManagePOS;
}
public List<BillManagePO> customerMonthlyReport(BillManageDO billManageDO) {
LoginUser loginUser = SecurityUtils.getLoginUser();
if (loginUser != null && loginUser.getUserPo() != null) {
Long userOrganizationId = loginUser.getUserPo().getOrganizationId();
Long frontendOrganizationId = billManageDO.getOrganizationId(); // 前端传递的 organizationId
// 当前登录用户的 organizationId 2827 可查全部组织数据其他组织只能查自己
if (userOrganizationId != null && userOrganizationId == 2827L) {
// 南光组织organizationId=2827可以查询所有组织的数据
if (frontendOrganizationId != null) {
// 前端传递了 organizationId包括2827或其他组织使用前端传递的值进行过滤
billManageDO.setOrganizationId(frontendOrganizationId);
billManageDO.setTopOrganizationId(null); // 明确设置为 null避免使用 topOrganizationId 条件
} else {
// 前端没有传递 organizationId清空组织过滤条件查询所有组织的数据
billManageDO.setOrganizationId(null);
billManageDO.setTopOrganizationId(null);
}
} else {
// 其他组织设置organizationId只查询当前组织的数据
// 如果前端传递了 organizationId验证是否与当前登录用户的组织ID一致
if (frontendOrganizationId != null && userOrganizationId != null) {
// 前端传递了 organizationId验证是否与当前登录用户的组织ID一致
if (!frontendOrganizationId.equals(userOrganizationId)) {
// 前端传递了其他组织的 organizationId强制使用当前登录用户的组织ID防止越权查询
billManageDO.setOrganizationId(userOrganizationId);
}
// 如果前端传递的 organizationId 与当前登录用户的组织ID一致使用前端传递的值
} else {
// 前端没有传递 organizationId使用当前登录用户的组织ID
if (userOrganizationId != null) {
billManageDO.setOrganizationId(userOrganizationId);
}
}
// 如果 organizationId 不为 null则优先使用 organizationId不使用 topOrganizationId
if (billManageDO.getOrganizationId() == null) {
Long topOrganizationId = loginUser.getUserPo().getTopOrganizationId();
if (topOrganizationId != null && topOrganizationId != 1) {
billManageDO.setTopOrganizationId(topOrganizationId);
}
} else {
// 对于非2827组织必须设置 topOrganizationId=2827
billManageDO.setTopOrganizationId(2827L);
}
}
}
List<BillManagePO> billManagePOS = billManageDomainService.customerMonthlyReport(billManageDO);
for (BillManagePO billManagePO : billManagePOS) {
String billNumber = billManagePO.getBillNumber(); // 获取账单编号
// 原有 - 前端列表用不变
List<BillDetail> billDetails = billDetailMapper.selectList(new LambdaQueryWrapper<BillDetail>().eq(BillDetail::getBillNumber, billNumber));
BigDecimal totalTaxAmount = billDetails.stream().map(BillDetail::getTaxAmount).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal totalTaxFreeFee = billDetails.stream().map(BillDetail::getTaxFreeFee).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add);
billManagePO.setTaxAmount(totalTaxAmount);
billManagePO.setTaxFreeFee(totalTaxFreeFee);
billManagePO.setBillDetailList(billDetails);
// 新增 - 导出用LEFT JOIN billing_statement 拿到费用科目
List<BillDetailPO> billDetailPOs = billDetailMapper.getDetailsByBillManageId(billManagePO.getBillManageId());
@@ -184,6 +253,10 @@ public class BillManageApplicationService {
return billManageDomainService.save(billingStatementDTO);
}
public Boolean saveBillJob(BillingStatementDTO billingStatementDTO) {
return billManageDomainService.saveJob(billingStatementDTO);
}
/**
* 批量删除账单管理
*/
@@ -287,6 +360,28 @@ public class BillManageApplicationService {
return billManageDomainService.confirmBilling(billManageDO);
}
@Transactional
public Boolean confirmBillingJob(BillManageDTO billManageDTO) {
//确认账单未收款状态之前都可以进行确认账单跳过核账流程直接到未收款状态
if(!StringUtils.isNotEmpty(billManageDTO.getBillManageIds())){
throw new ServiceException("账单管理ids不能为空");
}
//校验
Set<String> billManageIds = Stream.of(billManageDTO.getBillManageIds().split(",")).collect(Collectors.toSet());
BillManageDO billManageDO = new BillManageDO();
billManageDO.setBillManageIdSet(billManageIds);
List<BillManagePO> billManageList = billManageDomainService.queryList(billManageDO);
for (BillManagePO billManage : billManageList) {
if(billManage.getBillState() > 2){
throw new ServiceException("选中数据"+ billManage.getBillNumber() + "状态不可操作确认账单");
}
}
billManageDO.setOrganizationId(billManageDTO.getOrganizationId());
billManageDO.setOrganizationName(billManageDTO.getOrganizationName());
billManageDO.setTopOrganizationId(billManageDTO.getTopOrganizationId());
return billManageDomainService.confirmBillingJob(billManageDO);
}
public int editIsInvoice(String billNumber, Integer isInvoice, String invoiceId, String invoiceMakeTime,String applyNumber)
{
@@ -621,6 +716,22 @@ public class BillManageApplicationService {
return originalBill;
}
/**
* 应收账单-红冲
*/
@Transactional
public Boolean redFlushBill(BillManageDTO billManageDTO) {
LoginUser loginUser = SecurityUtils.getLoginUser();
if (ObjectUtil.isNull(loginUser)) {
throw new DigitalLogisticsException(UserError.TIMEOUT);
}
if (StringUtils.isEmpty(billManageDTO.getBillManageIds())) {
throw new ServiceException("账单id不能为空");
}
return billManageDomainService.redFlushBill(billManageDTO);
}
/**
* 应收账单-客户确认
*/
@@ -872,5 +983,66 @@ public class BillManageApplicationService {
return flag && flagTwo;
}
/**
* 客户每月账单分组查询
*/
public List<CustomerMonthBillVO> queryCustomerMonthBillList(BillManageDO billManageDO) {
LoginUser loginUser = SecurityUtils.getLoginUser();
if (loginUser != null && loginUser.getUserPo() != null) {
Long userOrganizationId = loginUser.getUserPo().getOrganizationId();
Long frontendOrganizationId = billManageDO.getOrganizationId();
if (userOrganizationId != null && userOrganizationId == 2827L) {
if (frontendOrganizationId != null) {
billManageDO.setOrganizationId(frontendOrganizationId);
billManageDO.setTopOrganizationId(null);
} else {
billManageDO.setOrganizationId(null);
billManageDO.setTopOrganizationId(null);
}
} else {
if (frontendOrganizationId != null && userOrganizationId != null) {
if (!frontendOrganizationId.equals(userOrganizationId)) {
billManageDO.setOrganizationId(userOrganizationId);
}
} else {
if (userOrganizationId != null) {
billManageDO.setOrganizationId(userOrganizationId);
}
}
if (billManageDO.getOrganizationId() == null) {
Long topOrganizationId = loginUser.getUserPo().getTopOrganizationId();
if (topOrganizationId != null && topOrganizationId != 1) {
billManageDO.setTopOrganizationId(topOrganizationId);
}
} else {
billManageDO.setTopOrganizationId(2827L);
}
}
}
List<CustomerMonthBillVO> list = billManageDomainService.queryCustomerMonthBillList(billManageDO);
// 计算付款状态
for (CustomerMonthBillVO vo : list) {
BigDecimal totalAmount = vo.getTotalBillAmount() != null ? vo.getTotalBillAmount() : BigDecimal.ZERO;
BigDecimal settledAmount = vo.getTotalSettledAmount() != null ? vo.getTotalSettledAmount() : BigDecimal.ZERO;
if (totalAmount.compareTo(BigDecimal.ZERO) == 0) {
vo.setPaymentStatus(3);
vo.setPaymentStatusName("已付款");
} else if (settledAmount.compareTo(BigDecimal.ZERO) == 0) {
vo.setPaymentStatus(1);
vo.setPaymentStatusName("未付款");
} else if (settledAmount.compareTo(totalAmount) >= 0) {
vo.setPaymentStatus(3);
vo.setPaymentStatusName("已付款");
} else {
vo.setPaymentStatus(2);
vo.setPaymentStatusName("部分付款");
}
}
return list;
}
}
@@ -2,12 +2,14 @@ package com.mhd.bms.application.server.receiptManage;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.mhd.bms.domain.billManage.entity.BillManage;
import com.mhd.bms.domain.billManage.service.BillManageDomainService;
import com.mhd.bms.domain.receiptManage.entity.ReceiptManage;
import com.mhd.bms.domain.receiptManage.repository.po.ReceiptManagePO;
import com.mhd.bms.domain.receiptManage.repository.todo.ReceiptManageDO;
import com.mhd.bms.domain.receiptManage.service.ReceiptManageDomainService;
import com.mhd.bms.infrastructure.utils.CheckPasswordUtil;
import com.mhd.bms.interfaces.dto.receiptManage.ReceiptDetailDTO;
import com.mhd.bms.interfaces.dto.receiptManage.ReceiptManageDTO;
import com.mhd.common.core.exception.ServiceException;
import com.mhd.common.core.exception.digitalLogisticsException.DigitalLogisticsException;
@@ -18,10 +20,13 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -147,6 +152,122 @@ public class ReceiptManageApplicationService {
}
/**
* 排队批量收款
* 接收集合按顺序逐笔处理任意一笔收款失败则整体回滚
*/
@Transactional(rollbackFor = Exception.class)
public Boolean queueBatchCollection(List<ReceiptManageDO> receiptManageDOList) {
LoginUser loginUser = SecurityUtils.getLoginUser();
if (ObjectUtil.isNull(loginUser)) {
throw new DigitalLogisticsException(UserError.TIMEOUT);
}
int index = 1;
for (ReceiptManageDO receiptManageDO : receiptManageDOList) {
try {
//根据账单id查询账单当前金额数据替换前端传的账单金额已收金额未收金额等仅保留前端传的收款金额和优惠金额
fillBillAmountFromBillManage(receiptManageDO);
Boolean flag = batchCollection(receiptManageDO);
if (!Boolean.TRUE.equals(flag)) {
throw new ServiceException("收款数据处理失败");
}
} catch (Exception e) {
//自己try捕获的异常也要标记事务回滚确保收款失败时全部回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
throw new ServiceException("" + index + "笔收款失败:" + e.getMessage() + ",本次收款已全部回滚");
}
index++;
}
return true;
}
/**
* 排队批量收款-根据账单id查询账单当前金额替换前端传的账单金额已收金额未收金额剩余未收仅保留前端传的收款金额和优惠金额
*/
private void fillBillAmountFromBillManage(ReceiptManageDO receiptManageDO) {
List<ReceiptDetailDTO> receiptDetailDTOList = receiptManageDO.getReceiptDetailDTOList();
if (null == receiptDetailDTOList || receiptDetailDTOList.size() == 0) {
throw new ServiceException("费用结算明细不能为空");
}
//汇总收款明细的账单id并查询账单当前数据
Set<Long> billManageIdSet = receiptDetailDTOList.stream().map(ReceiptDetailDTO::getBillManageId).filter(ObjectUtil::isNotNull).collect(Collectors.toSet());
if (billManageIdSet.size() == 0) {
throw new ServiceException("收款账单不能为空");
}
LambdaQueryWrapper<BillManage> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.in(BillManage::getBillManageId, billManageIdSet);
queryWrapper.eq(BillManage::getDelFlag, 1);
List<BillManage> billManageList = billManageDomainService.queryListLambda(queryWrapper);
if (null == billManageList || billManageList.size() == 0) {
throw new ServiceException("账单信息未找到");
}
Map<Long, BillManage> billManageMap = billManageList.stream().collect(Collectors.toMap(BillManage::getBillManageId, billManage -> billManage, (oldValue, newValue) -> oldValue));
//汇总账单金额已收金额未收金额收款金额优惠金额
BigDecimal billAmount = BigDecimal.ZERO;
BigDecimal billAmountSettlement = BigDecimal.ZERO;
BigDecimal billAmountUnsettled = BigDecimal.ZERO;
BigDecimal collectedAmount = BigDecimal.ZERO;
BigDecimal collectedDiscount = BigDecimal.ZERO;
for (ReceiptDetailDTO receiptDetailDTO : receiptDetailDTOList) {
BillManage billManage = billManageMap.get(receiptDetailDTO.getBillManageId());
if (null == billManage) {
throw new ServiceException("账单" + receiptDetailDTO.getBillNumber() + "信息未找到,请刷新后重试");
}
//只取前端传的收款金额优惠金额为空按0处理
BigDecimal detailCollectedAmount = null == receiptDetailDTO.getCollectedAmount() ? BigDecimal.ZERO : receiptDetailDTO.getCollectedAmount();
BigDecimal detailCollectedDiscount = null == receiptDetailDTO.getCollectedDiscount() ? BigDecimal.ZERO : receiptDetailDTO.getCollectedDiscount();
//账单当前金额以数据库为准
BigDecimal detailBillAmount = billManage.getBillAmount();
BigDecimal detailBillAmountSettlement = billManage.getBillAmountSettlement();
BigDecimal detailBillAmountUnsettled = billManage.getBillAmountUnsettled();
//剩余未收金额 = 未收金额 - 收款金额 - 优惠金额
BigDecimal detailBillAmountUnsettledResidue = detailBillAmountUnsettled.subtract(detailCollectedAmount).subtract(detailCollectedDiscount);
if (detailBillAmountUnsettledResidue.compareTo(BigDecimal.ZERO) < 0) {
throw new ServiceException("账单" + billManage.getBillNumber() + "未收金额小于本次收款金额与优惠金额之和,无法完成收款,请刷新后重试");
}
//用数据库当前值替换前端传的账单金额已收金额未收金额剩余未收
receiptDetailDTO.setBillAmount(detailBillAmount);
receiptDetailDTO.setBillAmountSettlement(detailBillAmountSettlement);
receiptDetailDTO.setBillAmountUnsettled(detailBillAmountUnsettled);
receiptDetailDTO.setBillAmountUnsettledResidue(detailBillAmountUnsettledResidue);
//累计收款单金额
billAmount = billAmount.add(detailBillAmount);
billAmountSettlement = billAmountSettlement.add(detailBillAmountSettlement);
billAmountUnsettled = billAmountUnsettled.add(detailBillAmountUnsettled);
collectedAmount = collectedAmount.add(detailCollectedAmount);
collectedDiscount = collectedDiscount.add(detailCollectedDiscount);
}
//收款单金额账单金额已收金额未收金额取查询值汇总收款金额优惠金额取前端传值汇总
receiptManageDO.setBillAmount(billAmount);
receiptManageDO.setBillAmountSettlement(billAmountSettlement);
receiptManageDO.setBillAmountUnsettled(billAmountUnsettled);
receiptManageDO.setCollectedAmount(collectedAmount);
receiptManageDO.setCollectedDiscount(collectedDiscount);
//剩余未收 = 未收金额 - 收款金额 - 优惠金额
receiptManageDO.setUncollectedAmount(billAmountUnsettled.subtract(collectedAmount).subtract(collectedDiscount));
}
/**
* 撤销收款
*/
@Transactional
public Boolean cancelCollection(ReceiptManageDO receiptManageDO) {
LoginUser loginUser = SecurityUtils.getLoginUser();
if (ObjectUtil.isNull(loginUser)) {
throw new DigitalLogisticsException(UserError.TIMEOUT);
}
return receiptManageDomainService.cancelCollection(receiptManageDO);
}
/**
* 撤销收款-查询账单涉及的收款单id用于加锁防止并发撤销时收款单金额重算冲突
*/
public Set<Long> queryCancelCollectionReceiptManageIds(String billManageIds) {
return receiptManageDomainService.queryReceiptManageIdsByBillManageIds(billManageIds);
}
/**
* 退款
*/
@@ -575,6 +575,8 @@ public class ReportFormApplicationService {
//收款单
QueryWrapper<ReceiptManage> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("top_organization_id", loginUser.getUserPo().getTopOrganizationId());
//过滤已撤销逻辑删除的收款单
queryWrapper.eq("del_flag", 1);
for (int i = 0; i < formQueryParamsList.size(); i++) {
FormQueryParams formQueryParams = formQueryParamsList.get(i);
FormQueryParams beforeFormQueryParams = null;
@@ -318,6 +318,7 @@ public class SettlementCustomersApplicationService {
contractManageDTO.setBillDays(10);
contractManageDTO.setContractType(1);
AjaxResult ajaxResult = systemServiceFeign.feignSave(contractManageDTO);
System.out.println(ajaxResult+"==================contractManageDTO");
if (ajaxResult != null && "200".equals(String.valueOf(ajaxResult.get("code")))) {
ContractManageFeignDTO contractManageFeignDTO = com.alibaba.fastjson.JSON.parseObject(com.alibaba.fastjson2.JSONObject.toJSONString(ajaxResult.get("data")), ContractManageFeignDTO.class);
Long contractManageId = contractManageFeignDTO.getContractManageId();
@@ -333,6 +334,7 @@ public class SettlementCustomersApplicationService {
contractManageParametersFeign.setOrganizationName(settlementCustomersDO.getOrganizationName());
contractManageParametersFeign.setTopOrganizationId(settlementCustomersDO.getTopOrganizationId());
AjaxResult paramResult = systemServiceFeign.feignSaveContractParameters(contractManageParametersFeign);
System.out.println(paramResult+"==================contractManageParametersFeign");
if (paramResult == null || !"200".equals(String.valueOf(paramResult.get("code")))) {
throw new ServiceException("保存合同参数失败");
}
@@ -47,8 +47,8 @@ public class BillManage extends BaseVOEntity{
@Excel(name = "账单类型", readConverterExp = "1=-应收,2-应付")
private Integer billType;
@ApiModelProperty("账单状态(1-未确认,2-对账中,3-未收款/付款,4-部分收款/付款,5-已收款/付款,6-已作废,7-已退款)")
@Excel(name = "账单状态", readConverterExp = "1=-未确认,2-对账中,3-未收款/付款,4-部分收款/付款,5-已收款/付款,6-已作废,7-已退款")
@ApiModelProperty("账单状态(1-未确认,2-对账中,3-未收款/付款,4-部分收款/付款,5-已收款/付款,6-已作废,7-已退款,8-红冲")
@Excel(name = "账单状态", readConverterExp = "1=-未确认,2-对账中,3-未收款/付款,4-部分收款/付款,5-已收款/付款,6-已作废,7-已退款,8-红冲")
private Integer billState;
@ApiModelProperty("账单流程节点(1-发起对账,2-客户确认,3-财务审核,4-收款/付款)")
@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.extension.service.IService;
import com.mhd.bms.domain.billManage.entity.BillManage;
import com.mhd.bms.domain.billManage.repository.po.BillManagePO;
import com.mhd.bms.domain.billManage.repository.todo.BillManageDO;
import com.mhd.bms.interfaces.dto.billManage.CustomerMonthBillVO;
import java.util.List;
@@ -20,6 +21,14 @@ public interface IBillManageService extends IService<BillManage>
*/
public List<BillManagePO> queryList(BillManageDO billManageDO);
public List<BillManagePO> customerMonthlyReport(BillManageDO billManageDO);
/**
* 客户每月账单分组查询
*/
public List<CustomerMonthBillVO> queryCustomerMonthBillList(BillManageDO billManageDO);
/**
* 新增账单管理
*/
@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.mhd.bms.domain.billManage.entity.BillManage;
import com.mhd.bms.domain.billManage.repository.po.BillManagePO;
import com.mhd.bms.domain.billManage.repository.todo.BillManageDO;
import com.mhd.bms.interfaces.dto.billManage.CustomerMonthBillVO;
import org.apache.ibatis.annotations.Param;
/**
* 账单管理Mapper接口
@@ -19,6 +20,13 @@ public interface BillManageMapper extends BaseMapper<BillManage>
*/
public List<BillManagePO> queryList(@Param("billManageDO") BillManageDO billManageDO);
public List<BillManagePO> customerMonthlyReport(@Param("billManageDO") BillManageDO billManageDO);
/**
* 客户每月账单分组查询
*/
public List<CustomerMonthBillVO> queryCustomerMonthBillList(@Param("billManageDO") BillManageDO billManageDO);
String getInvoiceItemName(@Param("invoiceItemId") String invoiceItemId);
String getInvoiceItemNcCode(@Param("invoiceItemId") String invoiceItemId);
@@ -31,6 +31,7 @@ import com.mhd.bms.domain.settlementCustomers.entity.SettlementCustomers;
import com.mhd.bms.domain.settlementCustomers.repository.facade.ISettlementCustomersService;
import com.mhd.bms.interfaces.dto.billManage.BillDetailDTO;
import com.mhd.bms.interfaces.dto.billManage.BillManageDTO;
import com.mhd.bms.interfaces.dto.billManage.CustomerMonthBillVO;
import com.mhd.common.core.domain.dto.ReconciliationDTO;
import com.mhd.common.core.domain.dto.nc.*;
import com.mhd.common.core.domain.entity.R;
@@ -126,6 +127,14 @@ public class BillManageImpl extends ServiceImpl<BillManageMapper, BillManage> im
return billManageMapper.queryList(billManageDO);
}
@Override
public List<BillManagePO> customerMonthlyReport(BillManageDO billManageDO)
{
return billManageMapper.customerMonthlyReport(billManageDO);
}
/**
* 新增账单管理
*/
@@ -1504,4 +1513,9 @@ public class BillManageImpl extends ServiceImpl<BillManageMapper, BillManage> im
return 0;
}
@Override
public List<CustomerMonthBillVO> queryCustomerMonthBillList(BillManageDO billManageDO) {
return billManageMapper.queryCustomerMonthBillList(billManageDO);
}
}
@@ -2,6 +2,7 @@ package com.mhd.bms.domain.billManage.repository.po;
import com.mhd.bms.domain.billManage.entity.BillDetail;
import com.mhd.bms.domain.billingStatement.repository.po.BillingStatementPO;
import com.mhd.bms.domain.receiptManage.entity.ReceiptDetail;
import com.mhd.common.core.annotation.Excel;
import lombok.Data;
import java.util.Date;
@@ -52,8 +53,8 @@ public class BillManagePO extends BaseVOEntity{
@Excel(name = "账单类型", readConverterExp = "1=-应收,2-应付")
private Integer billType;
@ApiModelProperty("账单状态(1-未确认,2-对账中,3-未收款/付款,4-部分收款/付款,5-已收款/付款,6-已作废,7-已退款)")
@Excel(name = "账单状态", readConverterExp = "1=-未确认,2-对账中,3-未收款/付款,4-部分收款/付款,5-已收款/付款,6-已作废,7-已退款")
@ApiModelProperty("账单状态(1-未确认,2-对账中,3-未收款/付款,4-部分收款/付款,5-已收款/付款,6-已作废,7-已退款,8-红冲")
@Excel(name = "账单状态", readConverterExp = "1=-未确认,2-对账中,3-未收款/付款,4-部分收款/付款,5-已收款/付款,6-已作废,7-已退款,8-红冲")
private Integer billState;
@ApiModelProperty("账单流程节点(1-发起对账,2-客户确认,3-财务审核,4-收款/付款)")
@@ -262,9 +263,12 @@ public class BillManagePO extends BaseVOEntity{
@ApiModelProperty("收款帐户名称")
private String paymentAccountName;
private List<BillDetail> billDetailList;
private List<ReceiptDetail> receiptDetailList;
@ApiModelProperty("最近收款时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@Excel(name = "最近收款时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date collectionTime;
@ApiModelProperty("合同编码")
private String contractNumbers;
}
@@ -47,8 +47,8 @@ public class BillManageDO extends BaseVOEntity{
@Excel(name = "账单类型", readConverterExp = "1=-应收,2-应付")
private Integer billType;
@ApiModelProperty("账单状态(1-未确认,2-对账中,3-未收款/付款,4-部分收款/付款,5-已收款/付款,6-已作废,7-已退款)")
@Excel(name = "账单状态", readConverterExp = "1=-未确认,2-对账中,3-未收款/付款,4-部分收款/付款,5-已收款/付款,6-已作废,7-已退款")
@ApiModelProperty("账单状态(1-未确认,2-对账中,3-未收款/付款,4-部分收款/付款,5-已收款/付款,6-已作废,7-已退款,8-红冲")
@Excel(name = "账单状态", readConverterExp = "1=-未确认,2-对账中,3-未收款/付款,4-部分收款/付款,5-已收款/付款,6-已作废,7-已退款,8-红冲")
private Integer billState;
@ApiModelProperty("账单流程节点(1-发起对账,2-客户确认,3-财务审核,4-收款/付款)")
@@ -266,4 +266,7 @@ public class BillManageDO extends BaseVOEntity{
private String paymentAccountName;
@ApiModelProperty("业务员ID")
private String salesmanId;
@ApiModelProperty("账单月份(yyyy-MM,按创建时间月份筛选,用于客户月账单分组查询)")
private String billMonth;
}
@@ -12,7 +12,9 @@ import com.mhd.bms.domain.billManage.repository.facade.IBillManageService;
import com.mhd.bms.domain.billManage.repository.po.BillManagePO;
import com.mhd.bms.domain.billManage.repository.todo.BillDetailDO;
import com.mhd.bms.domain.billManage.repository.todo.BillManageDO;
import com.mhd.bms.interfaces.dto.billManage.CustomerMonthBillVO;
import com.mhd.bms.domain.billOperationLog.entity.BillOperationLog;
import com.mhd.bms.domain.billOperationLog.repository.po.BillOperationLogPO;
import com.mhd.bms.domain.billOperationLog.repository.todo.BillOperationLogDO;
import com.mhd.bms.domain.billOperationLog.service.BillOperationLogDomainService;
import com.mhd.bms.domain.billingStatement.entity.BillingStatement;
@@ -44,6 +46,7 @@ import com.mhd.common.security.utils.SecurityUtils;
import com.mhd.system.api.SystemServiceFeign;
import com.mhd.system.api.model.LoginUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -85,6 +88,10 @@ public class BillManageDomainService {
return billManageService.queryList(billManageDO);
}
public List<BillManagePO> customerMonthlyReport(BillManageDO billManageDO) {
return billManageService.customerMonthlyReport(billManageDO);
}
/**
* 新增账单管理
@@ -233,6 +240,124 @@ public class BillManageDomainService {
return flag && flagTwo && three;
}
public Boolean saveBillJob(List<BillingStatementPO> billingStatementPOS,String billingRemark,Date billCreateTime,BillingStatementDTO billingStatementDTO) {
// LoginUser loginUser = SecurityUtils.getLoginUser();
// if (ObjectUtil.isNull(loginUser)) {
//// throw new DigitalLogisticsException(UserError.TIMEOUT);
// }
BigDecimal taxAmount = billingStatementDTO.getTaxAmount(); // 获取税额
Double taxRate = billingStatementDTO.getTaxRate(); // 获取税率
BigDecimal taxFreeFee = billingStatementDTO.getTaxFreeFee(); // 获取税免金额
String settlementCurrency = billingStatementDTO.getSettlementCurrency(); // 获取结算币种
BillManage billManage = new BillManage();
billManage.setTaxAmount(taxAmount);
billManage.setTaxRate(taxRate);
billManage.setTaxFreeFee(taxFreeFee);
billManage.setSettlementCurrency(settlementCurrency);
// billManage.setCreateBy(loginUser.getUserPo().getUserId());
// billManage.setCreateByName(loginUser.getUserPo().getUserName());
billManage.setCreateTime(billCreateTime==null ? new Date():billCreateTime);
billManage.setDelFlag(1);
billManage.setBillNumber(OrderSequence.getOrderCode("ZD"));
billManage.setTopOrganizationId(billingStatementDTO.getTopOrganizationId());
billManage.setOrganizationId(billingStatementDTO.getOrganizationId());
billManage.setOrganizationName(billingStatementDTO.getOrganizationName());
billManage.setBillType(billingStatementPOS.get(0).getAccountExpenseType());
billManage.setBelongModule(billingStatementPOS.get(0).getBelongModule());
billManage.setBelongModuleCode(billingStatementPOS.get(0).getBelongModuleCode());
billManage.setSettlementCustomersId(billingStatementPOS.get(0).getSettlementCustomersId());
billManage.setSettlementEntity(billingStatementPOS.get(0).getSettlementEntity());
billManage.setSettlementCustomersCode(billingStatementPOS.get(0).getSettlementCustomersCode());
billManage.setSalesmanId(billingStatementPOS.get(0).getSalesmanId());
billManage.setSalesmanName(billingStatementPOS.get(0).getSalesmanName());
billManage.setPaymentAccountId(billingStatementPOS.get(0).getPaymentAccountId());
billManage.setPaymentAccountName(billingStatementPOS.get(0).getPaymentAccountName());
billManage.setBillRemark(billingRemark);
//查询结算对象信息
SettlementCustomersPO settlementCustomersPO = settlementCustomersDomainService.getInfo(billingStatementPOS.get(0).getSettlementCustomersId());
if(null == settlementCustomersPO){
throw new ServiceException("结算对象信息未找到");
}
billManage.setSettlementMethod(settlementCustomersPO.getSettlementMethod());
billManage.setSettlementMethodCode(settlementCustomersPO.getSettlementMethodCode());
// if(settlementCustomersPO.getMainRole()==1){
// billManage.setBillType(1);
// }else {
// billManage.setBillType(2);
// }
//计算所有流水列表中的最早周期开始时间和晚周期结束时间
List<Date> cycleBeginTimeSet = billingStatementPOS.stream().map(BillingStatementPO::getCycleBeginTime).collect(Collectors.toList());
List<Date> cycleEndTimeSet = billingStatementPOS.stream().map(BillingStatementPO::getCycleEndTime).collect(Collectors.toList());
cycleBeginTimeSet.removeIf(Objects::isNull);
cycleEndTimeSet.removeIf(Objects::isNull);
if(cycleBeginTimeSet.size()>0){
billManage.setCycleBeginTime(Collections.min(cycleBeginTimeSet));
}
if(cycleEndTimeSet.size()>0){
billManage.setCycleEndTime(Collections.max(cycleEndTimeSet));
}
//将所有的流水金额相加
billManage.setBillTotalAmount(billingStatementPOS.stream().map(BillingStatementPO::getBillingAmount).reduce(BigDecimal.ZERO, BigDecimal::add));
billManage.setBillAmount(billManage.getBillTotalAmount());
billManage.setBillAmountUnsettled(billManage.getBillTotalAmount());
boolean flag = billManageService.save(billManage);
billingStatementDTO.setBillManageId(billManage.getBillManageId());
List<BillDetail> billDetailList = new ArrayList<>();
for (BillingStatementPO billingStatementPO : billingStatementPOS) {
//生成对账单详情
BillDetail billDetail = new BillDetail();
billDetail.setTopOrganizationId(billingStatementPO.getTopOrganizationId());
billDetail.setOrganizationId(billingStatementPO.getOrganizationId());
billDetail.setOrganizationName(billingStatementPO.getOrganizationName());
billDetail.setCreateBy(billManage.getCreateBy());
billDetail.setCreateByName(billManage.getCreateByName());
billDetail.setCreateTime(billManage.getCreateTime());
billDetail.setBillManageId(billManage.getBillManageId());
billDetail.setBillNumber(billManage.getBillNumber());
billDetail.setBillingStatementId(billingStatementPO.getBillingStatementId());
billDetail.setBillingFlow(billingStatementPO.getBillingFlow());
billDetail.setBillingTotalAmount(billingStatementPO.getBillingAmount());
billDetail.setBillingAmount(billingStatementPO.getBillingAmount());
billDetail.setBillType(billingStatementPO.getAccountExpenseType());
billDetail.setTaxAmount(billingStatementPO.getTaxAmount());
billDetail.setTaxRate(billingStatementPO.getTaxRate());
billDetail.setTaxFreeFee(billingStatementPO.getTaxFreeFee());
billDetail.setSettlementCurrency(billingStatementPO.getSettlementCurrency());
billDetail.setFeeType(billingStatementPO.getFeeType());
billDetail.setInvoiceItem(billingStatementPO.getInvoiceItem());
billDetail.setInvoiceItemName(billingStatementPO.getInvoiceItemName());
billDetailList.add(billDetail);
}
boolean flagTwo = billDetailDomainService.insertBatch(billDetailList);
//保存账单操作记录
BillOperationLogDO billOperationLogDO = new BillOperationLogDO();
billOperationLogDO.setBillManageId(billManage.getBillManageId());
billOperationLogDO.setOperationInfo(StringUtils.format(BillLogsConstants.create_bill_text, billManage.getBillNumber(), billManage.getBillTotalAmount()));
billOperationLogDO.setOperationType(1);
billOperationLogDO.setCreateByName(billManage.getCreateByName());
billOperationLogDO.setUpdateByName(billManage.getUpdateByName());
billOperationLogDO.setCreateTime(new Date());
billOperationLogDO.setUpdateTime(new Date());
billOperationLogDO.setDelFlag(1);
billOperationLogDO.setCreateBy(billManage.getCreateBy());
billOperationLogDO.setUpdateBy(billManage.getUpdateBy());
billOperationLogDO.setTopOrganizationId(billManage.getTopOrganizationId());
billOperationLogDO.setOrganizationId(billManage.getOrganizationId());
billOperationLogDO.setOrganizationName(billManage.getOrganizationName());
boolean three = billOperationLogDomainService.saveBillOperationLogJob(billOperationLogDO);
//修改流水记录
/*billingStatementDTO.getBillingStatementList().forEach(billingStatementADDVO -> {
billingStatementMapper.update(null,
new LambdaUpdateWrapper<BillingStatement>().set(BillingStatement::getTaxAmount,billingStatementADDVO.getTaxAmount())
.set(BillingStatement::getTaxRate,billingStatementADDVO.getTaxRate())
.set(BillingStatement::getTaxFreeFee,billingStatementADDVO.getTaxFreeFee())
.set(BillingStatement::getSettlementCurrency,billingStatementADDVO.getSettlementCurrency())
.eq(BillingStatement::getBillingStatementId,billingStatementADDVO.getBillingStatementId()));
});*/
return flag && flagTwo && three;
}
/**
* 保存账单
* @param billingStatementDTO
@@ -296,6 +421,64 @@ public class BillManageDomainService {
return saveBill(distinctList,billingStatementDTO.getBillingRemark(),billingStatementDTO.getBillCreateTime(),billingStatementDTO);
}
public Boolean saveJob(BillingStatementDTO billingStatementDTO) {
LoginUser loginUser = SecurityUtils.getLoginUser();
if (ObjectUtil.isNull(loginUser)) {
// throw new DigitalLogisticsException(UserError.TIMEOUT);
}
//根据前端传的流水id查询计费流水信息列表
Set<String> billingSet = Stream.of(billingStatementDTO.getBillingStatementIds().split(",")).collect(Collectors.toSet());
BillingStatementDO billingStatementDO = new BillingStatementDO();
billingStatementDO.setBillingStatementIdSet(billingSet);
List<BillingStatementPO> billingStatementPOS = billingStatementDomainService.queryList(billingStatementDO);
//获取最新一条的业务员信息
String salesmanId = null;
String salesmanName = null;
if (billingStatementPOS != null && !billingStatementPOS.isEmpty()) {
billingStatementPOS.sort((a, b) -> {
if (a.getCreateTime() == null && b.getCreateTime() == null) return 0;
if (a.getCreateTime() == null) return 1;
if (b.getCreateTime() == null) return -1;
return b.getCreateTime().compareTo(a.getCreateTime());
});
salesmanId = billingStatementPOS.get(0).getSalesmanId();
salesmanName = billingStatementPOS.get(0).getSalesmanName();
}
List<BillingStatementADDVO> billingStatementList = billingStatementDTO.getBillingStatementList();
for (BillingStatementPO billingStatementPO : billingStatementPOS) {
String billingStatementId = billingStatementPO.getBillingStatementId() == null ? "" : String.valueOf(billingStatementPO.getBillingStatementId());
BillingStatementADDVO billingStatementADDVO = billingStatementList.stream().filter(billingStatementADDVO1 -> billingStatementADDVO1.getBillingStatementId().contains(billingStatementId)).findFirst().orElse(null);
billingStatementPO.setTaxAmount(billingStatementADDVO.getTaxAmount()); // 设置税额
billingStatementPO.setTaxRate(billingStatementADDVO.getTaxRate()); // 设置税率
billingStatementPO.setTaxFreeFee(billingStatementADDVO.getTaxFreeFee()); // 设置税免金额
billingStatementPO.setSettlementCurrency(billingStatementADDVO.getSettlementCurrency());//设置结算币种
billingStatementPO.setFeeType(billingStatementADDVO.getFeeType());
billingStatementPO.setInvoiceItem(billingStatementADDVO.getInvoiceItem());
billingStatementPO.setInvoiceItemName(billingStatementADDVO.getInvoiceItemName());
billingStatementPO.setBillingAmount(billingStatementADDVO.getBillingAmount());
billingStatementPO.setBillingStatementIdStr(billingStatementADDVO.getBillingStatementId());
if (salesmanName != null && !salesmanName.isEmpty() && salesmanId != null && !salesmanId.isEmpty()) {
billingStatementPO.setSalesmanId(salesmanId);
billingStatementPO.setSalesmanName(salesmanName);
}
}
List<BillingStatementPO> distinctList = billingStatementPOS.stream()
.filter(Objects::nonNull)
.collect(Collectors.toMap(
BillingStatementPO::getBillingStatementIdStr, // key去重字段
po -> po, // value对象本身
(existing, replacement) -> existing // 重复时保留第一个
))
.values()
.stream().collect(Collectors.toList());
if(billingStatementPOS==null || billingStatementPOS.size()==0){
throw new ServiceException("所选流水信息未找到");
}
return saveBillJob(distinctList,billingStatementDTO.getBillingRemark(),billingStatementDTO.getBillCreateTime(),billingStatementDTO);
}
public List<BillManage> queryListLambda(LambdaQueryWrapper<BillManage> queryWrapper) {
return billManageService.list(queryWrapper);
@@ -378,6 +561,32 @@ public class BillManageDomainService {
return flag && three;
}
public Boolean confirmBillingJob(BillManageDO billManageDO) {
//直接修改状态到未收款3状态
// LoginUser loginUser = SecurityUtils.getLoginUser();
// if (ObjectUtil.isNull(loginUser)) {
// throw new DigitalLogisticsException(UserError.TIMEOUT);
// }
LambdaUpdateWrapper<BillManage> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.in(BillManage::getBillManageId,billManageDO.getBillManageIdSet())
.set(BillManage::getBillStep,4)
.set(BillManage::getBillState,3)
// .set(BillManage::getUpdateBy,loginUser.getUserPo().getUserId())
// .set(BillManage::getUpdateByName,loginUser.getUserPo().getUserName())
.set(BillManage::getUpdateTime,new Date());
boolean flag = billManageService.update(updateWrapper);
//保存账单操作记录
BillOperationLogDO billOperationLogDO = new BillOperationLogDO();
billOperationLogDO.setBillManageIdSet(billManageDO.getBillManageIdSet());
billOperationLogDO.setOperationInfo(BillLogsConstants.confirm_bill_text);
billOperationLogDO.setOperationType(3);
billOperationLogDO.setOrganizationId(billManageDO.getOrganizationId());
billOperationLogDO.setOrganizationName(billManageDO.getOrganizationName());
billOperationLogDO.setTopOrganizationId(billManageDO.getTopOrganizationId());
boolean three = billOperationLogDomainService.saveBillOperationLogBatchJob(billOperationLogDO);
return flag && three;
}
public int editIsInvoice(String billNumber, Integer isInvoice, String invoiceId, String invoiceMakeTime,String applyNumber)
{
@@ -504,6 +713,261 @@ public class BillManageDomainService {
}
/**
* 撤销收款账单恢复为收款为0状态并保存撤销收款操作日志
*/
public Boolean cancelCollectionInfo(List<ReceiptDetail> receiptDetails) {
LoginUser loginUser = SecurityUtils.getLoginUser();
if (ObjectUtil.isNull(loginUser)) {
throw new DigitalLogisticsException(UserError.TIMEOUT);
}
//按账单分组收款明细一个账单可能对应多个收款单
Map<Long, List<ReceiptDetail>> billDetailMap = receiptDetails.stream().collect(Collectors.groupingBy(ReceiptDetail::getBillManageId));
LambdaQueryWrapper<BillManage> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.in(BillManage::getBillManageId,billDetailMap.keySet());
List<BillManage> billManageList = billManageService.list(queryWrapper);
if(billManageList == null || billManageList.size()==0){
throw new ServiceException("账单信息未找到");
}
List<BillManage> billManageListSave = new ArrayList<>();
List<BillOperationLog> billOperationLogSave = new ArrayList<>();
for (BillManage billManage : billManageList) {
List<ReceiptDetail> receiptDetailList = billDetailMap.get(billManage.getBillManageId());
if(null == receiptDetailList || receiptDetailList.size() == 0){
continue;
}
BillManage billManageSave = new BillManage();
billManageSave.setBillManageId(billManage.getBillManageId());
//账单恢复为收款为0状态
billManageSave.setBillAmountSettlement(BigDecimal.ZERO);
billManageSave.setBillAmountUnsettled(billManage.getBillAmount());
billManageSave.setActualReceivedAmount(BigDecimal.ZERO);
billManageSave.setSettlementDiscountsAmount(BigDecimal.ZERO);
billManageSave.setBillState(3);
billManageSave.setUpdateBy(loginUser.getUserPo().getUserId());
billManageSave.setUpdateByName(loginUser.getUserPo().getUserName());
billManageSave.setUpdateTime(new Date());
billManageListSave.add(billManageSave);
//汇总该账单本次撤销的收款金额与收款单号
BigDecimal collectedDiscount = receiptDetailList.stream().map(info -> info.getCollectedDiscount() == null ? BigDecimal.ZERO : info.getCollectedDiscount()).reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal collectedAmount = receiptDetailList.stream().map(info -> info.getCollectedAmount() == null ? BigDecimal.ZERO : info.getCollectedAmount()).reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal all = collectedAmount.add(collectedDiscount);
String collectionNums = receiptDetailList.stream().map(ReceiptDetail::getCollectionNum).filter(StringUtils::isNotEmpty).distinct().collect(Collectors.joining(","));
//保存账单操作记录
BillOperationLog billOperationLog = new BillOperationLog();
billOperationLog.setBillManageId(billManage.getBillManageId());
billOperationLog.setOperationInfo(StringUtils.format(BillLogsConstants.cancel_collection_bill_text,collectionNums,all,collectedAmount,collectedDiscount));
billOperationLog.setOperationType(12);
billOperationLog.setDelFlag(1);
billOperationLogSave.add(billOperationLog);
}
Boolean three = billOperationLogDomainService.insertBatch(billOperationLogSave);
Boolean flag = billManageService.updateBatchById(billManageListSave);
return flag && three;
}
/**
* 应收账单红冲生成金额为负数的红冲账单对冲原账单不操作收款单等数据支持批量
*/
public Boolean redFlushBill(BillManageDTO billManageDTO) {
LoginUser loginUser = SecurityUtils.getLoginUser();
if (ObjectUtil.isNull(loginUser)) {
throw new DigitalLogisticsException(UserError.TIMEOUT);
}
if (StringUtils.isEmpty(billManageDTO.getBillManageIds())) {
throw new ServiceException("账单id不能为空");
}
Set<Long> billManageIdSet = Stream.of(billManageDTO.getBillManageIds().split(",")).filter(StringUtils::isNotEmpty).map(String::trim).map(Long::valueOf).collect(Collectors.toSet());
if (billManageIdSet.size() == 0) {
throw new ServiceException("账单id不能为空");
}
//查询账单信息
LambdaQueryWrapper<BillManage> queryWrapperBill = new LambdaQueryWrapper<>();
queryWrapperBill.in(BillManage::getBillManageId, billManageIdSet);
queryWrapperBill.eq(BillManage::getDelFlag, 1);
List<BillManage> billManageList = billManageService.list(queryWrapperBill);
if (null == billManageList || billManageList.size() == 0) {
throw new ServiceException("账单信息未找到");
}
if (billManageList.size() != billManageIdSet.size()) {
throw new ServiceException("部分账单信息未找到,请刷新后重试");
}
//逐个账单红冲任一账单校验不通过时事务整体回滚
Boolean flag = true;
for (BillManage originalBill : billManageList) {
flag = redFlushSingleBill(originalBill, billManageDTO.getRedFlushRemark(), loginUser) && flag;
}
return flag;
}
/**
* 红冲单个账单生成金额为负数的红冲账单对冲原账单同步镜像计费流水与账单明细
*/
private Boolean redFlushSingleBill(BillManage originalBill, String redFlushRemark, LoginUser loginUser) {
if (ObjectUtil.equal(originalBill.getBillType(), 2)) {
throw new ServiceException("账单" + originalBill.getBillNumber() + "为付款账单,仅支持应收账单红冲");
}
if (!ObjectUtil.equal(originalBill.getBillState(), 5)) {
throw new ServiceException("账单" + originalBill.getBillNumber() + "不是已收款状态,仅已收款状态的账单可以红冲,请刷新后重试");
}
//校验账单是否已红冲避免重复生成红冲账单
BillOperationLogDO redFlushLogQuery = new BillOperationLogDO();
redFlushLogQuery.setBillManageId(originalBill.getBillManageId());
redFlushLogQuery.setOperationType(13);
List<BillOperationLogPO> redFlushLogList = billOperationLogDomainService.queryList(redFlushLogQuery);
if (null != redFlushLogList && redFlushLogList.stream().anyMatch(info -> ObjectUtil.equal(info.getDelFlag(), 1))) {
throw new ServiceException("账单" + originalBill.getBillNumber() + "已红冲,不能重复红冲");
}
//生成红冲账单编号
String billNumber = OrderSequence.getOrderCode("ZD");
BigDecimal redFlushAmount = negateAmount(originalBill.getBillAmount());
//构建红冲账单金额全部取负对冲原账单
BillManage redBill = new BillManage();
BeanUtils.copyProperties(originalBill, redBill);
redBill.setBillManageId(null);
redBill.setBillNumber(billNumber);
redBill.setBillAmount(redFlushAmount);
redBill.setBillTotalAmount(negateAmount(originalBill.getBillTotalAmount()));
redBill.setBillDiscountAmount(negateAmount(originalBill.getBillDiscountAmount()));
redBill.setBillAmountSettlement(negateAmount(originalBill.getBillAmountSettlement()));
redBill.setBillAmountUnsettled(BigDecimal.ZERO);
redBill.setActualReceivedAmount(negateAmount(originalBill.getActualReceivedAmount()));
redBill.setSettlementDiscountsAmount(negateAmount(originalBill.getSettlementDiscountsAmount()));
redBill.setTaxAmount(negateAmount(originalBill.getTaxAmount()));
redBill.setTaxFreeFee(negateAmount(originalBill.getTaxFreeFee()));
//红冲账单状态为红冲已核对无误
redBill.setBillState(8);
redBill.setBillStep(4);
redBill.setReconciliationStatus(4);
redBill.setSettlementTime(new Date());
//重置对账审核开票同步等状态红冲账单不需要再走这些流程
redBill.setBillExpectedAmount(null);
redBill.setReconciliationOpinion(null);
redBill.setVoucherAddress(null);
redBill.setFinancialReviewFlag(null);
redBill.setFinancialReviewOpinion(null);
redBill.setIsInvoice(0);
redBill.setInvoiceId(null);
redBill.setInvoiceMakeTime(null);
redBill.setApplyNumber(null);
redBill.setSynchronousStatus(0);
redBill.setSynchronousTime(null);
redBill.setNcId(null);
redBill.setSkSynchronousStatus(0);
redBill.setSkSynchronousTime(null);
redBill.setSkNcId(null);
//红冲原因写入账单备注
String billRemark = "红冲原账单编号:" + originalBill.getBillNumber();
if (StringUtils.isNotEmpty(redFlushRemark)) {
billRemark = billRemark + ",红冲原因:" + redFlushRemark;
}
redBill.setBillRemark(billRemark);
redBill.setDelFlag(1);
redBill.setCreateBy(loginUser.getUserid());
redBill.setCreateByName(loginUser.getUserPo().getUserName());
redBill.setCreateTime(new Date());
redBill.setUpdateBy(loginUser.getUserid());
redBill.setUpdateByName(loginUser.getUserPo().getUserName());
redBill.setUpdateTime(new Date());
Boolean flag = billManageService.save(redBill);
if (!flag || null == redBill.getBillManageId()) {
throw new ServiceException("生成红冲账单失败");
}
//镜像红冲计费流水金额取负并记录原流水id与新流水的映射
Map<Long, BillingStatementDO> statementMapping = new HashMap<>();
List<BillingStatementPO> originalStatementList = billingStatementDomainService.getDetailsByManageId(originalBill.getBillManageId());
if (null != originalStatementList && originalStatementList.size() > 0) {
for (BillingStatementPO originalStatement : originalStatementList) {
BillingStatementDO redStatement = new BillingStatementDO();
BeanUtils.copyProperties(originalStatement, redStatement);
redStatement.setBillingStatementId(null);
redStatement.setBillManageId(redBill.getBillManageId());
redStatement.setBillingFlow(OrderSequence.getOrderCode("JFLS"));
redStatement.setBillingAmount(negateAmount(originalStatement.getBillingAmount()));
redStatement.setTaxAmount(negateAmount(originalStatement.getTaxAmount()));
redStatement.setTaxFreeFee(negateAmount(originalStatement.getTaxFreeFee()));
redStatement.setBillingState(3);
redStatement.setRedFlushState(1);
redStatement.setRedFlushRemark("红冲自计费流水:" + originalStatement.getBillingFlow());
redStatement.setReviewRemarks(null);
redStatement.setBusinessDocumentId(null);
redStatement.setDelFlag(1);
redStatement.setTopOrganizationId(loginUser.getUserPo().getTopOrganizationId());
redStatement.setOrganizationId(loginUser.getUserPo().getOrganizationId());
redStatement.setOrganizationName(loginUser.getUserPo().getOrganizationName());
redStatement.setCreateBy(loginUser.getUserid());
redStatement.setCreateByName(loginUser.getUserPo().getUserName());
redStatement.setCreateTime(new Date());
redStatement.setUpdateBy(loginUser.getUserid());
redStatement.setUpdateByName(loginUser.getUserPo().getUserName());
redStatement.setUpdateTime(new Date());
redStatement.setBillingStatementId(billingStatementDomainService.insertAndReturnId(redStatement));
statementMapping.put(originalStatement.getBillingStatementId(), redStatement);
}
}
//镜像红冲账单明细金额取负并关联新计费流水
Boolean flagTwo = true;
LambdaQueryWrapper<BillDetail> detailQueryWrapper = new LambdaQueryWrapper<>();
detailQueryWrapper.eq(BillDetail::getBillManageId, originalBill.getBillManageId());
detailQueryWrapper.eq(BillDetail::getDelFlag, 1);
List<BillDetail> originalDetailList = billDetailDomainService.queryByLambda(detailQueryWrapper);
if (null != originalDetailList && originalDetailList.size() > 0) {
List<BillDetail> redDetailList = new ArrayList<>();
for (BillDetail originalDetail : originalDetailList) {
BillDetail redDetail = new BillDetail();
BeanUtils.copyProperties(originalDetail, redDetail);
redDetail.setBillDetailId(null);
redDetail.setBillManageId(redBill.getBillManageId());
redDetail.setBillNumber(billNumber);
redDetail.setBillingTotalAmount(negateAmount(originalDetail.getBillingTotalAmount()));
redDetail.setBillingAmount(negateAmount(originalDetail.getBillingAmount()));
redDetail.setBillingDiscountAmount(negateAmount(originalDetail.getBillingDiscountAmount()));
redDetail.setTaxAmount(negateAmount(originalDetail.getTaxAmount()));
redDetail.setTaxFreeFee(negateAmount(originalDetail.getTaxFreeFee()));
redDetail.setBillingExpectedAmount(null);
BillingStatementDO redStatement = statementMapping.get(originalDetail.getBillingStatementId());
if (null != redStatement) {
redDetail.setBillingStatementId(redStatement.getBillingStatementId());
redDetail.setBillingFlow(redStatement.getBillingFlow());
} else {
redDetail.setBillingStatementId(null);
redDetail.setBillingFlow(null);
}
redDetail.setDelFlag(1);
redDetail.setCreateBy(loginUser.getUserid());
redDetail.setCreateByName(loginUser.getUserPo().getUserName());
redDetail.setCreateTime(new Date());
redDetail.setUpdateBy(loginUser.getUserid());
redDetail.setUpdateByName(loginUser.getUserPo().getUserName());
redDetail.setUpdateTime(new Date());
redDetailList.add(redDetail);
}
flagTwo = billDetailDomainService.insertBatch(redDetailList);
}
//红冲账单保存创建账单操作日志
BillOperationLogDO redBillLog = new BillOperationLogDO();
redBillLog.setBillManageId(redBill.getBillManageId());
redBillLog.setOperationInfo(StringUtils.format(BillLogsConstants.create_bill_text, billNumber, redFlushAmount));
redBillLog.setOperationType(1);
Boolean flagThree = billOperationLogDomainService.saveBillOperationLog(redBillLog);
//原账单保存红冲操作日志
BillOperationLogDO originalBillLog = new BillOperationLogDO();
originalBillLog.setBillManageId(originalBill.getBillManageId());
originalBillLog.setOperationInfo(StringUtils.format(BillLogsConstants.red_flush_bill_text, billNumber, null == redFlushAmount ? BigDecimal.ZERO : redFlushAmount.abs()));
originalBillLog.setOperationType(13);
Boolean flagFour = billOperationLogDomainService.saveBillOperationLog(originalBillLog);
return flag && flagTwo && flagThree && flagFour;
}
/**
* 金额取负空值返回null
*/
private BigDecimal negateAmount(BigDecimal amount) {
return null == amount ? null : amount.negate();
}
/**
* 更改账单状态付款
*/
@@ -600,4 +1064,11 @@ public class BillManageDomainService {
{
return billManageService.synchronousNcSK(businessDocumentDetaliIds);
}
/**
* 客户每月账单分组查询
*/
public List<CustomerMonthBillVO> queryCustomerMonthBillList(BillManageDO billManageDO) {
return billManageService.queryCustomerMonthBillList(billManageDO);
}
}
@@ -42,8 +42,8 @@ public class BillOperationLog extends BaseVOEntity{
@Excel(name = "账单管理id")
private Long billManageId;
@ApiModelProperty("操作类型(1-创建账单,2-调整账单,3-确认账单,4-发起对账,5-客户确认,6-财务审核,7-收款,8-作废账单,9-删除账单,10-退款,11-付款)")
@Excel(name = "操作类型", readConverterExp = "1=-创建账单,2-调整账单,3-确认账单,4-发起对账,5-客户确认,6-财务审核,7-收款,8-作废账单,9-删除账单,10-退款,11-付款")
@ApiModelProperty("操作类型(1-创建账单,2-调整账单,3-确认账单,4-发起对账,5-客户确认,6-财务审核,7-收款,8-作废账单,9-删除账单,10-退款,11-付款,12-撤销收款,13-红冲")
@Excel(name = "操作类型", readConverterExp = "1=-创建账单,2-调整账单,3-确认账单,4-发起对账,5-客户确认,6-财务审核,7-收款,8-作废账单,9-删除账单,10-退款,11-付款,12-撤销收款,13-红冲")
private Integer operationType;
@ApiModelProperty("操作信息")
@@ -41,8 +41,8 @@ public class BillOperationLogPO extends BaseVOEntity{
@Excel(name = "账单管理id")
private Long billManageId;
@ApiModelProperty("操作类型(1-创建账单,2-调整账单,3-确认账单,4-发起对账,5-客户确认,6-财务审核,7-收款,8-作废账单,9-删除账单,10-退款,11-付款)")
@Excel(name = "操作类型", readConverterExp = "1=-创建账单,2-调整账单,3-确认账单,4-发起对账,5-客户确认,6-财务审核,7-收款,8-作废账单,9-删除账单,10-退款,11-付款")
@ApiModelProperty("操作类型(1-创建账单,2-调整账单,3-确认账单,4-发起对账,5-客户确认,6-财务审核,7-收款,8-作废账单,9-删除账单,10-退款,11-付款,12-撤销收款,13-红冲")
@Excel(name = "操作类型", readConverterExp = "1=-创建账单,2-调整账单,3-确认账单,4-发起对账,5-客户确认,6-财务审核,7-收款,8-作废账单,9-删除账单,10-退款,11-付款,12-撤销收款,13-红冲")
private Integer operationType;
@ApiModelProperty("操作信息")
@@ -42,8 +42,8 @@ public class BillOperationLogDO extends BaseVOEntity{
@Excel(name = "账单管理id")
private Long billManageId;
@ApiModelProperty("操作类型(1-创建账单,2-调整账单,3-确认账单,4-发起对账,5-客户确认,6-财务审核,7-收款,8-作废账单,9-删除账单,10-退款,11-付款)")
@Excel(name = "操作类型", readConverterExp = "1=-创建账单,2-调整账单,3-确认账单,4-发起对账,5-客户确认,6-财务审核,7-收款,8-作废账单,9-删除账单,10-退款,11-付款")
@ApiModelProperty("操作类型(1-创建账单,2-调整账单,3-确认账单,4-发起对账,5-客户确认,6-财务审核,7-收款,8-作废账单,9-删除账单,10-退款,11-付款,12-撤销收款,13-红冲")
@Excel(name = "操作类型", readConverterExp = "1=-创建账单,2-调整账单,3-确认账单,4-发起对账,5-客户确认,6-财务审核,7-收款,8-作废账单,9-删除账单,10-退款,11-付款,12-撤销收款,13-红冲")
private Integer operationType;
@ApiModelProperty("操作信息")
@@ -95,6 +95,12 @@ public class BillOperationLogDomainService {
return billOperationLogService.insert(billOperationLogDO);
}
public Boolean saveBillOperationLogJob(BillOperationLogDO billOperationLogDO) {
billOperationLogDO.setDelFlag(1);
return billOperationLogService.insert(billOperationLogDO);
}
/**
* 批量保存操作日志
@@ -133,6 +139,40 @@ public class BillOperationLogDomainService {
return billOperationLogService.saveBatch(saveList);
}
public Boolean saveBillOperationLogBatchJob(BillOperationLogDO billOperationLogDO) {
// LoginUser loginUser = SecurityUtils.getLoginUser();
// if (ObjectUtil.isNull(loginUser)) {
// throw new DigitalLogisticsException(UserError.TIMEOUT);
// }
if(billOperationLogDO.getBillManageIdSet()==null || billOperationLogDO.getBillManageIdSet().size()==0){
throw new ServiceException("账单集合不能为空");
}
// String userName = loginUser.getUserPo().getUserName();
// // 获取登录人id
// Long userId = loginUser.getUserid();
BillOperationLogDO common = new BillOperationLogDO();
// common.setCreateByName(userName);
// common.setUpdateByName(userName);
common.setCreateTime(new Date());
common.setUpdateTime(new Date());
common.setDelFlag(1);
// common.setCreateBy(userId);
// common.setUpdateBy(userId);
// common.setTopOrganizationId(loginUser.getUserPo().getTopOrganizationId());
// common.setOrganizationId(loginUser.getUserPo().getOrganizationId());
// common.setOrganizationName(loginUser.getUserPo().getOrganizationName());
common.setOperationType(billOperationLogDO.getOperationType());
common.setOperationInfo(billOperationLogDO.getOperationInfo());
List<BillOperationLog> saveList = new ArrayList<>();
for (String billManageId : billOperationLogDO.getBillManageIdSet()) {
BillOperationLog save = new BillOperationLog();
BeanUtils.copyProperties(common, save);
save.setBillManageId(Long.valueOf(billManageId));
saveList.add(save);
}
return billOperationLogService.saveBatch(saveList);
}
/**
* 批量保存数据
@@ -161,4 +201,4 @@ public class BillOperationLogDomainService {
public List<BillOperationLogPO> getDetailsById(Long billManageId) {
return billOperationLogService.getDetailsById(billManageId);
}
}
}
@@ -171,6 +171,48 @@ public class BillingStatementDomainService {
return billingStatementService.updateBatchById(billingStatements);
}
public Boolean reviewBillingJob(BillingStatementDTO billingStatementDTO) {
LoginUser loginUser = SecurityUtils.getLoginUser();
if (ObjectUtil.isNull(loginUser)) {
// throw new DigitalLogisticsException(UserError.TIMEOUT);
}
List<BillingStatement> billingStatements = new ArrayList<>();
Set<String> billingSet = Stream.of(billingStatementDTO.getBillingStatementIds().split(",")).collect(Collectors.toSet());
for (String billingId : billingSet) {
Long billingStatementId = Long.valueOf(billingId);
BillingStatementPO billingStatementPO = billingStatementService.getInfo(billingStatementId);
if(null == billingStatementPO){
throw new ServiceException("流水不存在");
}
if(billingStatementPO.getBillingState()!=1){
throw new ServiceException("该计费流水状态不为待审核,不能进行审核");
}
if(billingStatementPO.getRedFlushState()==2){
throw new ServiceException("计费流水"+ billingStatementPO.getBillingFlow() +"已红冲,不能进行审核");
}
BillingStatement billingStatement = new BillingStatement();
billingStatement.setBillingStatementId(billingStatementId);
billingStatement.setReviewRemarks(billingStatementDTO.getReviewRemarks());
if(billingStatementDTO.getAuditOperation()==1){
//审核通过
billingStatement.setBillingState(2);
}else {
//审核不通过
billingStatement.setBillingState(4);
//计费流水审核不通过时修改业务单据中的状态到已生效
BusinessDocumentDO businessDocumentDO = new BusinessDocumentDO();
businessDocumentDO.setBusinessDocumentId(billingStatementPO.getBusinessDocumentId());
businessDocumentDO.setBusinessState(2);
businessDocumentService.update(businessDocumentDO);
}
billingStatement.setUpdateTime(new Date());
// billingStatement.setUpdateBy(loginUser.getUserPo().getUserId());
// billingStatement.setUpdateByName(loginUser.getUserPo().getUserName());
billingStatements.add(billingStatement);
}
return billingStatementService.updateBatchById(billingStatements);
}
/**
* 批量红冲
@@ -299,6 +341,45 @@ public class BillingStatementDomainService {
return update;
}
public Boolean updateBillingStateJob(BillingStatementDTO billingStatementDTO) {
LoginUser loginUser = SecurityUtils.getLoginUser();
if (ObjectUtil.isNull(loginUser)) {
// throw new DigitalLogisticsException(UserError.TIMEOUT);
}
Set<String> billingSet = Stream.of(billingStatementDTO.getBillingStatementIds().split(",")).collect(Collectors.toSet());
LambdaUpdateWrapper<BillingStatement> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.in(BillingStatement::getBillingStatementId,billingSet);
updateWrapper.set(BillingStatement::getBillingState,billingStatementDTO.getBillingState());
// updateWrapper.set(BillingStatement::getUpdateBy,loginUser.getUserPo().getUserId());
updateWrapper.set(BillingStatement::getUpdateTime,new Date());
// updateWrapper.set(BillingStatement::getUpdateByName,loginUser.getUserPo().getUserName());
updateWrapper.set(BillingStatement::getBillManageId,billingStatementDTO.getBillManageId());
boolean update = billingStatementService.update(updateWrapper);
List<BillingStatementADDVO> billingStatementList = billingStatementDTO.getBillingStatementList();
for (BillingStatementADDVO billingStatementADDVO : billingStatementList) {
String billingStatementIds = billingStatementADDVO.getBillingStatementId();
Set<String> billings = Stream.of(billingStatementIds.split(",")).collect(Collectors.toSet());
if (billings != null && billings.size() > 0) {
for (String billing : billings) {
Long billingStatementId = Long.valueOf(billing);
Double taxRate = billingStatementADDVO.getTaxRate();
BillingStatement billingStatement = billingStatementService.getById(billingStatementId);
if (billingStatement != null) {
BigDecimal billingAmount = billingStatement.getBillingAmount();
BigDecimal taxAmount = billingAmount.divide(BigDecimal.ONE.add(new BigDecimal(taxRate).divide(BigDecimal.valueOf(100))),10, BigDecimal.ROUND_HALF_UP).multiply(new BigDecimal(taxRate).divide(BigDecimal.valueOf(100))).setScale(2, BigDecimal.ROUND_HALF_UP);
BigDecimal taxFreeFee = billingAmount.subtract(taxAmount);
billingStatement.setTaxAmount(taxAmount);
billingStatement.setTaxFreeFee(taxFreeFee);
billingStatement.setTaxRate(taxRate);
billingStatementService.updateById(billingStatement);
}
}
}
}
return update;
}
public Boolean nullifyByIds(BillingStatementDTO billingStatementDTO) {
LoginUser loginUser = SecurityUtils.getLoginUser();
@@ -26,6 +26,8 @@ public interface IBmsInvoiceListService extends IService<BmsInvoiceList> {
void addReceivableBill(BmsInvoiceListAddDTO tmsInvoiceListAddDTO);
void addReceivableBillJob(BmsInvoiceListAddDTO tmsInvoiceListAddDTO);
void update(BmsInvoiceListDTO tmsInvoiceListDTO);
int delete(List<String> ids);
@@ -216,6 +216,76 @@ public class BmsInvoiceListServiceImpl extends ServiceImpl<BmsInvoiceListMapper,
}*/
}
@Transactional
@Override
public void addReceivableBillJob(BmsInvoiceListAddDTO bmsInvoiceListAddDTO) {
//获取登录人
// WlhyLoginUser sysUser = SecurityUtils.getLoginUser().getWlhyLoginUser();
// if (null == sysUser) {
// throw new ServiceException("sysUser是空值!");
// }
/*if (StringUtils.isEmpty(bmsInvoiceListAddDTO.getInvoiceTitle())){
throw new ServiceException("发票抬头不能为空!");
}*/
bmsInvoiceListAddDTO.setInvoiceValue(bmsInvoiceListAddDTO.getBillAmount());
BmsInvoiceList bmsInvoiceList = new BmsInvoiceList(); // 销项发票表
BeanUtils.copyProperties(bmsInvoiceListAddDTO, bmsInvoiceList);
// String createBy = sysUser.getId(); // 创建人ID
if(StringUtils.isNotBlank(bmsInvoiceListAddDTO.getCompanyName())){
bmsInvoiceList.setShipperName(bmsInvoiceListAddDTO.getCompanyName());
bmsInvoiceList.setCompanyName(bmsInvoiceListAddDTO.getCompanyName());
}
// 保存销项发票表数据
Long listId = saveBmsInvoiceListJob(bmsInvoiceList, bmsInvoiceListAddDTO, null, null);
//预保存发票明细信息
if(ObjectUtil.isNotNull(listId)){
List<BmsMakeOutInvoice> bmsMakeOutInvoices = bmsInvoiceListAddDTO.getBmsMakeOutInvoices();
if (ObjectUtil.isNotNull(bmsMakeOutInvoices) && bmsMakeOutInvoices.size() > 0) {
for (BmsMakeOutInvoice bmsMakeOutInvoice : bmsMakeOutInvoices) {
bmsMakeOutInvoice.setInvoiceId(listId);
//bmsMakeOutInvoice.setCreateBy(sysUser.getId());
bmsMakeOutInvoice.setCreateTime(new Date());
bmsMakeOutInvoiceMapper.insert(bmsMakeOutInvoice);
for (BmsMakeOutInvoiceItem bmsMakeOutInvoiceItem : bmsMakeOutInvoice.getBmsMakeOutInvoiceItems()) {
bmsMakeOutInvoiceItem.setMakeId(bmsMakeOutInvoice.getId());
//bmsMakeOutInvoiceItem.setCreateBy(sysUser.getId());
bmsMakeOutInvoiceItem.setCreateTime(new Date());
bmsMakeOutInvoiceItemMapper.insert(bmsMakeOutInvoiceItem);
}
}
}
}
// BillManage billManage = billManageMapper.selectById(bmsInvoiceListAddDTO.getBillManageId());
// if (billManage != null) {
// billManage.setIsInvoice(1);
// billManage.setInvoiceId(bmsInvoiceList.getId().toString());
// billManage.setApplyNumber(bmsInvoiceList.getApplyNumber());
// billManageMapper.updateById(billManage);
// }
List<Long> billManageIds = bmsInvoiceListAddDTO.getBillManageIds();
if (billManageIds != null && billManageIds.size() > 0){
} else {
billManageIds = new ArrayList<>();
billManageIds.add(bmsInvoiceListAddDTO.getBillManageId());
}
List<BillManage> billManages = billManageMapper.selectList(new LambdaQueryWrapper<BillManage>().in(BillManage::getBillManageId, billManageIds));
if (billManages != null && billManages.size() > 0) {
for (BillManage billManage : billManages) {
billManage.setIsInvoice(1);
billManage.setInvoiceId(bmsInvoiceList.getId().toString());
billManage.setApplyNumber(bmsInvoiceList.getApplyNumber());
billManageMapper.updateById(billManage);
}
}
/*AjaxResult ajaxResult = financeServiceFeign.editIsInvoice(transportationId,1,bmsInvoiceList.getId(),"",bmsInvoiceList.getApplyNumber());
if(!"200".equals(String.valueOf(ajaxResult.get("code")))){
throw new com.mhd.common.core.exception.ServiceException(ajaxResult.get("msg").toString());
}*/
}
@Override
public BmsSettlementPartyInvoice selectSettlementPartyInfoByName(String settlementPartyName) {
return null;
@@ -932,4 +1002,43 @@ public class BmsInvoiceListServiceImpl extends ServiceImpl<BmsInvoiceListMapper,
}
return null;
}
public Long saveBmsInvoiceListJob(BmsInvoiceList bmsInvoiceList, BmsInvoiceListAddDTO bmsInvoiceListAddDTO, WlhyLoginUser sysUser, String createBy){
bmsInvoiceList.setReceivingAddressId(bmsInvoiceListAddDTO.getReceivingAddressId());
//bmsInvoiceList.setCreateBy(createBy);
bmsInvoiceList.setApplyNumber(OrderSequence.getOrderCode("XF"));
bmsInvoiceList.setCreateTime(new Date());
bmsInvoiceList.setInvoiceApplyTime(new Date());
// bmsInvoiceList.setApplicantId(sysUser.getId());
// bmsInvoiceList.setRealCreateBy(sysUser.getId());
// bmsInvoiceList.setRealCreateName(sysUser.getRealname());
bmsInvoiceList.setCompanyName(bmsInvoiceListAddDTO.getSettlementEntity());
// 设置组织相关字段从当前登录用户获取组织信息
// LoginUser loginUser = SecurityUtils.getLoginUser();
// if (loginUser != null && loginUser.getUserPo() != null) {
// // 设置组织ID
// if (loginUser.getUserPo().getOrganizationId() != null) {
// bmsInvoiceList.setOrganizationId(loginUser.getUserPo().getOrganizationId());
// }
// // 设置组织名称
// if (StringUtils.isNotBlank(loginUser.getUserPo().getOrganizationName())) {
// bmsInvoiceList.setOrganizationName(loginUser.getUserPo().getOrganizationName());
// }
// // 设置一级组织ID
// if (loginUser.getUserPo().getTopOrganizationId() != null) {
// bmsInvoiceList.setTopOrganizationId(loginUser.getUserPo().getTopOrganizationId());
// }
// }
boolean saveSuccess = this.save(bmsInvoiceList);
// 3. 插入成功后直接从实体对象获取ID已被MyBatis-Plus自动赋值
if (saveSuccess) {
Long id = bmsInvoiceList.getId();
System.out.println("插入成功,生成的ID" + id);
return id;
}
return null;
}
}
@@ -79,5 +79,19 @@ public class BmsInvoiceListAddDTO implements Serializable {
@ApiModelProperty(value = "对账单id列表")
@Excel(name = "对账单id列表", width = 15)
private List<Long> billManageIds;
/**组织ID*/
@ApiModelProperty(value = "组织ID")
@Excel(name = "组织ID", width = 15)
private Long organizationId;
/**组织名称*/
@ApiModelProperty(value = "组织名称")
@Excel(name = "组织名称", width = 15)
private String organizationName;
/**一级组织ID*/
@ApiModelProperty(value = "一级组织ID")
@Excel(name = "一级组织ID", width = 15)
private Long topOrganizationId;
}
@@ -1,16 +1,22 @@
package com.mhd.bms.domain.receiptManage.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import cn.hutool.core.util.ObjectUtil;
import com.mhd.bms.domain.receiptManage.entity.ReceiptDetail;
import com.mhd.bms.domain.receiptManage.repository.facade.IReceiptDetailService;
import com.mhd.bms.domain.receiptManage.repository.po.ReceiptDetailPO;
import com.mhd.bms.domain.receiptManage.repository.todo.ReceiptDetailDO;
import com.mhd.common.core.exception.digitalLogisticsException.DigitalLogisticsException;
import com.mhd.common.core.exception.digitalLogisticsException.UserError;
import com.mhd.common.security.utils.SecurityUtils;
import com.mhd.system.api.model.LoginUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
@@ -95,6 +101,28 @@ public class ReceiptDetailDomainService {
public List<ReceiptDetail> queryListByIdSet(Set<Long> orderIdSet) {
LambdaQueryWrapper<ReceiptDetail> queryWrapperDetail = new LambdaQueryWrapper<>();
queryWrapperDetail.in(ReceiptDetail::getReceiptManageId, orderIdSet);
queryWrapperDetail.eq(ReceiptDetail::getDelFlag, 1);
return receiptDetailService.list(queryWrapperDetail);
}
/**
* 撤销收款批量逻辑删除收款明细
*/
public Boolean cancelCollectionDetails(List<ReceiptDetail> receiptDetails) {
LoginUser loginUser = SecurityUtils.getLoginUser();
if (ObjectUtil.isNull(loginUser)) {
throw new DigitalLogisticsException(UserError.TIMEOUT);
}
List<ReceiptDetail> receiptDetailSaveList = new ArrayList<>();
for (ReceiptDetail receiptDetail : receiptDetails) {
ReceiptDetail receiptDetailSave = new ReceiptDetail();
receiptDetailSave.setReceiptDetailId(receiptDetail.getReceiptDetailId());
receiptDetailSave.setDelFlag(2);
receiptDetailSave.setUpdateBy(loginUser.getUserPo().getUserId());
receiptDetailSave.setUpdateByName(loginUser.getUserPo().getUserName());
receiptDetailSave.setUpdateTime(new Date());
receiptDetailSaveList.add(receiptDetailSave);
}
return receiptDetailService.updateBatchById(receiptDetailSaveList);
}
}
@@ -42,7 +42,9 @@ import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -300,6 +302,132 @@ public class ReceiptManageDomainService {
return flag && flagTwo && flagThree;
}
/**
* 撤销收款
*/
public Boolean cancelCollection(ReceiptManageDO receiptManageDO) {
LoginUser loginUser = SecurityUtils.getLoginUser();
if (ObjectUtil.isNull(loginUser)) {
throw new DigitalLogisticsException(UserError.TIMEOUT);
}
if (StringUtils.isEmpty(receiptManageDO.getBillManageIds())) {
throw new ServiceException("撤销账单不能为空");
}
Set<Long> billManageIdSet = Stream.of(receiptManageDO.getBillManageIds().split(",")).filter(StringUtils::isNotEmpty).map(String::trim).map(Long::valueOf).collect(Collectors.toSet());
if (billManageIdSet.size() == 0) {
throw new ServiceException("撤销账单不能为空");
}
//查询账单信息
LambdaQueryWrapper<BillManage> queryWrapperBill = new LambdaQueryWrapper<>();
queryWrapperBill.in(BillManage::getBillManageId, billManageIdSet);
queryWrapperBill.eq(BillManage::getDelFlag, 1);
List<BillManage> billManageList = billManageDomainService.queryListLambda(queryWrapperBill);
if (null == billManageList || billManageList.size() == 0) {
throw new ServiceException("账单信息未找到");
}
if (billManageList.size() != billManageIdSet.size()) {
throw new ServiceException("部分账单信息未找到,请刷新后重试");
}
//查询账单的全部有效收款明细一个账单可能对应多个收款单
LambdaQueryWrapper<ReceiptDetail> queryWrapperDetail = new LambdaQueryWrapper<>();
queryWrapperDetail.in(ReceiptDetail::getBillManageId, billManageIdSet);
queryWrapperDetail.eq(ReceiptDetail::getDelFlag, 1);
List<ReceiptDetail> receiptDetails = receiptDetailDomainService.queryListByLambda(queryWrapperDetail);
if (null == receiptDetails || receiptDetails.size() == 0) {
throw new ServiceException("账单收款记录不存在或已撤销,请刷新后重试");
}
//校验每个账单都有收款记录
Map<Long, List<ReceiptDetail>> billDetailMap = receiptDetails.stream().collect(Collectors.groupingBy(ReceiptDetail::getBillManageId));
for (BillManage billManage : billManageList) {
if (!billDetailMap.containsKey(billManage.getBillManageId())) {
throw new ServiceException("账单" + billManage.getBillNumber() + "收款记录不存在或已撤销,请刷新后重试");
}
}
//校验收款单退款状态已退款的收款单不能撤销收款
Set<Long> receiptManageIdSet = receiptDetails.stream().map(ReceiptDetail::getReceiptManageId).collect(Collectors.toSet());
LambdaQueryWrapper<ReceiptManage> queryWrapperManage = new LambdaQueryWrapper<>();
queryWrapperManage.in(ReceiptManage::getReceiptManageId, receiptManageIdSet);
List<ReceiptManage> receiptManageList = receiptManageService.list(queryWrapperManage);
if (null != receiptManageList) {
ReceiptManage refundManage = receiptManageList.stream().filter(info -> ObjectUtil.equal(info.getPaymentStatus(), 2)).findAny().orElse(null);
if (null != refundManage) {
throw new ServiceException("收款单" + refundManage.getCollectionNum() + "已退款,无法撤销收款");
}
}
//账单恢复为收款为0状态并保存撤销收款操作日志
Boolean flagTwo = billManageDomainService.cancelCollectionInfo(receiptDetails);
//逻辑删除收款明细
Boolean flag = receiptDetailDomainService.cancelCollectionDetails(receiptDetails);
//按剩余明细重新计算涉及的收款单金额明细已全部撤销时逻辑删除收款单
Boolean flagThree = true;
for (Long receiptManageId : receiptManageIdSet) {
flagThree = updateReceiptManageByCancel(receiptManageId) && flagThree;
}
return flag && flagTwo && flagThree;
}
/**
* 撤销收款后重新计算收款单信息收款明细已全部撤销时逻辑删除收款单
*/
private Boolean updateReceiptManageByCancel(Long receiptManageId) {
LoginUser loginUser = SecurityUtils.getLoginUser();
if (ObjectUtil.isNull(loginUser)) {
throw new DigitalLogisticsException(UserError.TIMEOUT);
}
//查询收款单下剩余未撤销的收款明细
LambdaQueryWrapper<ReceiptDetail> queryWrapperDetail = new LambdaQueryWrapper<>();
queryWrapperDetail.eq(ReceiptDetail::getReceiptManageId, receiptManageId);
queryWrapperDetail.eq(ReceiptDetail::getDelFlag, 1);
List<ReceiptDetail> receiptDetailList = receiptDetailDomainService.queryListByLambda(queryWrapperDetail);
ReceiptManage receiptManageSave = new ReceiptManage();
receiptManageSave.setReceiptManageId(receiptManageId);
receiptManageSave.setUpdateBy(loginUser.getUserPo().getUserId());
receiptManageSave.setUpdateByName(loginUser.getUserPo().getUserName());
receiptManageSave.setUpdateTime(new Date());
if (null == receiptDetailList || receiptDetailList.size() == 0) {
//收款明细已全部撤销逻辑删除收款单
receiptManageSave.setDelFlag(2);
return receiptManageService.updateById(receiptManageSave);
}
//按剩余明细重新计算收款单金额
BigDecimal billAmount = receiptDetailList.stream().map(ReceiptDetail::getBillAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal collectedDiscount = receiptDetailList.stream().map(ReceiptDetail::getCollectedDiscount).reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal collectedAmount = receiptDetailList.stream().map(ReceiptDetail::getCollectedAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal uncollectedAmount = receiptDetailList.stream().map(ReceiptDetail::getBillAmountUnsettledResidue).reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal billAmountUnsettled = receiptDetailList.stream().map(ReceiptDetail::getBillAmountUnsettled).reduce(BigDecimal.ZERO, BigDecimal::add);
receiptManageSave.setBillAmount(billAmount);
receiptManageSave.setCollectedDiscount(collectedDiscount);
receiptManageSave.setCollectedAmount(collectedAmount);
receiptManageSave.setUncollectedAmount(uncollectedAmount);
receiptManageSave.setBillAmountUnsettled(billAmountUnsettled);
//账单金额 = 已收金额 + 剩余未收金额 + 收款优惠金额 + 收款金额
receiptManageSave.setBillAmountSettlement(billAmount.subtract(uncollectedAmount).subtract(collectedDiscount).subtract(collectedAmount));
return receiptManageService.updateById(receiptManageSave);
}
/**
* 撤销收款-查询账单涉及的收款单id用于加锁防止并发撤销时收款单金额重算冲突
*/
public Set<Long> queryReceiptManageIdsByBillManageIds(String billManageIds) {
Set<Long> receiptManageIdSet = new HashSet<>();
if (StringUtils.isEmpty(billManageIds)) {
return receiptManageIdSet;
}
Set<Long> billManageIdSet = Stream.of(billManageIds.split(",")).filter(StringUtils::isNotEmpty).map(String::trim).map(Long::valueOf).collect(Collectors.toSet());
if (billManageIdSet.size() == 0) {
return receiptManageIdSet;
}
LambdaQueryWrapper<ReceiptDetail> queryWrapperDetail = new LambdaQueryWrapper<>();
queryWrapperDetail.in(ReceiptDetail::getBillManageId, billManageIdSet);
queryWrapperDetail.eq(ReceiptDetail::getDelFlag, 1);
List<ReceiptDetail> receiptDetails = receiptDetailDomainService.queryListByLambda(queryWrapperDetail);
if (null == receiptDetails || receiptDetails.size() == 0) {
return receiptManageIdSet;
}
receiptDetails.forEach(receiptDetail -> receiptManageIdSet.add(receiptDetail.getReceiptManageId()));
return receiptManageIdSet;
}
/**
* 查询收款单详情
*/
@@ -88,6 +88,14 @@ public class BillLogsConstants
public static final String refund_bill_text = "退款单号:{},退款金额:{}元";
/**
* 撤销收款
* 收款单号{}撤销收款金额{}实收{}优惠{}
*/
public static final String cancel_collection_bill_title = "撤销收款";
public static final String cancel_collection_bill_text = "收款单号:{},撤销收款金额:{}元(实收{}元,优惠{}元)";
/**
* 付款
* 付款单号{}付款金额{}
@@ -104,4 +112,11 @@ public class BillLogsConstants
public static final String copy_bill_text = "原账单编号:{},新账单编号:{}";
/**
* 红冲账单
* 红冲账单编号{}红冲金额{}
*/
public static final String red_flush_bill_title = "红冲账单";
public static final String red_flush_bill_text = "红冲账单编号:{},红冲金额:{}元";
}
@@ -47,8 +47,8 @@ public class BillManageDTO extends BaseVOEntity{
@Excel(name = "账单类型", readConverterExp = "1=-应收,2-应付")
private Integer billType;
@ApiModelProperty("账单状态(1-未确认,2-对账中,3-未收款/付款,4-部分收款/付款,5-已收款/付款,6-已作废,7-已退款)")
@Excel(name = "账单状态", readConverterExp = "1=-未确认,2-对账中,3-未收款/付款,4-部分收款/付款,5-已收款/付款,6-已作废,7-已退款")
@ApiModelProperty("账单状态(1-未确认,2-对账中,3-未收款/付款,4-部分收款/付款,5-已收款/付款,6-已作废,7-已退款,8-红冲")
@Excel(name = "账单状态", readConverterExp = "1=-未确认,2-对账中,3-未收款/付款,4-部分收款/付款,5-已收款/付款,6-已作废,7-已退款,8-红冲")
private Integer billState;
@ApiModelProperty("账单流程节点(1-发起对账,2-客户确认,3-财务审核,4-收款/付款)")
@@ -276,4 +276,11 @@ public class BillManageDTO extends BaseVOEntity{
private String paymentAccountId;
@ApiModelProperty("收款帐户名称")
private String paymentAccountName;
@ApiModelProperty("账单月份(yyyy-MM,按创建时间月份筛选,用于客户月账单分组查询)")
private String billMonth;
@ApiModelProperty("红冲原因")
@Excel(name = "红冲原因")
private String redFlushRemark;
}
@@ -28,7 +28,7 @@ public class BillManageExportVO {
@Excel(name = "账单类型", readConverterExp = "1=-应收,2-应付")
private Integer billType;
@Excel(name = "账单状态", readConverterExp = "1=-未确认,2-对账中,3-未收款/付款,4-部分收款/付款,5-已收款/付款,6-已作废,7-已退款")
@Excel(name = "账单状态", readConverterExp = "1=-未确认,2-对账中,3-未收款/付款,4-部分收款/付款,5-已收款/付款,6-已作废,7-已退款,8-红冲")
private Integer billState;
@Excel(name = "账单流程节点", readConverterExp = "1=-发起对账,2-客户确认,3-财务审核,4-收款/付款")
@@ -0,0 +1,65 @@
package com.mhd.bms.interfaces.dto.billManage;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
@Data
@ApiModel(value = "客户月账单VO", description = "客户每月账单分组统计信息")
public class CustomerMonthBillVO implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("结算对象id")
private Long settlementCustomersId;
@ApiModelProperty("结算对象code")
private String settlementCustomersCode;
@ApiModelProperty("结算对象/客户名称")
private String settlementEntity;
@ApiModelProperty("结算主体id")
private Long settlementEntityId;
@ApiModelProperty("结算主体")
private String settlementEntityName;
@ApiModelProperty("组织ID")
private Long organizationId;
@ApiModelProperty("组织名称")
private String organizationName;
@ApiModelProperty("账单月份(yyyy-MM")
private String billMonth;
@ApiModelProperty("账单总金额合计")
private BigDecimal totalBillAmount;
@ApiModelProperty("已收金额合计")
private BigDecimal totalSettledAmount;
@ApiModelProperty("未收金额合计")
private BigDecimal totalUnsettledAmount;
@ApiModelProperty("账单数量")
private Integer billCount;
@ApiModelProperty("付款状态(1-未付款,2-部分付款,3-已付款)")
private Integer paymentStatus;
@ApiModelProperty("付款状态名称")
private String paymentStatusName;
@ApiModelProperty("应收账单id列表(逗号拼接)")
private String billManageIds;
@ApiModelProperty("结算方式")
private String settlementMethod;
@ApiModelProperty("默认结算币种")
private String settlementCurrency;
}
@@ -40,8 +40,8 @@ public class BillOperationLogDTO extends BaseVOEntity{
@Excel(name = "账单管理id")
private Long billManageId;
@ApiModelProperty("操作类型(1-创建账单,2-调整账单,3-确认账单,4-发起对账,5-客户确认,6-财务审核,7-收款,8-作废账单,9-删除账单,10-退款,11-付款)")
@Excel(name = "操作类型", readConverterExp = "1=-创建账单,2-调整账单,3-确认账单,4-发起对账,5-客户确认,6-财务审核,7-收款,8-作废账单,9-删除账单,10-退款,11-付款")
@ApiModelProperty("操作类型(1-创建账单,2-调整账单,3-确认账单,4-发起对账,5-客户确认,6-财务审核,7-收款,8-作废账单,9-删除账单,10-退款,11-付款,12-撤销收款,13-红冲")
@Excel(name = "操作类型", readConverterExp = "1=-创建账单,2-调整账单,3-确认账单,4-发起对账,5-客户确认,6-财务审核,7-收款,8-作废账单,9-删除账单,10-退款,11-付款,12-撤销收款,13-红冲")
private Integer operationType;
@ApiModelProperty("操作信息")
@@ -3,6 +3,7 @@ package com.mhd.bms.interfaces.facadeApi;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.math.BigDecimal;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -17,6 +18,7 @@ import com.mhd.bms.infrastructure.utils.UniqueKeyUtil;
import com.mhd.bms.interfaces.dto.billingStatement.BillingStatementDTO;
import com.mhd.bms.interfaces.dto.billManage.BillManageExportVO;
import com.mhd.common.core.exception.ServiceException;
import com.mhd.common.core.utils.StringUtils;
import com.mhd.common.core.utils.poi.ExcelUtil;
import com.mhd.common.log.annotation.Log;
import com.mhd.common.log.enums.BusinessType;
@@ -31,9 +33,17 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.*;
import com.mhd.bms.interfaces.dto.billManage.BillManageDTO;
import com.mhd.bms.interfaces.dto.billManage.CustomerMonthBillVO;
import com.mhd.bms.domain.billManage.repository.todo.BillManageDO;
import com.mhd.bms.domain.billManage.repository.po.BillManagePO;
import com.mhd.bms.interfaces.assembler.billManage.BillManageAssembler;
import com.mhd.bms.domain.receiptManage.service.ReceiptDetailDomainService;
import com.mhd.bms.domain.receiptManage.entity.ReceiptDetail;
import com.mhd.bms.domain.receiptManage.entity.ReceiptManage;
import com.mhd.bms.domain.receiptManage.repository.facade.IReceiptManageService;
import com.mhd.bms.domain.receiptManage.repository.po.ReceiptManagePO;
import com.mhd.bms.domain.receiptManage.repository.po.ReceiptDetailPO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import com.mhd.common.core.web.controller.BaseController;
@@ -54,6 +64,10 @@ public class BillManageApi extends BaseController{
private BillManageApplicationService billManageApplicationService;
@Autowired
private RedisLock redisLock;
@Autowired
private ReceiptDetailDomainService receiptDetailDomainService;
@Autowired
private IReceiptManageService receiptManageService;
/**
* 分页查询账单管理列表
@@ -84,6 +98,171 @@ public class BillManageApi extends BaseController{
return AjaxResult.success(billManageApplicationService.listCount(billManageDO));
}
/**
* 客户每月账单分组列表应收
*/
@ApiOperation("客户每月账单分组列表")
@GetMapping("/receivableCustomerMonthList")
public TableDataInfo receivableCustomerMonthList(BillManageDTO billManageDTO)
{
BillManageDO billManageDO = new BillManageDO();
BeanUtils.copyProperties(billManageDTO, billManageDO);
billManageDO.setBillType(1);
startPage();
List<CustomerMonthBillVO> list = billManageApplicationService.queryCustomerMonthBillList(billManageDO);
return getDataTable(list);
}
/**
* 客户每月账单分组列表统计应收
*/
@ApiOperation("客户每月账单分组列表统计")
@GetMapping(value = "/receivableCustomerMonthListCount")
public AjaxResult receivableCustomerMonthListCount(BillManageDTO billManageDTO)
{
BillManageDO billManageDO = new BillManageDO();
BeanUtils.copyProperties(billManageDTO, billManageDO);
billManageDO.setBillType(1);
List<CustomerMonthBillVO> list = billManageApplicationService.queryCustomerMonthBillList(billManageDO);
BigDecimal totalBillAmount = list.stream()
.map(CustomerMonthBillVO::getTotalBillAmount)
.filter(java.util.Objects::nonNull)
.reduce(java.math.BigDecimal.ZERO, java.math.BigDecimal::add);
BigDecimal totalSettledAmount = list.stream()
.map(CustomerMonthBillVO::getTotalSettledAmount)
.filter(java.util.Objects::nonNull)
.reduce(java.math.BigDecimal.ZERO, java.math.BigDecimal::add);
BigDecimal totalUnsettledAmount = list.stream()
.map(CustomerMonthBillVO::getTotalUnsettledAmount)
.filter(java.util.Objects::nonNull)
.reduce(java.math.BigDecimal.ZERO, java.math.BigDecimal::add);
java.util.Map<String, Object> resultMap = new java.util.HashMap<>();
resultMap.put("totalBillAmount", totalBillAmount);
resultMap.put("totalSettledAmount", totalSettledAmount);
resultMap.put("totalUnsettledAmount", totalUnsettledAmount);
resultMap.put("totalCount", list.size());
return AjaxResult.success(resultMap);
}
/**
* 根据账单ID列表查询应收账单详情客户月账单点击账单数量回显
*/
@ApiOperation("根据账单ID列表查询应收账单详情")
@GetMapping("/receivableDetailByMonth")
public TableDataInfo receivableDetailByMonth(BillManageDTO billManageDTO)
{
if (billManageDTO.getBillManageIds() != null && !billManageDTO.getBillManageIds().isEmpty()) {
Set<String> idSet = java.util.Arrays.stream(billManageDTO.getBillManageIds().split(","))
.collect(java.util.stream.Collectors.toSet());
billManageDTO.setBillManageIdSet(idSet);
}
BillManageDO billManageDO = new BillManageDO();
BeanUtils.copyProperties(billManageDTO, billManageDO);
billManageDO.setBillType(1);
startPage();
List<BillManagePO> list = billManageApplicationService.queryList(billManageDO);
return getDataTable(list);
}
@ApiOperation("根据账单ID列表查询应收账单汇总")
@GetMapping("/receivableSumByMonth")
public AjaxResult receivableSumByMonth(BillManageDTO billManageDTO)
{
if (billManageDTO.getBillManageIds() != null && !billManageDTO.getBillManageIds().isEmpty()) {
Set<String> idSet = java.util.Arrays.stream(billManageDTO.getBillManageIds().split(","))
.collect(java.util.stream.Collectors.toSet());
billManageDTO.setBillManageIdSet(idSet);
}
BillManageDO billManageDO = new BillManageDO();
BeanUtils.copyProperties(billManageDTO, billManageDO);
billManageDO.setBillType(1);
List<BillManagePO> list = billManageApplicationService.queryList(billManageDO);
Set<Long> billManageIdSet = list.stream()
.map(BillManagePO::getBillManageId)
.filter(java.util.Objects::nonNull)
.collect(java.util.stream.Collectors.toSet());
List<ReceiptDetail> allReceiptDetails = new ArrayList<>();
Set<Long> receiptManageIdSet = new java.util.HashSet<>();
List<ReceiptManagePO> receiptManageList = new ArrayList<>();
if (!billManageIdSet.isEmpty()) {
allReceiptDetails = receiptDetailDomainService.queryListByLambda(
new LambdaQueryWrapper<ReceiptDetail>()
.in(ReceiptDetail::getBillManageId, billManageIdSet)
.eq(ReceiptDetail::getDelFlag, 1));
java.util.Map<Long, List<ReceiptDetail>> detailByBillId = allReceiptDetails.stream()
.collect(java.util.stream.Collectors.groupingBy(ReceiptDetail::getBillManageId));
for (BillManagePO bill : list) {
bill.setReceiptDetailList(detailByBillId.getOrDefault(bill.getBillManageId(), new ArrayList<>()));
}
receiptManageIdSet = allReceiptDetails.stream()
.map(ReceiptDetail::getReceiptManageId)
.filter(java.util.Objects::nonNull)
.collect(java.util.stream.Collectors.toSet());
if (!receiptManageIdSet.isEmpty()) {
List<ReceiptManage> manages = receiptManageService.list(
new LambdaQueryWrapper<ReceiptManage>()
.in(ReceiptManage::getReceiptManageId, receiptManageIdSet)
.eq(ReceiptManage::getDelFlag, 1));
java.util.Map<Long, List<ReceiptDetailPO>> detailPOByManageId = allReceiptDetails.stream()
.map(d -> {
ReceiptDetailPO po = new ReceiptDetailPO();
BeanUtils.copyProperties(d, po);
return po;
})
.collect(java.util.stream.Collectors.groupingBy(ReceiptDetailPO::getReceiptManageId));
for (ReceiptManage m : manages) {
ReceiptManagePO po = new ReceiptManagePO();
BeanUtils.copyProperties(m, po);
po.setReceiptDetailPOList(detailPOByManageId.getOrDefault(m.getReceiptManageId(), new ArrayList<>()));
receiptManageList.add(po);
}
}
}
BigDecimal totalBillAmount = list.stream()
.map(BillManagePO::getBillAmount)
.filter(java.util.Objects::nonNull)
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal totalSettledAmount = list.stream()
.map(BillManagePO::getBillAmountSettlement)
.filter(java.util.Objects::nonNull)
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal totalUnsettledAmount = list.stream()
.map(BillManagePO::getBillAmountUnsettled)
.filter(java.util.Objects::nonNull)
.reduce(BigDecimal.ZERO, BigDecimal::add);
int billCount = list.size();
String collectionStatus;
if (billCount == 0) {
collectionStatus = "无数据";
} else if (totalUnsettledAmount.compareTo(BigDecimal.ZERO) == 0) {
collectionStatus = "已收款";
} else if (totalSettledAmount.compareTo(BigDecimal.ZERO) == 0) {
collectionStatus = "未收款";
} else {
collectionStatus = "部分收款";
}
java.util.Map<String, Object> resultMap = new java.util.HashMap<>();
resultMap.put("totalBillAmount", totalBillAmount);
resultMap.put("totalSettledAmount", totalSettledAmount);
resultMap.put("totalUnsettledAmount", totalUnsettledAmount);
resultMap.put("billCount", billCount);
resultMap.put("collectionStatus", collectionStatus);
resultMap.put("billList", list);
resultMap.put("receiptManageList", receiptManageList);
return AjaxResult.success(resultMap);
}
@ApiOperation("导出应收账单")
@GetMapping(value = "/exportReceivableList", produces = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
public void exportReceivableList(BillManageDTO billManageDTO, HttpServletResponse response) throws IOException
@@ -328,6 +507,46 @@ public class BillManageApi extends BaseController{
}
@ApiOperation("应收账单-红冲")
@PostMapping("/redFlushBill")
public AjaxResult redFlushBill(@RequestBody BillManageDTO billManageDTO)
{
if(!StringUtils.isNotEmpty(billManageDTO.getBillManageIds())){
throw new ServiceException("账单id不能为空");
}
//批量加锁
RedissonMultiLock redissonMultiLock = null;
boolean lockFlag = false;
try {
RedisLockTypeEnum redisLockTypeEnum = RedisLockTypeEnum.BMS;
List<RLock> locks = new ArrayList<>();
Set<String> billIdSet = Stream.of(billManageDTO.getBillManageIds().split(",")).filter(StringUtils::isNotEmpty).map(String::trim).collect(Collectors.toSet());
billIdSet.forEach(x -> {
//获取唯一key值
String key = redisLockTypeEnum.getUniqueKey(UniqueKeyUtil.getBillingKey(x));
locks.add(redisLock.getRLock(key));
});
redissonMultiLock = new RedissonMultiLock(locks.toArray(new RLock[billIdSet.size()]));
//尝试获取锁 不等待 持有锁10分钟
lockFlag = redissonMultiLock.tryLock(-1, 10, TimeUnit.MINUTES);
if (!lockFlag) {
redissonMultiLock = null;
throw new ServiceException("账单正在操作中,请稍后重试");
}
log.info("红冲账单加锁成功");
billManageApplicationService.redFlushBill(billManageDTO);
return AjaxResult.success("操作成功");
}catch (Exception e){
return AjaxResult.error("红冲账单失败:"+e.getMessage());
} finally {
if (redissonMultiLock != null && lockFlag){
redissonMultiLock.unlock();
log.info("红冲账单释放锁成功");
}
}
}
@ApiOperation("应收账单-客户确认")
@PostMapping("/customerConfirmation")
public AjaxResult customerConfirmation(@RequestBody BillManageDTO billManageDTO)
@@ -438,6 +657,21 @@ public class BillManageApi extends BaseController{
}
/**
* 客户月结报表
*/
@ApiOperation("客户月结报表")
@GetMapping("/customerMonthlyReport")
public TableDataInfo customerMonthlyReport(BillManageDTO billManageDTO)
{
//转换实体
BillManageDO billManageDO = new BillManageDO();
BeanUtils.copyProperties(billManageDTO,billManageDO);
startPage();
billManageDO.setBillType(1);
List<BillManagePO> list = billManageApplicationService.customerMonthlyReport(billManageDO);
return getDataTable(list);
}
@@ -339,7 +339,10 @@ public class BillingStatementApi extends BaseController {
String shipperId = XxlJobHelper.getJobParam();
logger.info("XXL-JOB 开始入库业务单推送bms计费流水,货主id参数:{}", shipperId);
try {
//干仓
billingStatementApplicationService.pushBillManage(shipperId);
//冻仓
billingStatementApplicationService.pushBillManageDC(shipperId);
// 设置任务执行结果
XxlJobHelper.handleSuccess("入库业务单推送bms计费流水执行成功");
} catch (Exception e) {
@@ -1,8 +1,10 @@
package com.mhd.bms.interfaces.facadeApi;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -55,6 +57,11 @@ public class ReceiptManageApi extends BaseController{
@Autowired
private RedisLock redisLock;
/**
* 排队批量收款-获取账单锁的最大等待时间()等待期内排队获取锁避免并发收款直接失败
*/
private static final long QUEUE_COLLECTION_LOCK_WAIT_SECONDS = 30L;
/**
* 分页查询收款单管理列表
*/
@@ -114,6 +121,116 @@ public class ReceiptManageApi extends BaseController{
}
}
/**
* 排队批量收款
* 接收多个批量收款请求参数集合整体加锁后按顺序排队依次处理
* 同一账单分多次收款时前一笔处理完成后才处理下一笔避免redis锁冲突导致收款失败
* 整个集合在同一事务内处理任意一笔收款失败则全部回滚
*/
@ApiOperation("排队批量收款")
@RepeatSubmit
@PostMapping("/queueBatchCollection")
public AjaxResult queueBatchCollection(@RequestBody List<ReceiptManageDTO> receiptManageDTOList)
{
if(StringUtils.isEmpty(receiptManageDTOList)){
throw new ServiceException("收款账单不能为空");
}
//汇总所有收款请求涉及的账单id并排序整体加锁保证事务期间锁不释放且加锁顺序一致避免死锁
Set<String> lockKeySet = new TreeSet<>();
for (ReceiptManageDTO receiptManageDTO : receiptManageDTOList) {
if(!StringUtils.isNotEmpty(receiptManageDTO.getBillManageIds())){
throw new ServiceException("收款账单不能为空");
}
lockKeySet.addAll(Stream.of(receiptManageDTO.getBillManageIds().split(",")).filter(StringUtils::isNotEmpty).map(String::trim).collect(Collectors.toSet()));
}
RedisLockTypeEnum redisLockTypeEnum = RedisLockTypeEnum.BMS;
List<RLock> locks = new ArrayList<>();
lockKeySet.forEach(x -> {
//获取唯一key值
String key = redisLockTypeEnum.getUniqueKey(UniqueKeyUtil.getBillingKey(x));
locks.add(redisLock.getRLock(key));
});
RedissonMultiLock redissonMultiLock = new RedissonMultiLock(locks.toArray(new RLock[lockKeySet.size()]));
boolean payInfoDetailLock = false;
try {
//尝试获取锁 等待指定时间内排队获取 持有锁10分钟
payInfoDetailLock = redissonMultiLock.tryLock(QUEUE_COLLECTION_LOCK_WAIT_SECONDS, 10, TimeUnit.MINUTES);
if (!payInfoDetailLock) {
redissonMultiLock = null;
return AjaxResult.error("账单正在处理,请稍后再试");
}
log.info("排队批量收款-生成账单加锁成功");
//转换实体
List<ReceiptManageDO> receiptManageDOList = new ArrayList<>();
for (ReceiptManageDTO receiptManageDTO : receiptManageDTOList) {
ReceiptManageDO receiptManageDO = new ReceiptManageDO();
BeanUtils.copyProperties(receiptManageDTO,receiptManageDO);
receiptManageDOList.add(receiptManageDO);
}
//事务内排队逐笔收款任意一笔失败全部回滚
return toAjax(receiptManageApplicationService.queueBatchCollection(receiptManageDOList));
}catch (Exception e){
//异常已在应用服务层事务内标记回滚收款失败必回滚此处仅返回失败结果
return AjaxResult.error("收款失败:" + e.getMessage());
}finally {
if (redissonMultiLock != null && payInfoDetailLock) {
redissonMultiLock.unlock();
log.info("排队批量收款-生成账单释放了锁");
}
}
}
/**
* 撤销收款
*/
@ApiOperation("撤销收款")
@RepeatSubmit
@PostMapping("/cancelCollection")
public AjaxResult cancelCollection(@RequestBody ReceiptManageDTO receiptManageDTO)
{
if(!StringUtils.isNotEmpty(receiptManageDTO.getBillManageIds())){
throw new ServiceException("撤销账单不能为空");
}
RedissonMultiLock redissonMultiLock = null;
boolean payInfoDetailLock = false;
try {
RedisLockTypeEnum redisLockTypeEnum = RedisLockTypeEnum.BMS;
List<RLock> locks = new ArrayList<>();
//账单id与涉及的收款单id去重后加锁避免同一把锁重复加锁
Set<String> lockKeySet = new LinkedHashSet<>();
lockKeySet.addAll(Stream.of(receiptManageDTO.getBillManageIds().split(",")).filter(StringUtils::isNotEmpty).map(String::trim).collect(Collectors.toSet()));
//查询账单涉及的收款单一并加锁避免并发撤销时收款单金额重算冲突
Set<Long> receiptManageIdSet = receiptManageApplicationService.queryCancelCollectionReceiptManageIds(receiptManageDTO.getBillManageIds());
if (null != receiptManageIdSet) {
receiptManageIdSet.forEach(x -> lockKeySet.add(String.valueOf(x)));
}
lockKeySet.forEach(x -> {
//获取唯一key值
String key = redisLockTypeEnum.getUniqueKey(UniqueKeyUtil.getBillingKey(x));
locks.add(redisLock.getRLock(key));
});
redissonMultiLock = new RedissonMultiLock(locks.toArray(new RLock[lockKeySet.size()]));
//尝试获取锁 不等待 持有锁10分钟
payInfoDetailLock = redissonMultiLock.tryLock(-1, 10, TimeUnit.MINUTES);
if (!payInfoDetailLock) {
redissonMultiLock = null;
return AjaxResult.error("收款单正在处理,请稍后再试");
}
log.info("撤销收款加锁成功");
//转换实体
ReceiptManageDO receiptManageDO = new ReceiptManageDO();
BeanUtils.copyProperties(receiptManageDTO,receiptManageDO);
return toAjax(receiptManageApplicationService.cancelCollection(receiptManageDO));
}catch (Exception e){
return AjaxResult.error("撤销收款失败:" + e.getMessage());
}finally {
if (redissonMultiLock != null && payInfoDetailLock) {
redissonMultiLock.unlock();
log.info("撤销收款释放了锁");
}
}
}
/**
* 批量删除收款单管理
*/
@@ -22,7 +22,56 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
ORDER BY a.create_time DESC
</select>
<select id="customerMonthlyReport" parameterType="com.mhd.bms.domain.billManage.repository.todo.BillManageDO" resultType="com.mhd.bms.domain.billManage.repository.po.BillManagePO">
SELECT
a.*,
b.SETTLEMENT_ENTITY_ID AS "settlementEntityId",
(SELECT MAX(c.collection_time)
FROM receipt_detail rd
LEFT JOIN receipt_manage c ON rd.receipt_manage_id = c.receipt_manage_id
WHERE rd.bill_manage_id = a.bill_manage_id
AND rd.del_flag = 1
AND c.del_flag = 1) AS "collectionTime",
(SELECT LISTAGG(bs.CONTRACT_NUMBER, ',') WITHIN GROUP (ORDER BY bs.CONTRACT_NUMBER)
FROM BILLING_STATEMENT bs
WHERE bs.BILL_MANAGE_ID = a.bill_manage_id
AND bs.CONTRACT_NUMBER IS NOT NULL) AS "contractNumbers"
FROM bill_manage a
LEFT JOIN SETTLEMENT_CUSTOMERS b ON a.SETTLEMENT_CUSTOMERS_ID = b.SETTLEMENT_CUSTOMERS_ID
WHERE a.del_flag = 1
<include refid="common_where"/>
ORDER BY a.create_time DESC
</select>
<select id="queryCustomerMonthBillList" parameterType="com.mhd.bms.domain.billManage.repository.todo.BillManageDO" resultType="com.mhd.bms.interfaces.dto.billManage.CustomerMonthBillVO">
SELECT
a.SETTLEMENT_CUSTOMERS_ID AS settlementCustomersId,
a.SETTLEMENT_CUSTOMERS_CODE AS settlementCustomersCode,
a.SETTLEMENT_ENTITY AS settlementEntity,
b.SETTLEMENT_ENTITY_ID AS settlementEntityId,
b.SETTLEMENT_ENTITY AS settlementEntityName,
a.ORGANIZATION_ID AS organizationId,
a.ORGANIZATION_NAME AS organizationName,
TO_CHAR(a.CREATE_TIME, 'YYYY-MM') AS billMonth,
SUM(a.BILL_AMOUNT) AS totalBillAmount,
SUM(NVL(a.BILL_AMOUNT_SETTLEMENT, 0)) AS totalSettledAmount,
SUM(NVL(a.BILL_AMOUNT_UNSETTLED, 0)) AS totalUnsettledAmount,
COUNT(*) AS billCount,
LISTAGG(a.BILL_MANAGE_ID, ',') WITHIN GROUP (ORDER BY a.BILL_MANAGE_ID) AS billManageIds,
b.SETTLEMENT_METHOD AS settlementMethod,
b.SETTLEMENT_CURRENCY AS settlementCurrency
FROM bill_manage a
LEFT JOIN SETTLEMENT_CUSTOMERS b ON a.SETTLEMENT_CUSTOMERS_ID = b.SETTLEMENT_CUSTOMERS_ID AND b.del_flag = 1
WHERE a.del_flag = 1
<include refid="common_where"/>
GROUP BY a.SETTLEMENT_CUSTOMERS_ID, TO_CHAR(a.CREATE_TIME, 'YYYY-MM'), a.ORGANIZATION_ID, a.ORGANIZATION_NAME, b.SETTLEMENT_ENTITY_ID, b.SETTLEMENT_ENTITY, b.SETTLEMENT_METHOD, b.SETTLEMENT_CURRENCY, a.SETTLEMENT_CUSTOMERS_CODE, a.SETTLEMENT_ENTITY
ORDER BY MAX(a.CREATE_TIME) DESC
</select>
<sql id="common_where">
<if test="billManageDO.billMonth != null and billManageDO.billMonth != ''">
AND TO_CHAR(a.CREATE_TIME, 'YYYY-MM') = #{billManageDO.billMonth}
</if>
<if test="billManageDO.createTimeStart != null and billManageDO.createTimeStart != ''">
AND date_format(a.create_time,'%Y-%m-%d') <![CDATA[>=]]> #{billManageDO.createTimeStart}
</if>
@@ -12,39 +12,39 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<sql id="selectBillOperationLogPo1">
<where>
<if test="createByName != null and createByName != ''">
and create_by_name like concat('%', #{createByName}, '%')
<if test="billOperationLogDO.createByName != null and billOperationLogDO.createByName != ''">
and create_by_name like concat('%', #{billOperationLogDO.createByName}, '%')
</if>
<if test="updateByName != null and updateByName != ''">
and update_by_name like concat('%', #{updateByName}, '%')
<if test="billOperationLogDO.updateByName != null and billOperationLogDO.updateByName != ''">
and update_by_name like concat('%', #{billOperationLogDO.updateByName}, '%')
</if>
<if test="topOrganizationId != null ">
and top_organization_id = #{topOrganizationId}
<if test="billOperationLogDO.topOrganizationId != null ">
and top_organization_id = #{billOperationLogDO.topOrganizationId}
</if>
<if test="organizationId != null ">
and organization_id = #{organizationId}
<if test="billOperationLogDO.organizationId != null ">
and organization_id = #{billOperationLogDO.organizationId}
</if>
<if test="organizationName != null and organizationName != ''">
and organization_name like concat('%', #{organizationName}, '%')
<if test="billOperationLogDO.organizationName != null and billOperationLogDO.organizationName != ''">
and organization_name like concat('%', #{billOperationLogDO.organizationName}, '%')
</if>
<if test="billManageId != null ">
and bill_manage_id = #{billManageId}
<if test="billOperationLogDO.billManageId != null ">
and bill_manage_id = #{billOperationLogDO.billManageId}
</if>
<if test="billNumber != null and billNumber != ''">
and bill_number = #{billNumber}
<!-- <if test="billOperationLogDO.billNumber != null and billOperationLogDO.billNumber != ''">-->
<!-- and bill_number = #{billOperationLogDO.billNumber}-->
<!-- </if>-->
<if test="billOperationLogDO.operationType != null ">
and operation_type = #{billOperationLogDO.operationType}
</if>
<if test="operationType != null ">
and operation_type = #{operationType}
</if>
<if test="operationInfo != null and operationInfo != ''">
and operation_info = #{operationInfo}
</if>
<if test="operator != null and operator != ''">
and operator = #{operator}
</if>
<if test="operationTime != null ">
and operation_time = #{operationTime}
<if test="billOperationLogDO.operationInfo != null and billOperationLogDO.operationInfo != ''">
and operation_info = #{billOperationLogDO.operationInfo}
</if>
<!-- <if test="billOperationLogDO.operator != null and billOperationLogDO.operator != ''">-->
<!-- and operator = #{billOperationLogDO.operator}-->
<!-- </if>-->
<!-- <if test="billOperationLogDO.operationTime != null ">-->
<!-- and operation_time = #{billOperationLogDO.operationTime}-->
<!-- </if>-->
</where>
</sql>
@@ -64,4 +64,4 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
AND bill_manage_id = #{billManageIds}
ORDER BY create_time ASC
</select>
</mapper>
</mapper>
@@ -275,7 +275,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
SELECT
RATE as "taxRate"
FROM
EXPENSE_ACCOUNT
NGWL_TEST_SYSTEM.EXPENSE_ACCOUNT
WHERE
SUBJECT_CODE = #{subjectCode}
AND SUBJECT_NAME = #{subjectName}
@@ -13,7 +13,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
WM_CONCAT(b.bill_number) AS billNumbers
FROM
receipt_manage a
LEFT JOIN receipt_detail b ON a.receipt_manage_id = b.receipt_manage_id
LEFT JOIN receipt_detail b ON a.receipt_manage_id = b.receipt_manage_id AND b.del_flag = 1
WHERE
a.del_flag = 1
<include refid="common_where"/>
@@ -101,6 +101,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
left join PAYMENT_WALLET c on b.payment_card_id = c.PAYMENT_WALLET_ID
WHERE
a.bill_manage_id = #{billManageId}
AND a.del_flag = 1
</select>
</mapper>
@@ -0,0 +1,58 @@
package com.mhd.oms.application.service;
import com.mhd.common.core.exception.ServiceException;
import com.mhd.common.core.web.domain.AjaxResult;
import com.mhd.oms.domain.reservationStockInOrder.repository.mapper.ReservationStockInOrderMapper;
import com.mhd.system.api.SystemServiceFeign;
import com.mhd.system.api.domain.WarehouseFeignPO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* 仓库信息获取(带本地兜底): 下发/下单冻结/保存等流程原先直接 Feign system 服务取仓库信息,
* system 服务未启动/降级/返回非200时整个流程被"获取仓库信息失败"阻断
* 本组件统一收口: 优先 Feign, 失败时回退本地跨库直查 "NGWL_TEST_SYSTEM".WAREHOUSE( Feign 同库同表, OMS 库账号可读),
* 两侧都取不到才抛错并带出 Feign 真实返回原因
*
* @date 2026/9/11
*/
@Slf4j
@Service
public class WarehouseInfoApplicationService {
@Autowired
private SystemServiceFeign systemServiceFeign;
@Autowired
private ReservationStockInOrderMapper reservationStockInOrderMapper;
/**
* 获取仓库信息: Feign 优先, 失败走本地兜底; 两边都取不到抛 ServiceException
*
* @param warehouseId 仓库id
* @return 仓库信息(含编码/名称/单号生成规则/库存锁定模式等)
*/
public WarehouseFeignPO getWarehouseInfo(Long warehouseId) {
if (warehouseId == null) {
throw new ServiceException("仓库ID不能为空");
}
AjaxResult ajaxResult = null;
try {
ajaxResult = systemServiceFeign.getWarehouseInfoByWarehouseId(warehouseId);
} catch (Exception e) {
log.warn("获取仓库信息Feign调用异常, 走本地兜底查询, warehouseId={}, error={}", warehouseId, e.getMessage());
}
if (ajaxResult != null && "200".equals(String.valueOf(ajaxResult.get("code"))) && ajaxResult.get("data") != null) {
return com.alibaba.fastjson.JSON.parseObject(
com.alibaba.fastjson2.JSONObject.toJSONString(ajaxResult.get("data")), WarehouseFeignPO.class);
}
String remoteMsg = ajaxResult == null ? "Feign调用异常" : String.valueOf(ajaxResult.get("msg"));
log.warn("获取仓库信息Feign失败(返回: {}), 走本地兜底查询, warehouseId={}", remoteMsg, warehouseId);
WarehouseFeignPO local = reservationStockInOrderMapper.getWarehouseInfoFallback(warehouseId);
if (local == null) {
throw new ServiceException("获取仓库信息失败(Feign返回:" + remoteMsg + "), 且本地未查到仓库[warehouseId="
+ warehouseId + "], 请检查仓库是否存在/已启用");
}
return local;
}
}
@@ -7,9 +7,16 @@ import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.mhd.common.core.domain.dto.wlhy.TmsAddressMultiDTO;
import com.mhd.common.core.domain.dto.wlhy.TmsCargoInfoDTO;
import com.mhd.common.core.domain.dto.wlhy.TmsOrderAccount;
import com.mhd.common.core.exception.ServiceException;
import com.mhd.common.core.web.domain.AjaxResult;
import com.mhd.common.security.utils.SecurityUtils;
import com.mhd.oms.domain.businessDocumentAddressMulti.entity.BusinessDocumentAddressMulti;
import com.mhd.oms.domain.businessDocumentAddressMulti.repository.mapper.BusinessDocumentAddressMultiMapper;
import com.mhd.oms.domain.businessDocumentCargoMulti.entity.BusinessDocumentCargoMulti;
import com.mhd.oms.domain.businessDocumentCargoMulti.repository.mapper.BusinessDocumentCargoMultiMapper;
import com.mhd.oms.domain.businessDocumentOrder.entity.BusinessDocumentOrder;
import com.mhd.oms.domain.businessDocumentOrder.repository.mapper.BusinessDocumentOrderMapper;
import com.mhd.oms.domain.businessDocumentOrderAdjust.entity.BusinessDocumentOrderAdjust;
@@ -20,11 +27,13 @@ import com.mhd.oms.domain.businessOrderAccount.BusinessOrderAccount;
import com.mhd.oms.domain.businessOrderAccount.repository.mapper.BusinessOrderAccountMapper;
import com.mhd.oms.domain.executeOrder.entity.ExecuteOrder;
import com.mhd.oms.domain.executeOrder.repository.mapper.ExecuteOrderMapper;
import com.mhd.oms.interfaces.dto.businessDocumentAddressMulti.BusinessDocumentAddressMultiDTO;
import com.mhd.oms.interfaces.dto.businessDocumentOrderAdjust.BusinessDocumentOrderAdjustAuditDTO;
import com.mhd.oms.interfaces.dto.businessDocumentOrderAdjust.BusinessDocumentOrderAdjustDTO;
import com.mhd.oms.interfaces.dto.businessDocumentOrderAdjust.BusinessDocumentOrderAdjustQueryDTO;
import com.mhd.oms.interfaces.dto.businessDocumentOrderAdjust.BusinessDocumentOrderAdjustRecordDTO;
import com.mhd.system.api.WlhyServiceFeign;
import com.mhd.system.api.domain.TmsOrderAddDTO;
import com.mhd.system.api.model.LoginUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@@ -36,6 +45,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
@@ -46,16 +56,23 @@ import java.util.stream.Collectors;
* 2. 费用数据源 = 前端回传的费用明细 business_order_account(feeDetailList)
* 主表金额 deliveryFreight 为其 amount 合计(冗余汇总后端自动算)
* 3. 是否需审核一旦任一金额发生变化
* = feeDetailList 明细 amount 变化或主表金额列变化 -> 走业务主管审核
* = feeDetailList 明细 amount 变化或主表金额/费用列变化 -> 走业务主管审核
* 否则(仅改非金额字段) -> 直通
* 金额/费用字段涵盖: deliveryFreight / collectedFreight / uncollectedFreight /
* collectLessFreight / transportationUnitPrice / infoFee / freightUnitPrice /
* cargoInsuranceAmount / freightCharges / freightFee / unloadingFee / customsFee /
* shortageFee / miscellaneousFee / otherFee / detentionFee以及 settlementCurrency /
* paymentAccountId / accountName / accountInformation(结算币种+收款账户任一变化即视为费用变更)
* 判定口径: 以后端兜底比对为准(主表金额/费用列 + 明细 amount 合计)
* 前端 amountChanged 仅作提示后端比对结果与前端 OR 运算避免前端误传 false 绕过审核
* 4. 涉及金额提交 -> 仅把业务单 review_status 置为调整待审核(3)主记录 AUDIT_STATUS=0(待审核)
* 不更新业务单数据不影响其它状态(order_status / execute_status 不动)
* 5. 审核通过 -> 覆盖业务单(金额) + 替换费用明细review_status 一律置为 1(已审核)
* 不影响 order_status / execute_status
* 审核驳回 -> 不更新业务单数据review_status 还原为调整前值(主记录 before_review_status)可再次调整
* 审核驳回 -> 不更新业务单数据review_status 置为调整驳回(4)不影响 order_status/execute_status
* 可再次调整提交审核
* 6. 直通(未改金额) -> 覆盖业务单可编辑字段 + deliveryFreight(明细合计)并替换 business_order_account
* (逻辑删旧 is_delete=1 + 插新 is_delete=0)不改动任何审核/执行状态
* 7. 前端以 amountChanged 告知是否改金额(默认前端判定)未传时后端兜底比对
*
* 说明review_status 原枚举 0未审/1通过/2驳回新增 3=调整待审核不改变原有用例
*
@@ -70,12 +87,22 @@ public class BusinessDocumentOrderAdjustService {
private static final Integer AUDIT_PASS = 1; // 调整记录审核通过
private static final Integer AUDIT_REJECT = 2; // 调整记录驳回
/** 主表金额(费用)字段,任一变化触发审核 */
/** 业务单 review_status:调整待审核(提交涉及金额时置) */
private static final Long REVIEW_ADJUST_WAIT = 3L;
/** 业务单 review_status:调整驳回(审核驳回时置,与普通驳回 2 区分) */
private static final Long REVIEW_ADJUST_REJECT = 4L;
/** 主表金额(费用)字段任一变化触发审核
* : 运费相关 8 + 其他费用 8 + 结算币种 + 收款账户(3 字段)
* 任意一项变化即需要审核(对应前端"费用合计 / 运费 / 装卸费 / 仓租及柜租(走明细) /
* 保险 / 压车(走明细) / 运输处理费(走明细) / 报关费 / 其他 / 费用合计 / 结算币种 / 收款账户") */
private static final List<String> FEE_FIELDS = Arrays.asList(
"deliveryFreight", "collectedFreight", "uncollectedFreight", "collectLessFreight",
"transportationUnitPrice", "infoFee", "freightUnitPrice", "cargoInsuranceAmount",
"freightCharges", "freightFee", "unloadingFee", "customsFee", "shortageFee",
"miscellaneousFee", "otherFee", "detentionFee");
"miscellaneousFee", "otherFee", "detentionFee",
// 结算币种 + 收款账户 任一变化也视为费用变更需审核
"settlementCurrency", "paymentAccountId", "accountName", "accountInformation");
/** 不允许被前端调整覆盖的系统字段 */
private static final List<String> PROTECTED_FIELDS = Arrays.asList(
@@ -83,7 +110,7 @@ public class BusinessDocumentOrderAdjustService {
"orderStatus", "reviewStatus", "reviewId", "review", "reviewTime", "reviewOpinion",
"salesManager", "auditStatus", "auditBy", "auditTime",
"executeStatus", "executeId", "executeTime", "executor",
"executeOrderId", "executeOrderNumber", "adjustStatus",
"executeOrderId", "executeOrderNumber",
"isDelete", "delFlag", "createBy", "createByName", "createTime",
"updateBy", "updateByName", "updateTime",
"pushStatus", "pushTime", "pushBy", "pushByName",
@@ -112,6 +139,12 @@ public class BusinessDocumentOrderAdjustService {
@Autowired
private ExecuteOrderMapper executeOrderMapper;
@Autowired
private BusinessDocumentCargoMultiMapper businessDocumentCargoMultiMapper;
@Autowired
private BusinessDocumentAddressMultiMapper businessDocumentAddressMultiMapper;
@Autowired
private WlhyServiceFeign wlhyServiceFeign;
@@ -136,12 +169,20 @@ public class BusinessDocumentOrderAdjustService {
.eq(BusinessOrderAccount::getOrderId, dbOrder.getId())
.eq(BusinessOrderAccount::getIsDelete, 0));
// 2. 调整后的费用明细(前端) null 则视为保持库中不变
// 2. 调整后的费用明细(前端) null 则视为保持库中不变
// 兼容前端两种传法顶层 feeDetailList或整单回传 order.businessOrderAccountList
List<BusinessOrderAccount> newFeeList = dto.getFeeDetailList();
if ((newFeeList == null || newFeeList.isEmpty()) && front.getBusinessOrderAccountList() != null) {
newFeeList = front.getBusinessOrderAccountList();
}
// 3. 生成"调整后"业务单对象DB原值 + 前端可编辑字段覆盖(忽略null)
BusinessDocumentOrder afterOrder = buildAfterOrder(dbOrder, front);
// 3.1 地址兼容前端把地址放在 shipment/receipt loadingAddressList/unloadAddressList
// 映射到 loading*/unload* 扁平字段(与下单主流程 BusinessDocumentOrderDomainService 一致口径)
applyAddressMapping(afterOrder, front);
// 4. 若提供了费用明细 deliveryFreight(合计)=明细 amount 之和并联动到 afterOrder
boolean hasFeeDetail = newFeeList != null && !newFeeList.isEmpty();
if (hasFeeDetail) {
@@ -149,14 +190,10 @@ public class BusinessDocumentOrderAdjustService {
afterOrder.setDeliveryFreight(sum);
}
// 5. 判定是否修改了金额前端 amountChanged 为准(前端整单回传已自行判定)
// 前端未传则后端兜底比对(主表金额列 明细 amount 变化)
boolean amountChanged;
if (dto.getAmountChanged() != null) {
amountChanged = dto.getAmountChanged();
} else {
amountChanged = isAmountChanged(dbOrder, front, hasFeeDetail, dbFeeList, newFeeList);
}
// 5. 判定是否修改了金额后端兜底比对为准避免前端误传 false 绕过审核
// 前端 true 也尊重后端比对结果与前端 OR 运算任一为真则需审核
boolean backendChanged = isAmountChanged(dbOrder, front, hasFeeDetail, dbFeeList, newFeeList);
boolean amountChanged = backendChanged || Boolean.TRUE.equals(dto.getAmountChanged());
// 6. 写主记录
LoginUser loginUser = SecurityUtils.getLoginUser();
@@ -188,7 +225,7 @@ public class BusinessDocumentOrderAdjustService {
// 8b. 涉及金额仅把业务单 review_status 置为"调整待审核(3)"其余状态(exec/orderStatus)不动不更新业务单数据
BusinessDocumentOrder statusOnly = new BusinessDocumentOrder();
statusOnly.setId(dbOrder.getId());
statusOnly.setReviewStatus(3L); // 调整待审核
statusOnly.setReviewStatus(REVIEW_ADJUST_WAIT); // 调整待审核
businessDocumentOrderMapper.updateById(statusOnly);
}
@@ -250,11 +287,27 @@ public class BusinessDocumentOrderAdjustService {
/**
* 按业务单查询调整记录(供订单详情页"调整记录"Tab用)
* 每条记录同时带出调整后明细快照(detail)前端可自包含展示无需再单独调详情接口
*/
public List<BusinessDocumentOrderAdjustRecordDTO> listAdjustRecordByOrder(Long orderId) {
BusinessDocumentOrderAdjustQueryDTO query = new BusinessDocumentOrderAdjustQueryDTO();
query.setOrderId(orderId);
return listAdjustRecord(query);
List<BusinessDocumentOrderAdjustRecordDTO> list = listAdjustRecord(query);
if (list == null || list.isEmpty()) {
return list;
}
// 批量取明细快照按 adjust_id 一次性拼上避免 N+1
List<Long> adjustIds = list.stream()
.map(BusinessDocumentOrderAdjustRecordDTO::getId)
.collect(Collectors.toList());
List<BusinessDocumentOrderAdjustDetail> details = adjustDetailMapper.selectList(
new LambdaQueryWrapper<BusinessDocumentOrderAdjustDetail>()
.in(BusinessDocumentOrderAdjustDetail::getAdjustId, adjustIds)
.eq(BusinessDocumentOrderAdjustDetail::getDelFlag, 1));
Map<Long, BusinessDocumentOrderAdjustDetail> detailMap = details.stream()
.collect(Collectors.toMap(BusinessDocumentOrderAdjustDetail::getAdjustId, d -> d, (a, b) -> a));
list.forEach(vo -> vo.setDetail(detailMap.get(vo.getId())));
return list;
}
/**
@@ -353,6 +406,7 @@ public class BusinessDocumentOrderAdjustService {
syncTmsAsync(order.getGoodsNumber(), toSave);
} else {
adjust.setAuditStatus(AUDIT_REJECT);
adjust.setIsAudit(1); // 走到审核即说明该调整需要审核保持 is_audit audit_status 语义一致
adjust.setAuditBy(auditBy);
adjust.setAuditByName(auditByName);
adjust.setAuditTime(now);
@@ -361,10 +415,11 @@ public class BusinessDocumentOrderAdjustService {
adjust.setUpdateTime(now);
adjustMapper.updateById(adjust);
// 驳回不更新业务单数据仅把 review_status 还原为调整前值(不影响其它状态)
// 驳回不更新业务单数据仅把 review_status 置为调整驳回(4)
// 不影响 order_status / execute_status 等其它单据状态可再次调整提交审核
BusinessDocumentOrder statusOnly = new BusinessDocumentOrder();
statusOnly.setId(adjust.getOrderId());
statusOnly.setReviewStatus(adjust.getBeforeReviewStatus() == null ? 0L : adjust.getBeforeReviewStatus());
statusOnly.setReviewStatus(REVIEW_ADJUST_REJECT);
businessDocumentOrderMapper.updateById(statusOnly);
}
return AjaxResult.success();
@@ -383,6 +438,50 @@ public class BusinessDocumentOrderAdjustService {
return after;
}
/**
* 地址兼容映射前端整单回传时地址在 shipment/receipt loadingAddressList/unloadAddressList
* 这里统一映射到 business_document_order loading unload 系列扁平字段
* 口径与 BusinessDocumentOrderDomainService 下单主流程保持一致
* 装货取第一条地址卸货取最后一条地址
*/
private void applyAddressMapping(BusinessDocumentOrder after, BusinessDocumentOrder front) {
BusinessDocumentAddressMultiDTO loading = null;
BusinessDocumentAddressMultiDTO unloading = null;
// 优先取 shipment/receipt 单对象其次取 loadingAddressList/unloadAddressList 集合
if (front.getShipment() != null) {
loading = front.getShipment();
} else if (front.getLoadingAddressList() != null && !front.getLoadingAddressList().isEmpty()) {
loading = front.getLoadingAddressList().get(0);
}
if (front.getReceipt() != null) {
unloading = front.getReceipt();
} else if (front.getUnloadAddressList() != null && !front.getUnloadAddressList().isEmpty()) {
unloading = front.getUnloadAddressList().get(front.getUnloadAddressList().size() - 1);
}
if (loading != null) {
after.setLoadingAreaId(loading.getAreaCode());
after.setLoadingName(loading.getAreaName() != null ? loading.getAreaName() : loading.getAreaCode());
after.setLoadingAddress(loading.getAddress());
after.setLoadingLongitude(loading.getLongitude());
after.setLoadingLatitude(loading.getLatitude());
after.setFreighterName(loading.getContactName());
after.setLoadingPhone(loading.getContactPhone());
after.setLoadingDate(loading.getLayDate());
}
if (unloading != null) {
after.setUnloadAreaId(unloading.getAreaCode());
after.setUnloadName(unloading.getAreaName() != null ? unloading.getAreaName() : unloading.getAreaCode());
after.setUnloadAddress(unloading.getAddress());
after.setUnloadLongitude(unloading.getLongitude());
after.setUnloadLatitude(unloading.getLatitude());
after.setDischargerName(unloading.getContactName());
after.setUnloadPhone(unloading.getContactPhone());
after.setUnloadingData(unloading.getLayDate());
}
}
private BusinessDocumentOrderAdjust buildAdjust(BusinessDocumentOrder dbOrder, LoginUser u, Date now,
boolean amountChanged, String reason) {
BusinessDocumentOrderAdjust adjust = new BusinessDocumentOrderAdjust();
@@ -421,21 +520,25 @@ public class BusinessDocumentOrderAdjustService {
}
/**
* 是否修改了金额主表金额列变化 明细 amount 变化
* 是否修改了金额主表金额/费用列变化 明细 amount 变化
*/
private boolean isAmountChanged(BusinessDocumentOrder dbOrder, BusinessDocumentOrder front,
boolean hasFeeDetail, List<BusinessOrderAccount> dbFeeList,
List<BusinessOrderAccount> newFeeList) {
// a) 主表金额列比对
// a) 主表金额/费用列比对金额列用 BigDecimal 比对字符串列用 equals 比对
boolean mainChanged = FEE_FIELDS.stream().anyMatch(field -> {
Object newVal = BeanUtil.getFieldValue(front, field);
if (newVal == null) {
return false;
}
Object oldVal = BeanUtil.getFieldValue(dbOrder, field);
BigDecimal old = oldVal == null ? BigDecimal.ZERO : (BigDecimal) oldVal;
BigDecimal now = (BigDecimal) newVal;
return old.compareTo(now) != 0;
if (newVal instanceof BigDecimal || oldVal instanceof BigDecimal) {
BigDecimal old = oldVal == null ? BigDecimal.ZERO : new BigDecimal(oldVal.toString());
BigDecimal now = new BigDecimal(newVal.toString());
return old.compareTo(now) != 0;
}
// 字符串字段(结算币种/收款账户等)
return !newVal.toString().equals(oldVal == null ? "" : oldVal.toString());
});
if (mainChanged) {
return true;
@@ -505,6 +608,74 @@ public class BusinessDocumentOrderAdjustService {
businessOrderAccountMapper.insert(acc);
}
}
// 同步地址明细表(business_document_address_multi)逻辑删旧 + 按调整后扁平字段插新
syncAddressDetail(orderId, toSave, loginUser, now);
}
/**
* 同步地址明细表调整后业务单的装/卸货扁平字段写回 business_document_address_multi
* applyAddressMapping 口径一致(装货取第一条卸货取最后一条)此处各保留一条
*/
private void syncAddressDetail(Long orderId, BusinessDocumentOrder toSave, LoginUser loginUser, Date now) {
// 逻辑删旧
List<BusinessDocumentAddressMulti> oldList = businessDocumentAddressMultiMapper.selectList(
new LambdaQueryWrapper<BusinessDocumentAddressMulti>()
.eq(BusinessDocumentAddressMulti::getOrderId, String.valueOf(orderId))
.eq(BusinessDocumentAddressMulti::getDelFlag, 1));
if (oldList != null) {
for (BusinessDocumentAddressMulti old : oldList) {
old.setDelFlag(0);
old.setUpdateBy(loginUser != null ? loginUser.getUserid() : null);
old.setUpdateTime(now);
businessDocumentAddressMultiMapper.updateById(old);
}
}
Long userId = loginUser != null ? loginUser.getUserid() : null;
// 装货
BusinessDocumentAddressMulti loading = new BusinessDocumentAddressMulti();
loading.setOrderId(String.valueOf(orderId));
loading.setAreaCode(toSave.getLoadingAreaId());
loading.setAreaName(toSave.getLoadingName());
loading.setAddress(toSave.getLoadingAddress());
loading.setLongitude(toSave.getLoadingLongitude());
loading.setLatitude(toSave.getLoadingLatitude());
loading.setContactName(toSave.getFreighterName());
loading.setContactPhone(toSave.getLoadingPhone());
loading.setLayDate(toSave.getLoadingDate());
loading.setLayType(1L);
loading.setDelFlag(1);
loading.setOrganizationId(toSave.getOrganizationId());
loading.setOrganizationName(toSave.getOrganizationName());
loading.setTopOrganizationId(toSave.getTopOrganizationId());
loading.setCreateBy(userId);
loading.setCreateTime(now);
loading.setUpdateBy(userId);
loading.setUpdateTime(now);
businessDocumentAddressMultiMapper.insert(loading);
// 卸货
BusinessDocumentAddressMulti unload = new BusinessDocumentAddressMulti();
unload.setOrderId(String.valueOf(orderId));
unload.setAreaCode(toSave.getUnloadAreaId());
unload.setAreaName(toSave.getUnloadName());
unload.setAddress(toSave.getUnloadAddress());
unload.setLongitude(toSave.getUnloadLongitude());
unload.setLatitude(toSave.getUnloadLatitude());
unload.setContactName(toSave.getDischargerName());
unload.setContactPhone(toSave.getUnloadPhone());
unload.setLayDate(toSave.getUnloadingData());
unload.setLayType(2L);
unload.setDelFlag(1);
unload.setOrganizationId(toSave.getOrganizationId());
unload.setOrganizationName(toSave.getOrganizationName());
unload.setTopOrganizationId(toSave.getTopOrganizationId());
unload.setCreateBy(userId);
unload.setCreateTime(now);
unload.setUpdateBy(userId);
unload.setUpdateTime(now);
businessDocumentAddressMultiMapper.insert(unload);
}
private void copySnapshot(BusinessDocumentOrder order, BusinessDocumentOrderAdjustDetail detail) {
@@ -545,10 +716,111 @@ public class BusinessDocumentOrderAdjustService {
if (executeOrders == null || executeOrders.isEmpty()) {
return;
}
log.info("业务单[{}]调整完成,关联执行单[{}]。如需回写TMS请接入TMS改费Feign。", goodsNumber,
executeOrders.stream().map(ExecuteOrder::getGoodsNumber).collect(Collectors.toList()));
for (ExecuteOrder executeOrder : executeOrders) {
// 仅已下发 TMS(存在 TMS 单号)的执行单需要同步修改
if (StrUtil.isBlank(executeOrder.getTmsOrderNumber())) {
continue;
}
syncTmsOrder(executeOrder, after);
}
} catch (Exception e) {
log.error("同步TMS失败,不影响主流程", e);
}
}
/**
* 同步修改单个 TMS 订单按调整后业务单重新组装 TmsOrderAddDTO
* executeOrderNumber 定位 TMS 订单调用 TMS /feign/order/update 接口
*/
private void syncTmsOrder(ExecuteOrder executeOrder, BusinessDocumentOrder after) {
TmsOrderAddDTO tmsOrderAddDTO = new TmsOrderAddDTO();
BeanUtil.copyProperties(after, tmsOrderAddDTO);
// id OMS 业务单主键TMS 侧用作 tms_order_account.order_id 口径
// TMS update 在覆盖前会保留 tms_order 真实主键不会被污染
tmsOrderAddDTO.setId(String.valueOf(after.getId()));
// TMS 侧按执行单号定位订单
tmsOrderAddDTO.setExecuteOrderId(String.valueOf(executeOrder.getId()));
tmsOrderAddDTO.setExecuteOrderNumber(executeOrder.getGoodsNumber());
tmsOrderAddDTO.setGoodsNumber(after.getGoodsNumber());
tmsOrderAddDTO.setOrderSource("0");
// TMS update appointShipperId(业务单货主) 定位货主需显式带上口径与下单一致
if (StrUtil.isNotBlank(after.getAppointShipperId())) {
tmsOrderAddDTO.setAppointShipperId(after.getAppointShipperId());
}
if (after.getAppointShipper() != null) {
tmsOrderAddDTO.setAppointShipper(after.getAppointShipper());
}
if (after.getSzwlUserId() != null) {
tmsOrderAddDTO.setSzwlUserId(after.getSzwlUserId());
}
// 货物信息
List<BusinessDocumentCargoMulti> cargoList = businessDocumentCargoMultiMapper.selectList(
new LambdaQueryWrapper<BusinessDocumentCargoMulti>()
.eq(BusinessDocumentCargoMulti::getOrderId, String.valueOf(after.getId()))
.eq(BusinessDocumentCargoMulti::getDelFlag, 1));
List<TmsCargoInfoDTO> cargoInfoList = new ArrayList<>();
if (cargoList != null) {
for (BusinessDocumentCargoMulti cargo : cargoList) {
TmsCargoInfoDTO dto = new TmsCargoInfoDTO();
BeanUtil.copyProperties(cargo, dto);
cargoInfoList.add(dto);
}
}
tmsOrderAddDTO.setCargoInfoList(cargoInfoList);
// 装货地址统一以调整后业务单的扁平字段为准避免明细表(business_document_address_multi)旧值覆盖
TmsAddressMultiDTO loadingDto = new TmsAddressMultiDTO();
loadingDto.setAreaCode(after.getLoadingAreaId());
loadingDto.setAreaName(after.getLoadingName());
loadingDto.setAddress(after.getLoadingAddress());
loadingDto.setLongitude(after.getLoadingLongitude());
loadingDto.setLatitude(after.getLoadingLatitude());
loadingDto.setContactName(after.getFreighterName());
loadingDto.setContactPhone(after.getLoadingPhone());
loadingDto.setLayDate(after.getLoadingDate());
loadingDto.setLayType(1);
List<TmsAddressMultiDTO> loadingAddressList = new ArrayList<>();
loadingAddressList.add(loadingDto);
tmsOrderAddDTO.setLoadingAddressList(loadingAddressList);
// 卸货地址同样以调整后业务单的扁平字段为准
TmsAddressMultiDTO unloadDto = new TmsAddressMultiDTO();
unloadDto.setAreaCode(after.getUnloadAreaId());
unloadDto.setAreaName(after.getUnloadName());
unloadDto.setAddress(after.getUnloadAddress());
unloadDto.setLongitude(after.getUnloadLongitude());
unloadDto.setLatitude(after.getUnloadLatitude());
unloadDto.setContactName(after.getDischargerName());
unloadDto.setContactPhone(after.getUnloadPhone());
unloadDto.setLayDate(after.getUnloadingData());
unloadDto.setLayType(2);
List<TmsAddressMultiDTO> unloadAddressList = new ArrayList<>();
unloadAddressList.add(unloadDto);
tmsOrderAddDTO.setUnloadAddressList(unloadAddressList);
// 费用明细
List<BusinessOrderAccount> accountList = businessOrderAccountMapper.selectList(
new LambdaQueryWrapper<BusinessOrderAccount>()
.eq(BusinessOrderAccount::getOrderId, after.getId())
.eq(BusinessOrderAccount::getIsDelete, 0));
List<TmsOrderAccount> tmsOrderAccountList = new ArrayList<>();
if (accountList != null) {
for (BusinessOrderAccount acc : accountList) {
TmsOrderAccount dto = new TmsOrderAccount();
BeanUtil.copyProperties(acc, dto);
tmsOrderAccountList.add(dto);
}
}
tmsOrderAddDTO.setTmsOrderAccountList(tmsOrderAccountList);
AjaxResult result = wlhyServiceFeign.update(tmsOrderAddDTO);
if (result != null && "200".equals(String.valueOf(result.get("code")))) {
log.info("调整后同步修改TMS订单成功,执行单[{}],业务单[{}]", executeOrder.getGoodsNumber(), after.getGoodsNumber());
} else {
log.error("调整后同步修改TMS订单失败,执行单[{}],返回: {}", executeOrder.getGoodsNumber(),
result == null ? "null" : result.get("msg"));
}
}
}
@@ -11,6 +11,7 @@ import com.mhd.oms.domain.outEntrustOrder.repository.po.OutEntrustOrderPO;
import com.mhd.oms.domain.outEntrustOrder.repository.todo.OutEntrustOrderDO;
import com.mhd.oms.domain.outEntrustOrder.repository.todo.OutEntrustOrderDetailDO;
import com.mhd.oms.domain.outEntrustOrder.service.OutEntrustOrderDomainService;
import com.mhd.oms.domain.outEntrustOrder.repository.listener.OutEntrustExcelParseService;
import com.mhd.system.api.SystemServiceFeign;
import com.mhd.system.api.WmsServiceFeign;
import com.mhd.system.api.domain.NoticeMaterialDetailDTO;
@@ -20,8 +21,10 @@ import com.mhd.system.api.model.LoginUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
@@ -46,9 +49,15 @@ public class OutEntrustOrderApplicationService {
@Resource
private SystemServiceFeign systemServiceFeign;
@Resource
private com.mhd.oms.application.service.WarehouseInfoApplicationService warehouseInfoApplicationService;
@Resource
private WmsServiceFeign wmsServiceFeign;
@Resource
private OutEntrustExcelParseService outEntrustExcelParseService;
/**
* 分页查询出仓委托单列表(不做默认状态过滤,显示全部数据)
*/
@@ -105,6 +114,45 @@ public class OutEntrustOrderApplicationService {
return outEntrustOrderDomainService.insert(outEntrustOrderDO);
}
/**
* 导入出仓委托单(模板与WMS出库管理导入一致前11行表头+第12行起明细)
* 解析校验通过后复用新增链路落库生成CKWT委托单号状态待审核/未下发
*
* @return 校验错误信息空串表示导入成功
*/
public StringBuilder importOutEntrustOrder(MultipartFile file, Long warehouseId, String warehouseCode, String warehouseName) throws IOException {
//解析+校验(客户解析/物料匹配/字典转换/必填项)
OutEntrustExcelParseService.ParseResult parseResult =
outEntrustExcelParseService.parseOutEntrustOrderExcel(file, warehouseName, warehouseCode, warehouseId);
if (parseResult.getErrorMsg().length() > 0) {
return parseResult.getErrorMsg();
}
OutEntrustOrderDO outEntrustOrderDO = parseResult.getOrder();
//审计信息(与WMS出库导入一致导入人=当前登录人)
LoginUser loginUser = SecurityUtils.getLoginUser();
if (loginUser != null) {
String userRealName;
if (loginUser.getUserPo() != null && loginUser.getUserPo().getUserName() != null
&& !loginUser.getUserPo().getUserName().trim().isEmpty()) {
userRealName = loginUser.getUserPo().getUserName();
} else if (loginUser.getRealname() != null && !loginUser.getRealname().trim().isEmpty()) {
userRealName = loginUser.getRealname();
} else {
userRealName = loginUser.getUsername();
}
Date now = new Date();
outEntrustOrderDO.setCreateBy(loginUser.getUserid());
outEntrustOrderDO.setCreateByName(userRealName);
outEntrustOrderDO.setCreateTime(now);
outEntrustOrderDO.setUpdateBy(loginUser.getUserid());
outEntrustOrderDO.setUpdateByName(userRealName);
outEntrustOrderDO.setUpdateTime(now);
}
//复用新增链路必填校验默认普通出库仓库校验组织信息汇总申报数量生成CKWT委托单号(待审核/未下发)
insert(outEntrustOrderDO);
return new StringBuilder();
}
/**
* 修改出仓委托单(仅待审核/已驳回且未下发可修改已驳回单编辑后状态重置为待审核)
*/
@@ -352,14 +400,10 @@ public class OutEntrustOrderApplicationService {
}
/**
* 仓库信息校验并回填编码/名称
* 仓库信息校验并回填编码/名称 (Feign失败走本地跨库兜底)
*/
private void setWarehouseInfo(OutEntrustOrderDO outEntrustOrderDO) {
AjaxResult ajaxResult = systemServiceFeign.getWarehouseInfoByWarehouseId(outEntrustOrderDO.getWarehouseId());
if (ajaxResult == null || !"200".equals(String.valueOf(ajaxResult.get("code")))) {
throw new ServiceException("获取仓库信息失败,请检查仓库是否有效");
}
WarehouseFeignPO warehouseFeignPO = JSONUtil.toBean(JSONUtil.toJsonStr(ajaxResult.get("data")), WarehouseFeignPO.class);
WarehouseFeignPO warehouseFeignPO = warehouseInfoApplicationService.getWarehouseInfo(outEntrustOrderDO.getWarehouseId());
if (warehouseFeignPO == null || Objects.isNull(warehouseFeignPO.getWarehouseId())) {
throw new ServiceException("仓库不存在,请重新选择");
}
@@ -8,6 +8,7 @@ import com.mhd.common.core.web.domain.BaseVOEntity;
import java.math.BigDecimal;
import lombok.Data;
import java.util.Date;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.format.annotation.DateTimeFormat;
@@ -659,7 +660,7 @@ public class BusinessDocumentOrder extends BaseVOEntity{
@Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()")
private Date documentDate;
@ApiModelProperty( "审核状态 0-未审核 1-审核通过 2-已驳回 3-调整待审核")
@ApiModelProperty( "审核状态 0-未审核 1-审核通过 2-已驳回 3-调整待审核 4-调整驳回")
private Long reviewStatus;
@ApiModelProperty("审核时间")
@@ -826,6 +827,34 @@ public class BusinessDocumentOrder extends BaseVOEntity{
@Excel(name = "车次")
private String shuttleNo;
@ApiModelProperty("业务单调整状态 0-无 1-调整待审核 2-调整驳回 3-调整已通过")
private Integer adjustStatus;
/**
* 前端整单回传时携带的费用明细(business_order_account)非表字段仅用于订单调整入参接收
* 与顶层 feeDetailList 二选一后端在 adjust() 中做回退兼容
*/
@com.baomidou.mybatisplus.annotation.TableField(exist = false)
private List<com.mhd.oms.domain.businessOrderAccount.BusinessOrderAccount> businessOrderAccountList;
/**
* 前端整单回传时的装货地址(单个)非表字段仅用于订单调整入参接收与映射到 loading* 扁平字段
*/
@com.baomidou.mybatisplus.annotation.TableField(exist = false)
private com.mhd.oms.interfaces.dto.businessDocumentAddressMulti.BusinessDocumentAddressMultiDTO shipment;
/**
* 前端整单回传时的卸货地址(单个)非表字段仅用于订单调整入参接收与映射到 unload* 扁平字段
*/
@com.baomidou.mybatisplus.annotation.TableField(exist = false)
private com.mhd.oms.interfaces.dto.businessDocumentAddressMulti.BusinessDocumentAddressMultiDTO receipt;
/**
* 前端整单回传时的装货地址集合非表字段仅用于订单调整入参接收与映射
*/
@com.baomidou.mybatisplus.annotation.TableField(exist = false)
private List<com.mhd.oms.interfaces.dto.businessDocumentAddressMulti.BusinessDocumentAddressMultiDTO> loadingAddressList;
/**
* 前端整单回传时的卸货地址集合非表字段仅用于订单调整入参接收与映射
*/
@com.baomidou.mybatisplus.annotation.TableField(exist = false)
private List<com.mhd.oms.interfaces.dto.businessDocumentAddressMulti.BusinessDocumentAddressMultiDTO> unloadAddressList;
}
@@ -39,8 +39,8 @@ public interface BusinessDocumentOrderMapper extends BaseMapper<BusinessDocument
String getPushVerifyByOrgId(@Param("organizationId") Long organizationId);
/**
* 统计指定日期前缀(yyMMdd)下已生成的新格式业务单号数量
* 统计新格式(长度<=12)过滤历史16位旧数据保证序号每日从001正确累计
* 查询指定日期前缀(yyMMdd)下已生成的新格式业务单号列表
* 新格式(长度<=12)过滤历史16位旧数据供取号器解析当日最大序号
*/
int countByGoodsNumberPrefix(@Param("prefix") String prefix);
List<String> selectGoodsNumbersByPrefix(@Param("prefix") String prefix);
}
@@ -237,11 +237,8 @@ public class BusinessDocumentOrderImpl extends ServiceImpl<BusinessDocumentOrder
subOrder.setRecipientName(user.getUserName());
//生成子单货源单号yyMMdd + 当日顺序号
String orderCode = goodsNumberGenerator.generateUniqueGoodsNumber(subOrder.getDocumentDate());
subOrder.setGoodsNumber(orderCode);
businessDocumentOrderMapper.insert(subOrder);
//生成子单货源单号yyMMdd + 当日顺序号并落库, 撞唯一索引自动重新取号重试
goodsNumberGenerator.insertWithUniqueGoodsNumber(subOrder);
subOrderCount++; // 增加子单计数
// 保存子单的地址信息到BUSINESS_DOCUMENT_ADDRESS_MULTI表
@@ -285,24 +285,9 @@ public class BusinessDocumentOrderDomainService {
}
}
//生成货源单号yyMMdd + 当日顺序号每日重置
String orderCode = goodsNumberGenerator.generateUniqueGoodsNumber(tmsOrder.getDocumentDate());
tmsOrder.setGoodsNumber(orderCode);
ReservationStockOutOrder reservationStockOutOrder = reservationStockOutOrderMapper.selectOne(new LambdaQueryWrapper<ReservationStockOutOrder>()
.eq(ReservationStockOutOrder::getOutOrderNumber, tmsOrder.getInOrderNumber())
.eq(ReservationStockOutOrder::getDelFlag, 1));
if (reservationStockOutOrder != null){
reservationStockOutOrder.setBusinessNumber(tmsOrder.getGoodsNumber());
reservationStockOutOrderMapper.updateById(reservationStockOutOrder);
}
ReservationStockInOrder reservationStockInOrder = reservationStockInOrderMapper.selectOne(new LambdaQueryWrapper<ReservationStockInOrder>()
.eq(ReservationStockInOrder::getInOrderNumber, tmsOrder.getInOrderNumber())
.eq(ReservationStockInOrder::getDelFlag, 1));
if (reservationStockInOrder != null){
reservationStockInOrder.setBusinessNumber(tmsOrder.getGoodsNumber());
reservationStockInOrderMapper.updateById(reservationStockInOrder);
}
//生成货源单号并落库见下方 insertWithUniqueGoodsNumber:
//取号与插单合并为原子重试(撞goods_number唯一索引自动重新取号),
//出库/入库业务单的单号回填也移至插单成功之后,避免插单失败留下脏回填
List<BusinessDocumentAddressMultiDTO> loadingAddressList = businessDocumentOrderDO.getLoadingAddressList();//装货地信息集合
List<BusinessDocumentAddressMultiDTO> unloadAddressList = businessDocumentOrderDO.getUnloadAddressList();//卸货地信息集合
@@ -426,7 +411,24 @@ public class BusinessDocumentOrderDomainService {
//一天后
tmsOrder.setDocumentDate(businessDocumentOrderDO.getDocumentDate());
boolean istrue = businessDocumentOrderMapper.insert(tmsOrder) > 0;
//生成货源单号yyMMdd + 当日顺序号每日重置并落库, 撞唯一索引自动重新取号重试
boolean istrue = goodsNumberGenerator.insertWithUniqueGoodsNumber(tmsOrder);
//落库成功后将运输业务单号回填至关联的出库/入库业务单
ReservationStockOutOrder reservationStockOutOrder = reservationStockOutOrderMapper.selectOne(new LambdaQueryWrapper<ReservationStockOutOrder>()
.eq(ReservationStockOutOrder::getOutOrderNumber, tmsOrder.getInOrderNumber())
.eq(ReservationStockOutOrder::getDelFlag, 1));
if (reservationStockOutOrder != null){
reservationStockOutOrder.setBusinessNumber(tmsOrder.getGoodsNumber());
reservationStockOutOrderMapper.updateById(reservationStockOutOrder);
}
ReservationStockInOrder reservationStockInOrder = reservationStockInOrderMapper.selectOne(new LambdaQueryWrapper<ReservationStockInOrder>()
.eq(ReservationStockInOrder::getInOrderNumber, tmsOrder.getInOrderNumber())
.eq(ReservationStockInOrder::getDelFlag, 1));
if (reservationStockInOrder != null){
reservationStockInOrder.setBusinessNumber(tmsOrder.getGoodsNumber());
reservationStockInOrderMapper.updateById(reservationStockInOrder);
}
if (hasDetails) {
for (BusinessDeliveryDetailsLinkDTO businessDeliveryDetailsLinkDTO : businessDeliveryDetailsLinkDTOList) {
@@ -5,23 +5,44 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.mhd.common.core.exception.ServiceException;
import com.mhd.oms.domain.businessDocumentOrder.entity.BusinessDocumentOrder;
import com.mhd.oms.domain.businessDocumentOrder.repository.mapper.BusinessDocumentOrderMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 业务单据订单(goodsNumber)单号生成器
* 规则: yyMMdd(取自单据日期) + 当日顺序号(每日重置, 3位起步, 超999自动扩位)
* 示例: 2608040012608049992608041000
*
* 唯一性保障(不依赖唯一索引, 三层防线):
* 1) 取号: 当日已生成单号 MAX+1 作为基线 + 候选号存在性校验后移收敛
* (对齐入库单号修复先例 ReservationStockInOrderDomainService#generateReserveOrderNumber)
* 2) 落库: 按日期前缀分段的JVM锁串行化"取号+插单", 消除本实例内并发审核/
* 批量取号的check-then-act竞态(多实例部署时本层只覆盖各自实例)
* 3) 自检自愈: 插单后立即校验同号数量, >1 说明与并发插入撞号, 给本单重新
* 取号改号, 兜住跨实例残余竞态
* 若日后补建goods_number唯一索引( sql/20260906_oms_business_goods_number_unique.sql),
* 撞号将抛DuplicateKeyException由重试逻辑接住, 与上述防线叠加互不冲突
*/
@Slf4j
@Component
public class GoodsNumberGenerator {
private static final int MAX_RETRY = 100;
/**
* 取号+插单分段锁: 按单号日期前缀(yyMMdd)分段, 同日串行跨日并行,
* 锁对象常驻(每日新增1个, 增量可忽略)
*/
private static final Map<String, Object> LOCK_STRIPES = new ConcurrentHashMap<>();
@Autowired
private BusinessDocumentOrderMapper businessDocumentOrderMapper;
@@ -33,8 +54,21 @@ public class GoodsNumberGenerator {
Date date = (documentDate == null) ? new Date() : documentDate;
String datePart = DateUtil.format(date, "yyMMdd");
// count 只查一次作为序号基线
long seq = (long) businessDocumentOrderMapper.countByGoodsNumberPrefix(datePart) + 1;
// 当日已生成单号的最大序号作为基线(MAX+1而非COUNT+1, 防脏数据/序号错位导致回退复用)
List<String> existNumbers = businessDocumentOrderMapper.selectGoodsNumbersByPrefix(datePart);
long seq = 0;
for (String existNumber : existNumbers) {
if (existNumber == null || existNumber.length() <= datePart.length()) {
continue;
}
String numStr = existNumber.substring(datePart.length());
if (!numStr.matches("\\d+")) {
continue; // 防脏数据(非纯数字后缀)导致解析失败
}
seq = Math.max(seq, Long.parseLong(numStr));
}
seq = seq + 1;
int retryCount = 0;
while (retryCount < MAX_RETRY) {
String orderCode = datePart + String.format("%03d", seq);
@@ -46,8 +80,60 @@ public class GoodsNumberGenerator {
if (CollectionUtils.isEmpty(exist)) {
return orderCode;
}
seq++; // 关键查重失败序号往后递增找空位而不是重新count
seq++; // 关键查重失败序号往后递增找空位而不是重新取基线
}
throw new ServiceException("生成唯一业务单号失败,已达到最大重试次数:" + MAX_RETRY);
}
}
/**
* 生成唯一业务单号并落库: JVM分段锁内完成"取号+插单",
* 插入撞goods_number唯一索引时自动重新取号重试(索引为可选兜底),
* 插单成功后自检同号并自愈改号
* 供业务单创建入口(手动/审核新增审核拆单预约确认)统一使用
* @return 是否插入成功(mapper.insert结果)
*/
public boolean insertWithUniqueGoodsNumber(BusinessDocumentOrder order) {
Date date = (order.getDocumentDate() == null) ? new Date() : order.getDocumentDate();
String lockKey = DateUtil.format(date, "yyMMdd");
synchronized (LOCK_STRIPES.computeIfAbsent(lockKey, k -> new Object())) {
int retryCount = 0;
while (retryCount < MAX_RETRY) {
retryCount++;
order.setGoodsNumber(generateUniqueGoodsNumber(order.getDocumentDate()));
try {
boolean inserted = businessDocumentOrderMapper.insert(order) > 0;
// 插单后自检自愈: 无唯一索引时跨实例并发窗口内的撞号双方都能落库,
// 同号数量>1说明本单撞号, 给本单重新取号改号, 防止单号重复扩散
healIfDuplicated(order);
return inserted;
} catch (DuplicateKeyException e) {
// 已建唯一索引时的兜底: 仅业务单号撞唯一索引时重试, 其他唯一约束冲突原样抛出
String message = e.getMessage();
if (message == null || !message.toLowerCase().contains("goods_number")) {
throw e;
}
log.warn("业务单号[{}]撞号, 自动重新取号重试(第{}次): {}", order.getGoodsNumber(), retryCount, message);
}
}
throw new ServiceException("生成唯一业务单号失败,已达到最大重试次数:" + MAX_RETRY);
}
}
/**
* 插单后自检自愈: 查同号数量, >1 说明有并发插入撞号(本单为后插入方),
* 保持先落库单据单号不变, 重新取号给本单改号
*/
private void healIfDuplicated(BusinessDocumentOrder order) {
List<BusinessDocumentOrder> sameNumber = businessDocumentOrderMapper.selectList(
new QueryWrapper<BusinessDocumentOrder>().eq("goods_number", order.getGoodsNumber())
);
if (sameNumber != null && sameNumber.size() > 1) {
String oldNumber = order.getGoodsNumber();
String newNumber = generateUniqueGoodsNumber(order.getDocumentDate());
order.setGoodsNumber(newNumber);
businessDocumentOrderMapper.updateById(order);
log.warn("业务单号[{}]与并发插入撞号(无唯一索引兜底), 已自愈改号为[{}], orderId={}",
oldNumber, newNumber, order.getId());
}
}
}
@@ -3,8 +3,10 @@ package com.mhd.oms.domain.businessDocumentOrderAdjust.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.util.Date;
@@ -38,6 +40,8 @@ public class BusinessDocumentOrderAdjust implements Serializable {
private String adjustByName;
@ApiModelProperty("调整时间")
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date adjustTime;
@ApiModelProperty("调整原因")
@@ -59,6 +63,8 @@ public class BusinessDocumentOrderAdjust implements Serializable {
private String auditByName;
@ApiModelProperty("审核时间")
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date auditTime;
@ApiModelProperty("审核/驳回意见")
@@ -77,6 +83,8 @@ public class BusinessDocumentOrderAdjust implements Serializable {
private String createName;
@ApiModelProperty("创建时间")
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@ApiModelProperty("更新人")
@@ -86,6 +94,8 @@ public class BusinessDocumentOrderAdjust implements Serializable {
private String updateName;
@ApiModelProperty("更新时间")
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
@ApiModelProperty("删除标记 1-正常 0-删除")
@@ -3,8 +3,12 @@ package com.mhd.oms.domain.businessDocumentOrderAdjustDetail.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.math.BigDecimal;
@@ -24,9 +28,11 @@ public class BusinessDocumentOrderAdjustDetail implements Serializable {
@ApiModelProperty("主键")
@TableId(type = IdType.ASSIGN_ID)
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
@ApiModelProperty("关联调整主记录ID")
@JsonSerialize(using = ToStringSerializer.class)
private Long adjustId;
@ApiModelProperty("业务单ID")
@@ -51,6 +57,8 @@ public class BusinessDocumentOrderAdjustDetail implements Serializable {
private String loadingAddress;
@ApiModelProperty("装货时间")
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date loadingDate;
@ApiModelProperty("卸货地")
@@ -60,6 +68,8 @@ public class BusinessDocumentOrderAdjustDetail implements Serializable {
private String unloadAddress;
@ApiModelProperty("卸货时间")
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date unloadingData;
@ApiModelProperty("发货人")
@@ -81,6 +91,8 @@ public class BusinessDocumentOrderAdjustDetail implements Serializable {
private String route;
@ApiModelProperty("单据日期")
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date documentDate;
@ApiModelProperty("组织ID")
@@ -154,12 +166,16 @@ public class BusinessDocumentOrderAdjustDetail implements Serializable {
private String createName;
@ApiModelProperty("创建时间")
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@ApiModelProperty("更新人")
private String updateBy;
@ApiModelProperty("更新时间")
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
@ApiModelProperty("删除标记 1-正常 0-删除")
@@ -501,6 +501,6 @@ public class ExecutionInMaterialDetailPO extends ExecutionStockInOrderBasePO {
private BigDecimal insureAmount;
@ApiModelProperty("货物类型:0-物料 1-半成品 2-成品")
@Excel(name = "货物类型")
@Excel(name = "货物类型", readConverterExp = "0=物料,1=半成品,2=成品")
private Integer goodsType;
}
@@ -1,5 +1,7 @@
package com.mhd.oms.domain.executionInMaterialDetail.repository.po;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.mhd.common.core.annotation.Excel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@@ -15,21 +17,34 @@ import java.util.Date;
public class ExecutionInMaterialDetailWithOrderPO extends ExecutionInMaterialDetailPO {
private static final long serialVersionUID = 1L;
@ApiModelProperty("入库单状态: 1-已创建 2-已审核 3-收货中 4-上架中 5-已入库 6-已取消 7-已关闭")
@ApiModelProperty("入库单流转状态(参考): 1-已创建 2-已审核 3-收货中 4-上架中 5-已入库 6-已取消 7-已关闭")
private Integer inOrderStatus;
@ApiModelProperty("入库单展示状态: 0-已创建 2-已审核 3-已驳回 4-已入库(WMS回写status=5时同步/推送创建时置4,前端状态列绑定此字段)")
@Excel(name = "单据状态", readConverterExp = "0=已创建,2=已审核,3=已驳回,4=已入库")
private Integer issueStatus;
@ApiModelProperty("货物种类(物料分类名称,多个逗号分隔)")
@Excel(name = "货物种类")
private String materialClassifyName;
@ApiModelProperty("入库单审核状态: 1-未审核 2-审核不通过 3-审核通过")
@Excel(name = "入库单审核状态: 1-未审核 2-审核不通过 3-审核通过")
private Integer inOrderAuditStatus;
@ApiModelProperty("货主ID")
private Long orderShipperId;
@ApiModelProperty("货主编码")
@Excel(name = "货主编码")
private String orderShipperCode;
@ApiModelProperty("货主名称")
@Excel(name = "货主名称")
private String orderShipperName;
@ApiModelProperty("入库日期")
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
@Excel(name = "入仓日期", width = 30, dateFormat = "yyyy-MM-dd")
private Date warehouseDate;
}
@@ -45,6 +45,12 @@ public class ExecutionInMaterialDetailQueryDTO extends BaseVOEntity {
@ApiModelProperty("入库单类型编码")
private String inOrderTypeCode;
@ApiModelProperty("入库单类型编码(与inOrderTypeCode同义)")
private String orderTypeCode;
@ApiModelProperty("入库单类型名称(模糊查询)")
private String orderTypeName;
@ApiModelProperty("入库状态: 1-已创建 2-已审核 3-收货中 4-上架中 5-已入库 6-已取消 7-已关闭")
private Integer inStatus;
@@ -66,4 +72,10 @@ public class ExecutionInMaterialDetailQueryDTO extends BaseVOEntity {
@ApiModelProperty("物料名称")
private String materialName;
@ApiModelProperty("实际入仓日期 开始(yyyy-MM-dd,为空按建档时间兜底)")
private String warehouseDateStart;
@ApiModelProperty("实际入仓日期 结束(yyyy-MM-dd,为空按建档时间兜底)")
private String warehouseDateEnd;
}
@@ -78,6 +78,12 @@ public class QueryOrderOverStockDO extends BaseVOEntity {
@Excel(name = "入库单类型编码")
private String inOrderTypeCode;
@ApiModelProperty("入库单类型编码(与inOrderTypeCode同义)")
private String orderTypeCode;
@ApiModelProperty("入库单类型名称")
private String orderTypeName;
@ApiModelProperty("入库单类型名称")
@Excel(name = "入库单类型名称")
private String inOrderTypeName;
@@ -137,4 +143,10 @@ public class QueryOrderOverStockDO extends BaseVOEntity {
@ApiModelProperty("越库单状态 0:无状态 1:处理中 2:越库完成")
@Excel(name = "越库单状态 0:无状态 1:处理中 2:越库完成")
private Integer overStockStatus;
@ApiModelProperty("实际入仓日期 开始(yyyy-MM-dd,为空按建档时间兜底)")
private String warehouseDateStart;
@ApiModelProperty("实际入仓日期 结束(yyyy-MM-dd,为空按建档时间兜底)")
private String warehouseDateEnd;
}
@@ -85,4 +85,21 @@ public class ExecutionInMaterialDetailApplicationService {
return inMaterialDetailDomainService.queryInOrderDetailList(queryOrderOverStockDO);
}
/**
* 入库明细导出全字段口径同 queryInOrderDetailList不分页
*/
public List<ExecutionInMaterialDetailWithOrderPO> exportInOrderDetailList(QueryOrderOverStockDO queryOrderOverStockDO) {
List<ExecutionInMaterialDetailWithOrderPO> list = queryInOrderDetailList(queryOrderOverStockDO);
// 货物种类=挂载分类的父级分类名称material_classify_name为分类路径("食品,饮料")取倒数第二段
// 仅一级分类(无父级)时取自身名称
for (ExecutionInMaterialDetailWithOrderPO po : list) {
String names = po.getMaterialClassifyName();
if (names != null && names.contains(",")) {
String[] parts = names.split(",");
po.setMaterialClassifyName(parts[parts.length - 2].trim());
}
}
return list;
}
}
@@ -169,6 +169,9 @@ public class ExecutionOutMaterialDetail extends BaseVOEntity {
@ApiModelProperty("分配仓库id")
private Long warehouseId;
@ApiModelProperty("库存锁定模式: 1-下单冻结 2-分配库存冻结(默认), 下发时从业务单明细复制")
private Integer stockLockMode;
@ApiModelProperty("分配仓库编码")
@Excel(name = "分配仓库编码")
private String warehouseCode;
@@ -518,4 +521,7 @@ public class ExecutionOutMaterialDetail extends BaseVOEntity {
private String inLineNumGw;
private String opWhLocGw;
private String warehouseGw;
}
@ApiModelProperty("反审标记: 0-无 1-已反审(业务单反审时软删del_flag=2并打标记)")
private Integer reverseFlag;
}
@@ -183,6 +183,9 @@ public class ExecutionOutMaterialDetailPO extends ExecutionStockOutOrderBaseDO {
@ApiModelProperty("分配仓库id")
private Long warehouseId;
@ApiModelProperty("库存锁定模式: 1-下单冻结 2-分配库存冻结(默认)")
private Integer stockLockMode;
@ApiModelProperty("分配仓库编码")
@Excel(name = "分配仓库编码")
private String warehouseCode;
@@ -1,6 +1,7 @@
package com.mhd.oms.domain.executionOutMaterialDetail.repository.po;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.mhd.common.core.annotation.Excel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
@@ -18,34 +19,49 @@ import java.util.Date;
public class ExecutionOutMaterialDetailWithOrderPO extends ExecutionOutMaterialDetailPO {
private static final long serialVersionUID = 1L;
@ApiModelProperty("出库单状态: 1-已创建 2-已审核 3-部分分配 4-已分配 5-拣货中 6-波次中 7-拣货完成 8-复核中 9-已出库 10-已取消 11-已关闭")
@ApiModelProperty("出库单流转状态(参考): 1-已创建 2-已审核 3-部分分配 4-已分配 5-拣货中 6-波次中 7-拣货完成 8-复核中 9-已出库 10-已取消 11-已关闭 12-已复核 13-已交接;WMS出库完成不回写此字段")
private Integer outOrderStatus;
@ApiModelProperty("出库单展示状态: 0-已创建 2-已审核 3-已驳回 4-已出库(WMS出库完成/推送创建时回写,前端状态列绑定此字段)")
@Excel(name = "单据状态", readConverterExp = "0=已创建,2=已审核,3=已驳回,4=已出库")
private Integer issueStatus;
@ApiModelProperty("出库单审核状态: 1-未审核 2-审核不通过 3-审核通过")
@Excel(name = "出库单审核状态: 1-未审核 2-审核不通过 3-审核通过")
private Integer outOrderAuditStatus;
@ApiModelProperty("货主ID")
private Long orderShipperId;
@ApiModelProperty("货主编码")
@Excel(name = "货主编码")
private String orderShipperCode;
@ApiModelProperty("货主名称")
@Excel(name = "货主名称")
private String orderShipperName;
@ApiModelProperty("出仓日期")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Excel(name = "出仓日期", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date orderOutboundDate;
@ApiModelProperty("出库时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Excel(name = "出库时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date orderOutboundTime;
@ApiModelProperty("保额")
@Excel(name = "保额")
private BigDecimal insureAmount;
@ApiModelProperty("货物类型:0-物料 1-半成品 2-成品")
@Excel(name = "货物类型", readConverterExp = "0=物料,1=半成品,2=成品")
private Integer goodsType;
@ApiModelProperty("货物种类(物料分类名称,多个逗号分隔)")
@Excel(name = "货物种类")
private String materialClassifyName;
}
@@ -45,6 +45,12 @@ public class ExecutionOutMaterialDetailQueryDTO extends BaseVOEntity {
@ApiModelProperty("出库单类型编码")
private String outOrderTypeCode;
@ApiModelProperty("出库单类型编码(与outOrderTypeCode同义)")
private String orderTypeCode;
@ApiModelProperty("出库单类型名称(模糊查询)")
private String orderTypeName;
@ApiModelProperty("出库状态: 1-已创建 2-已审核 3-部分分配 4-已分配 5-拣货中 6-波次中 7-拣货完成 8-复核中 9-已出库 10-已取消 11-已关闭")
private Integer outStatus;
@@ -66,4 +72,10 @@ public class ExecutionOutMaterialDetailQueryDTO extends BaseVOEntity {
@ApiModelProperty("物料名称")
private String materialName;
@ApiModelProperty("实际出仓日期 开始(yyyy-MM-dd,为空按建档时间兜底)")
private String outboundDateStart;
@ApiModelProperty("实际出仓日期 结束(yyyy-MM-dd,为空按建档时间兜底)")
private String outboundDateEnd;
}
@@ -459,4 +459,21 @@ public class ExecutionStockInOrder extends BaseVOEntity {
@ApiModelProperty("通知单备注")
private String noticeRemark;
@ApiModelProperty("反审状态: 0-无 1-已反审")
private Integer reverseAuditStatus;
@ApiModelProperty("反审原因")
private String reverseAuditRemark;
@ApiModelProperty("反审人id")
private Long reverseAuditBy;
@ApiModelProperty("反审人姓名")
private String reverseAuditByName;
@ApiModelProperty("反审时间")
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date reverseAuditTime;
}
@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.extension.service.IService;
import com.mhd.oms.domain.executionInMaterialDetail.repository.po.ExecutionInMaterialDetailPO;
import com.mhd.oms.domain.executionStockInOrder.entity.ExecutionStockInOrder;
import com.mhd.oms.domain.executionStockInOrder.repository.po.ExecutionStockInOrderPO;
import com.mhd.oms.domain.executionStockInOrder.repository.po.ExecutionStockInOrderTotalPO;
import com.mhd.oms.domain.executionStockInOrder.repository.todo.ExecutionStockInOrderDO;
import com.mhd.oms.domain.executionStockInOrder.repository.vo.DeliveryDataDTO;
import com.mhd.oms.domain.executionStockInOrder.repository.vo.StockInDataDTO;
@@ -25,6 +26,11 @@ public interface IExecutionStockInOrderService extends IService<ExecutionStockIn
*/
public List<ExecutionStockInOrderPO> queryList(ExecutionStockInOrderDO stockInOrderDO);
/**
* 列表合计按查询条件全量(不分页)汇总计划数量与入库数量
*/
public ExecutionStockInOrderTotalPO queryListTotal(ExecutionStockInOrderDO stockInOrderDO);
/**
* 新增入库单
*/
@@ -7,6 +7,7 @@ import com.mhd.oms.domain.executionStockInOrder.repository.po.MaterialBaseInfoPO
import com.mhd.oms.domain.executionStockInOrder.repository.po.MaterialGoodsRulePO;
import com.mhd.oms.domain.executionStockInOrder.repository.po.MaterialWarehouseControlPO;
import com.mhd.oms.domain.executionStockInOrder.repository.po.ExecutionStockInOrderPO;
import com.mhd.oms.domain.executionStockInOrder.repository.po.ExecutionStockInOrderTotalPO;
import com.mhd.oms.domain.executionStockInOrder.repository.todo.ExecutionStockInOrderDO;
//import com.mhd.oms.domain.reservationInMaterialDetail.repository.po.ExecutionInMaterialDetailPO;
import org.apache.ibatis.annotations.Param;
@@ -27,6 +28,11 @@ public interface ExecutionStockInOrderMapper extends BaseMapper<ExecutionStockIn
*/
public List<ExecutionStockInOrderPO> queryList(ExecutionStockInOrderDO stockInOrderDO);
/**
* 列表合计按查询条件全量(不分页)汇总计划数量与入库数量
*/
public ExecutionStockInOrderTotalPO queryListTotal(ExecutionStockInOrderDO stockInOrderDO);
public MaterialBaseInfoPO getinfo(@Param("id") Long id);
public MaterialBaseInfoPO getByName(@Param("name") String name);
@@ -26,6 +26,7 @@ import com.mhd.oms.domain.executionStockInOrder.repository.listener.StockInExcel
import com.mhd.oms.domain.executionStockInOrder.repository.mapper.ExecutionStockInOrderMapper;
import com.mhd.oms.domain.executionStockInOrder.repository.po.MaterialBaseInfoPO;
import com.mhd.oms.domain.executionStockInOrder.repository.po.ExecutionStockInOrderPO;
import com.mhd.oms.domain.executionStockInOrder.repository.po.ExecutionStockInOrderTotalPO;
import com.mhd.oms.domain.executionStockInOrder.repository.todo.ExecutionStockInOrderDO;
import com.mhd.oms.domain.executionStockInOrder.repository.vo.*;
import com.mhd.oms.domain.executionStockOutOrder.entity.ExecutionStockOutOrder;
@@ -93,6 +94,15 @@ public class ExecutionStockInOrderImpl extends ServiceImpl<ExecutionStockInOrder
return stockInOrderMapper.queryList(stockInOrderDO);
}
/**
* 列表合计按查询条件全量(不分页)汇总计划数量与入库数量
*/
@Override
public ExecutionStockInOrderTotalPO queryListTotal(ExecutionStockInOrderDO stockInOrderDO)
{
return stockInOrderMapper.queryListTotal(stockInOrderDO);
}
/**
* 新增入库单
*/
@@ -482,4 +482,21 @@ public class ExecutionStockInOrderPO extends BaseVOEntity {
private String khdm;
@ApiModelProperty("通知单备注")
private String noticeRemark;
@ApiModelProperty("反审状态: 0-无 1-已反审")
private Integer reverseAuditStatus;
@ApiModelProperty("反审原因")
private String reverseAuditRemark;
@ApiModelProperty("反审人id")
private Long reverseAuditBy;
@ApiModelProperty("反审人姓名")
private String reverseAuditByName;
@ApiModelProperty("反审时间")
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date reverseAuditTime;
}
@@ -0,0 +1,30 @@
package com.mhd.oms.domain.executionStockInOrder.repository.po;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
/**
* 入库执行单列表合计PO
*
* <p>当前筛选条件下不分页的全量合计随分页列表一起返回
* totalPlanQuantity/totalInQuantity计划数量与入库数量分开计算</p>
*
* @author rcx
* @date 2026-09-14
*/
@Data
@ApiModel(value = "入库执行单列表合计对象", description = "入库执行单列表合计响应对象")
public class ExecutionStockInOrderTotalPO implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("计划数量总数")
private BigDecimal totalPlanQuantity;
@ApiModelProperty("入库数量总数")
private BigDecimal totalInQuantity;
}
@@ -513,4 +513,21 @@ public class ExecutionStockInOrderDO extends BaseVOEntity {
private String businessPriceWay;
@ApiModelProperty("通知单备注")
private String noticeRemark;
@ApiModelProperty("反审状态: 0-无 1-已反审")
private Integer reverseAuditStatus;
@ApiModelProperty("反审原因")
private String reverseAuditRemark;
@ApiModelProperty("反审人id")
private Long reverseAuditBy;
@ApiModelProperty("反审人姓名")
private String reverseAuditByName;
@ApiModelProperty("反审时间")
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date reverseAuditTime;
}
@@ -510,4 +510,21 @@ public class ExecutionStockInOrderDTO extends ExecutionStockInOrderBaseDTO {
private String businessPriceWay;
@ApiModelProperty("通知单备注")
private String noticeRemark;
@ApiModelProperty("反审状态: 0-无 1-已反审")
private Integer reverseAuditStatus;
@ApiModelProperty("反审原因")
private String reverseAuditRemark;
@ApiModelProperty("反审人id")
private Long reverseAuditBy;
@ApiModelProperty("反审人姓名")
private String reverseAuditByName;
@ApiModelProperty("反审时间")
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date reverseAuditTime;
}
@@ -18,6 +18,7 @@ import com.mhd.oms.domain.executionStockInOrder.entity.ExecutionStockInOrder;
import com.mhd.oms.domain.executionStockInOrder.repository.mapper.ExecutionStockInOrderMapper;
import com.mhd.oms.domain.executionStockInOrder.repository.po.MaterialBaseInfoPO;
import com.mhd.oms.domain.executionStockInOrder.repository.po.ExecutionStockInOrderPO;
import com.mhd.oms.domain.executionStockInOrder.repository.po.ExecutionStockInOrderTotalPO;
import com.mhd.oms.domain.executionStockInOrder.repository.todo.ExecutionStockInOrderDO;
import com.mhd.oms.domain.executionStockInOrder.repository.todo.ExecutionStockInOrderDTO;
import com.mhd.oms.domain.executionStockInOrder.repository.vo.GwInDetail;
@@ -172,6 +173,15 @@ public class ExecutionStockInOrderApplicationService {
return stockInOrderDomainService.queryList(stockInOrderDO);
}
/**
* 列表合计按查询条件全量(不分页)汇总计划数量与入库数量
* 须在 queryList/queryList1 之后用同一个DO调用数据权限条件由列表查询原地写入DO
* 合计查询才能继承生效后的筛选 PageHelper startPage 只拦截下一条SQL本查询不会被分页
*/
public ExecutionStockInOrderTotalPO queryListTotal(ExecutionStockInOrderDO stockInOrderDO) {
return stockInOrderDomainService.queryListTotal(stockInOrderDO);
}
/**
* 新增入库单
@@ -263,7 +273,16 @@ public class ExecutionStockInOrderApplicationService {
*/
public ExecutionStockInOrderPO getInfo(Long inOrderId)
{
ExecutionStockInOrderPO info = stockInOrderDomainService.getInfo(inOrderId);
return getInfo(inOrderId, false);
}
/**
* 获取入库执行单详情getReverseAuditInfo 归档详情复用
* @param archive true-查询已反审tab 归档数据
*/
public ExecutionStockInOrderPO getInfo(Long inOrderId, boolean archive)
{
ExecutionStockInOrderPO info = stockInOrderDomainService.getInfo(inOrderId, archive);
String userNcCode = reservationStockInOrderMapper.getUserNcCode(info.getShipperId());
info.setKhdm(userNcCode);
List<ExecutionInMaterialDetailPO> materialDetailList = info.getMaterialDetailList();
@@ -276,6 +295,14 @@ public class ExecutionStockInOrderApplicationService {
return info;
}
/**
* 查询入库执行单已反审tab 归档详情已软删+已反审标记的数据含软删明细
*/
public ExecutionStockInOrderPO getReverseAuditInfo(Long inOrderId)
{
return getInfo(inOrderId, true);
}
/**
@@ -30,6 +30,7 @@ import com.mhd.oms.domain.executionStockInOrder.repository.po.MaterialBaseInfoPO
import com.mhd.oms.domain.executionStockInOrder.repository.po.MaterialGoodsRulePO;
import com.mhd.oms.domain.executionStockInOrder.repository.po.MaterialWarehouseControlPO;
import com.mhd.oms.domain.executionStockInOrder.repository.po.ExecutionStockInOrderPO;
import com.mhd.oms.domain.executionStockInOrder.repository.po.ExecutionStockInOrderTotalPO;
import com.mhd.oms.domain.executionStockInOrder.repository.todo.ExecutionStockInOrderDO;
import com.mhd.oms.domain.executionStockOutOrder.entity.ExecutionStockOutOrder;
import com.mhd.oms.domain.executionStockOutOrder.repository.facade.IExecutionStockOutOrderService;
@@ -113,6 +114,13 @@ public class ExecutionStockInOrderDomainService {
return stockInOrderService.queryList(stockInOrderDO);
}
/**
* 列表合计按查询条件全量(不分页)汇总计划数量与入库数量
*/
public ExecutionStockInOrderTotalPO queryListTotal(ExecutionStockInOrderDO stockInOrderDO) {
return stockInOrderService.queryListTotal(stockInOrderDO);
}
/**
* 从单号列表中提取最大序号
* 单号格式: prefix + 数字序号 RK260807001 prefix=RK260807, 序号=001
@@ -314,14 +322,31 @@ public class ExecutionStockInOrderDomainService {
* 获取入库单详细信息
*/
public ExecutionStockInOrderPO getInfo(Long inOrderId)
{
return getInfo(inOrderId, false);
}
/**
* 获取入库执行单详情
* @param archive true-查询已反审tab 归档数据主单须为已软删+已反审标记明细放行软删行
*/
public ExecutionStockInOrderPO getInfo(Long inOrderId, boolean archive)
{
ExecutionStockInOrderPO stockInOrderPO = stockInOrderService.getInfo(inOrderId);
if (archive && (stockInOrderPO.getDelFlag() == null || stockInOrderPO.getDelFlag() != 2
|| !Integer.valueOf(1).equals(stockInOrderPO.getReverseAuditStatus()))) {
throw new ServiceException("入库执行单非已反审归档数据,无法查看归档详情");
}
ExecutionInMaterialDetailDO inMaterialDetailDO = new ExecutionInMaterialDetailDO();
String inOrderNumber = stockInOrderPO.getInOrderNumber();
List<BusinessDocumentOrder> businessDocumentOrders = businessDocumentOrderMapper.selectList(new LambdaQueryWrapper<BusinessDocumentOrder>().eq(BusinessDocumentOrder::getInOrderNumber, inOrderNumber).notIn(BusinessDocumentOrder::getOrderStatus,4,5));
stockInOrderPO.setBusinessDocumentOrderDOList(businessDocumentOrders);
inMaterialDetailDO.setInOrderNumber(inOrderNumber);
inMaterialDetailDO.setLevel(1);
if (archive) {
//归档明细已随主单软删(del_flag=2)放行软删明细行
inMaterialDetailDO.setDelFlag(2);
}
List<ExecutionInMaterialDetailPO> inMaterialDetailPOList = materialDetailService.queryList(inMaterialDetailDO);
// 先通过入库单号查询收货单列表
@@ -516,4 +516,10 @@ public class ExecutionStockOutOrder extends BaseVOEntity {
@ApiModelProperty("折扣")
private BigDecimal discount;
}
@ApiModelProperty("反审标记: 0-无 1-已反审(业务单反审时软删del_flag=2并打标记)")
private Integer reverseFlag;
@ApiModelProperty("库存锁定模式: 1-下单冻结 2-分配库存冻结(默认), 下发时按该仓明细行取值")
private Integer stockLockMode;
}
@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.extension.service.IService;
import com.mhd.oms.domain.executionOutMaterialDetail.repository.po.ExecutionOutMaterialDetailPO;
import com.mhd.oms.domain.executionStockOutOrder.entity.ExecutionStockOutOrder;
import com.mhd.oms.domain.executionStockOutOrder.repository.po.ExecutionStockOutOrderPO;
import com.mhd.oms.domain.executionStockOutOrder.repository.po.ExecutionStockOutOrderTotalPO;
import com.mhd.oms.domain.executionStockOutOrder.repository.todo.ExecutionStockOutOrderDO;
import java.util.List;
@@ -21,6 +22,11 @@ public interface IExecutionStockOutOrderService extends IService<ExecutionStockO
*/
public List<ExecutionStockOutOrderPO> queryList(ExecutionStockOutOrderDO stockOutOrderDO);
/**
* 列表合计按查询条件全量(不分页)汇总一级明细的计划数量与出库数量
*/
public ExecutionStockOutOrderTotalPO queryListTotal(ExecutionStockOutOrderDO stockOutOrderDO);
/**
* 新增出库单
*/
@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.mhd.oms.domain.executionOutMaterialDetail.repository.po.ExecutionOutMaterialDetailPO;
import com.mhd.oms.domain.executionStockOutOrder.entity.ExecutionStockOutOrder;
import com.mhd.oms.domain.executionStockOutOrder.repository.po.ExecutionStockOutOrderPO;
import com.mhd.oms.domain.executionStockOutOrder.repository.po.ExecutionStockOutOrderTotalPO;
import com.mhd.oms.domain.executionStockOutOrder.repository.todo.ExecutionStockOutOrderDO;
import org.apache.ibatis.annotations.Param;
//import com.mhd.oms.domain.reservationOutMaterialDetail.repository.po.ExecutionOutMaterialDetailPO;
@@ -23,6 +24,11 @@ public interface ExecutionStockOutOrderMapper extends BaseMapper<ExecutionStockO
*/
public List<ExecutionStockOutOrderPO> queryList(ExecutionStockOutOrderDO stockOutOrderDO);
/**
* 列表合计按查询条件全量(不分页)汇总一级明细的计划数量与出库数量
*/
public ExecutionStockOutOrderTotalPO queryListTotal(ExecutionStockOutOrderDO stockOutOrderDO);
/**
* 出库单提货单打印-表头
*/
@@ -6,6 +6,7 @@ import com.mhd.oms.domain.executionStockOutOrder.entity.ExecutionStockOutOrder;
import com.mhd.oms.domain.executionStockOutOrder.repository.facade.IExecutionStockOutOrderService;
import com.mhd.oms.domain.executionStockOutOrder.repository.mapper.ExecutionStockOutOrderMapper;
import com.mhd.oms.domain.executionStockOutOrder.repository.po.ExecutionStockOutOrderPO;
import com.mhd.oms.domain.executionStockOutOrder.repository.po.ExecutionStockOutOrderTotalPO;
import com.mhd.oms.domain.executionStockOutOrder.repository.todo.ExecutionStockOutOrderDO;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
@@ -34,6 +35,15 @@ public class ExecutionStockOutOrderImpl extends ServiceImpl<ExecutionStockOutOrd
return reservationStockOutOrderMapper.queryList(stockOutOrderDO);
}
/**
* 列表合计按查询条件全量(不分页)汇总一级明细的计划数量与出库数量
*/
@Override
public ExecutionStockOutOrderTotalPO queryListTotal(ExecutionStockOutOrderDO stockOutOrderDO)
{
return reservationStockOutOrderMapper.queryListTotal(stockOutOrderDO);
}
/**
* 新增出库单
*/
@@ -29,6 +29,10 @@ public class ExecutionStockOutOrderPO extends BaseVOEntity {
@ApiModelProperty("出库单id")
private Long outOrderId;
@ApiModelProperty("反审标记: 1-已反审(反审软删归档, 【已反审】tab按 delFlag=2+reverseFlag=1 查询)")
private Integer reverseFlag;
@ApiModelProperty("库存锁定模式: 1-下单冻结 2-分配库存冻结(默认), 下发时按该仓明细行取值")
private Integer stockLockMode;
@ApiModelProperty("总价")
@Excel(name = "总价")
private BigDecimal amount;
@@ -0,0 +1,30 @@
package com.mhd.oms.domain.executionStockOutOrder.repository.po;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
/**
* 出库执行单列表合计PO
*
* <p>当前筛选条件下不分页的全量合计随分页列表一起返回
* totalPlanQuantity/totalOutboundQuantity计划数量与出库数量分开计算</p>
*
* @author rcx
* @date 2026-09-14
*/
@Data
@ApiModel(value = "出库执行单列表合计对象", description = "出库执行单列表合计响应对象")
public class ExecutionStockOutOrderTotalPO implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("计划数量总数(一级明细计划数量之和)")
private BigDecimal totalPlanQuantity;
@ApiModelProperty("出库数量总数(一级明细出库数量之和)")
private BigDecimal totalOutboundQuantity;
}
@@ -29,6 +29,9 @@ public class ExecutionStockOutOrderDO extends BaseVOEntity {
@ApiModelProperty("出库单id")
private Long outOrderId;
@ApiModelProperty("反审标记查询: 1-已反审(与delFlag=2成对传, 查【已反审】tab归档数据)")
private Integer reverseFlag;
@ApiModelProperty("组织表ID")
@Excel(name = "组织表ID")
private Long organizationId;
@@ -27,6 +27,9 @@ public class ExecutionStockOutOrderDTO extends ExecutionStockOutOrderBaseDTO {
@ApiModelProperty("出库单id")
private Long outOrderId;
@ApiModelProperty("反审标记查询: 1-已反审(与delFlag=2成对传, 查【已反审】tab归档数据)")
private Integer reverseFlag;
@ApiModelProperty("组织表ID")
@Excel(name = "组织表ID")
private Long organizationId;
@@ -26,6 +26,7 @@ import com.mhd.oms.domain.executionStockOutOrder.entity.ExecutionStockOutOrder;
import com.mhd.oms.domain.executionStockOutOrder.repository.facade.IExecutionStockOutOrderService;
import com.mhd.oms.domain.executionStockOutOrder.repository.mapper.ExecutionStockOutOrderMapper;
import com.mhd.oms.domain.executionStockOutOrder.repository.po.ExecutionStockOutOrderPO;
import com.mhd.oms.domain.executionStockOutOrder.repository.po.ExecutionStockOutOrderTotalPO;
import com.mhd.oms.domain.executionStockOutOrder.repository.todo.ExecutionStockOutOrderDO;
import com.mhd.oms.domain.executionStockOutOrder.repository.todo.ExecutionStockOutOrderDTO;
import com.mhd.oms.domain.gwLog.entity.GwLog;
@@ -66,6 +67,8 @@ import java.util.stream.Collectors;
public class ExecutionStockOutOrderApplicationService {
@Autowired
private ExecutionStockOutOrderDomainService stockOutOrderDomainService;
@Autowired
private com.mhd.oms.application.service.WarehouseInfoApplicationService warehouseInfoApplicationService;
// @Autowired
// private ExecutionOutMaterialDetailDomainService outMaterialDetailDomainService;
// @Autowired
@@ -223,6 +226,15 @@ public class ExecutionStockOutOrderApplicationService {
return stockOutOrderPOS;
}
/**
* 列表合计按查询条件全量(不分页)汇总一级明细的计划数量与出库数量
* 须在 queryList/queryList1 之后用同一个DO调用数据权限条件由列表查询原地写入DO
* 合计查询才能继承生效后的筛选 PageHelper startPage 只拦截下一条SQL本查询不会被分页
*/
public ExecutionStockOutOrderTotalPO queryListTotal(ExecutionStockOutOrderDO stockOutOrderDO) {
return stockOutOrderDomainService.queryListTotal(stockOutOrderDO);
}
/**
* 新增出库单
@@ -862,11 +874,8 @@ public class ExecutionStockOutOrderApplicationService {
if (stockOutOrderDO.getWarehouseId() == null) {
throw new ServiceException("仓库ID不能为空");
}
AjaxResult ajaxResult = systemServiceFeign.getWarehouseInfoByWarehouseId(stockOutOrderDO.getWarehouseId());
if(!"200".equals(String.valueOf(ajaxResult.get("code")))){
throw new ServiceException("获取仓库信息失败");
}
WarehouseFeignPO warehouseFeignPO = JSON.parseObject(JSONObject.toJSONString(ajaxResult.get("data")), WarehouseFeignPO.class);
// Feign失败走本地跨库兜底, 避免保存被"获取仓库信息失败"阻断
WarehouseFeignPO warehouseFeignPO = warehouseInfoApplicationService.getWarehouseInfo(stockOutOrderDO.getWarehouseId());
stockOutOrderDO.setWarehouseCode(warehouseFeignPO.getWarehouseCode());
stockOutOrderDO.setWarehouseName(warehouseFeignPO.getWarehouseName());
}
@@ -30,6 +30,7 @@ import com.mhd.oms.domain.executionStockOutOrder.entity.ExecutionStockOutOrder;
import com.mhd.oms.domain.executionStockOutOrder.repository.facade.IExecutionStockOutOrderService;
import com.mhd.oms.domain.executionStockOutOrder.repository.mapper.ExecutionStockOutOrderMapper;
import com.mhd.oms.domain.executionStockOutOrder.repository.po.ExecutionStockOutOrderPO;
import com.mhd.oms.domain.executionStockOutOrder.repository.po.ExecutionStockOutOrderTotalPO;
import com.mhd.oms.domain.executionStockOutOrder.repository.todo.ExecutionStockOutOrderDO;
import com.mhd.oms.domain.materialBaseInfo.service.MaterialBaseInfoResolveService;
import com.mhd.oms.domain.reservationDeliveryDetailsLink.entity.ReservationDeliveryDetailsLink;
@@ -71,6 +72,8 @@ public class ExecutionStockOutOrderDomainService {
@Autowired
private IExecutionStockOutOrderService stockOutOrderService;
@Autowired
private com.mhd.oms.application.service.WarehouseInfoApplicationService warehouseInfoApplicationService;
@Autowired
private IExecutionOutMaterialDetailService outMaterialDetailService;
@Autowired
private IReservationDeliveryDetailsLinkService deliveryDetailsLinkService;
@@ -112,6 +115,13 @@ public class ExecutionStockOutOrderDomainService {
return stockOutOrderService.queryList(stockOutOrderDO);
}
/**
* 列表合计按查询条件全量(不分页)汇总一级明细的计划数量与出库数量
*/
public ExecutionStockOutOrderTotalPO queryListTotal(ExecutionStockOutOrderDO stockOutOrderDO) {
return stockOutOrderService.queryListTotal(stockOutOrderDO);
}
/**
* 从单号列表中提取最大序号
* 单号格式: prefix + 数字序号 RK260807001 prefix=RK260807, 序号=001
@@ -148,14 +158,9 @@ public class ExecutionStockOutOrderDomainService {
public Boolean insert(ExecutionStockOutOrderDO stockOutOrderDO) {
//建单校验明细数量必须大于00数量单下发WMS后自动分配必失败出库单会卡死在已审核状态
validateMaterialDetailQuantity(stockOutOrderDO.getMaterialDetailList());
//设置单号
//设置单号(仓库信息: Feign优先, system服务失败/降级时本地跨库兜底)
Long warehouseId = stockOutOrderDO.getWarehouseId();
AjaxResult ajaxResult = systemServiceFeign.getWarehouseInfoByWarehouseId(warehouseId);
if (!"200".equals(String.valueOf(ajaxResult.get("code")))) {
throw new ServiceException("获取仓库信息失败");
}
//仓库信息
WarehouseFeignPO warehouseFeignPO = com.alibaba.fastjson.JSON.parseObject(com.alibaba.fastjson2.JSONObject.toJSONString(ajaxResult.get("data")), WarehouseFeignPO.class);
WarehouseFeignPO warehouseFeignPO = warehouseInfoApplicationService.getWarehouseInfo(warehouseId);
String orderNoGenerateRule = warehouseFeignPO.getOrderNoGenerateRule();
String prefix = warehouseFeignPO.getOutOrderNoPrefix();
if ("yang_pin_chu_ku".equals(stockOutOrderDO.getBusinessType())) {
@@ -218,14 +223,9 @@ public class ExecutionStockOutOrderDomainService {
@Transactional(rollbackFor = Exception.class)
public Boolean batchInsert(List<ExecutionStockOutOrderDO> stockOutOrderDOList) {
for (ExecutionStockOutOrderDO stockOutOrderDO : stockOutOrderDOList){
//设置单号
//设置单号(仓库信息: Feign优先, system服务失败/降级时本地跨库兜底)
Long warehouseId = stockOutOrderDO.getWarehouseId();
AjaxResult ajaxResult = systemServiceFeign.getWarehouseInfoByWarehouseId(warehouseId);
if (!"200".equals(String.valueOf(ajaxResult.get("code")))) {
throw new ServiceException("获取仓库信息失败");
}
//仓库信息
WarehouseFeignPO warehouseFeignPO = com.alibaba.fastjson.JSON.parseObject(com.alibaba.fastjson2.JSONObject.toJSONString(ajaxResult.get("data")), WarehouseFeignPO.class);
WarehouseFeignPO warehouseFeignPO = warehouseInfoApplicationService.getWarehouseInfo(warehouseId);
String orderNoGenerateRule = warehouseFeignPO.getOrderNoGenerateRule();
String prefix = warehouseFeignPO.getOutOrderNoPrefix();
if ("yang_pin_chu_ku".equals(stockOutOrderDO.getBusinessType())) {
@@ -448,8 +448,9 @@ public class ExecutionStockOutOrderDomainService {
BigDecimal freeze = freezeAfterQuantity.subtract(freezeBeforeQuantity);
BigDecimal allocationQuantity = reservationMaterialInventory.getAllocationQuantity();
BigDecimal freezeQuantity = reservationMaterialInventory.getFreezeQuantity();
allocationQuantity.add(allocation);
freezeQuantity.subtract(freeze);
// 修复BigDecimal 不可变add/subtract 返回新对象必须回赋原实现未回赋取消下发的镜像库存还原实际不生效
allocationQuantity = (allocationQuantity == null ? BigDecimal.ZERO : allocationQuantity).add(allocation);
freezeQuantity = (freezeQuantity == null ? BigDecimal.ZERO : freezeQuantity).subtract(freeze);
reservationMaterialInventory.setAllocationQuantity(allocationQuantity);
reservationMaterialInventory.setFreezeQuantity(freezeQuantity);
reservationMaterialInventory.setUpdateTime(new Date());
@@ -513,7 +514,7 @@ public class ExecutionStockOutOrderDomainService {
checkDetail.setMaterialName(detail.getMaterialName());
checkList.add(checkDetail);
}
AjaxResult checkResult = wmsServiceFeign.checkOmsAssignInventory(checkList);
AjaxResult checkResult = wmsServiceFeign.checkOmsAssignInventory(checkList, byId.getBusinessOutOrderNumber());
if (checkResult == null || !"200".equals(String.valueOf(checkResult.get("code")))) {
validateErrors.add("出库单[" + byId.getOutOrderNumber() + "]库存校验失败:"
+ (checkResult == null ? "WMS服务不可用,已阻止下发" : checkResult.get("msg")));
@@ -535,6 +536,9 @@ public class ExecutionStockOutOrderDomainService {
try {
StockOutOrder stockOutOrder = new StockOutOrder();
BeanUtils.copyProperties(byId, stockOutOrder,"outOrderId");
// 下单冻结模式WMS单头带上业务单号字段名不同copyProperties不会复制必须显式set
// 供WMS分配承接/下发校验做单据级冻结记录过滤双单冻结同行防误消费
stockOutOrder.setBusinessOrderNo(byId.getBusinessOutOrderNumber());
Long warehouseId = stockOutOrder.getWarehouseId();
Long topOrgId = stockInOrderMapper.getTopOrgId(warehouseId);
Long orgId = stockInOrderMapper.getOrgId(warehouseId);
@@ -542,7 +546,12 @@ public class ExecutionStockOutOrderDomainService {
stockOutOrder.setOrganizationId(orgId);
stockOutOrder.setOrganizationName(orgName);
stockOutOrder.setTopOrganizationId(topOrgId);
wmsServiceFeign.omsOutOrderAdd(stockOutOrder);
//单头/配送明细推送同样校验返回值: 降级吞掉会残留"有单无明细"等半截单
AjaxResult orderAddResult = wmsServiceFeign.omsOutOrderAdd(stockOutOrder);
if (orderAddResult == null || !"200".equals(String.valueOf(orderAddResult.get("code")))) {
throw new ServiceException("出库单[" + currentOrderNumber + "]WMS出库单推送失败:"
+ (orderAddResult == null ? "WMS服务不可用或已降级" : orderAddResult.get("msg")));
}
List<ReservationDeliveryDetailsLink> list = deliveryDetailsLinkService.list(new QueryWrapper<ReservationDeliveryDetailsLink>().lambda().eq(ReservationDeliveryDetailsLink::getOrderNumber, byId.getOutOrderNumber()));
List<DeliveryDetailsLink> deliveryDetailsLinkList = new ArrayList<>();
@@ -552,7 +561,11 @@ public class ExecutionStockOutOrderDomainService {
deliveryDetailsLink.setOmsId(reservationDeliveryDetailsLink.getId());
deliveryDetailsLinkList.add(deliveryDetailsLink);
}
wmsServiceFeign.omsOutOrderAddDeliveryDetailsLink(deliveryDetailsLinkList);
AjaxResult linkAddResult = wmsServiceFeign.omsOutOrderAddDeliveryDetailsLink(deliveryDetailsLinkList);
if (linkAddResult == null || !"200".equals(String.valueOf(linkAddResult.get("code")))) {
throw new ServiceException("出库单[" + currentOrderNumber + "]WMS配送明细推送失败:"
+ (linkAddResult == null ? "WMS服务不可用或已降级" : linkAddResult.get("msg")));
}
String inOrderNumber = stockOutOrder.getOutOrderNumber();
@@ -566,7 +579,12 @@ public class ExecutionStockOutOrderDomainService {
outMaterialDetail.setTopOrganizationId(topOrgId);
outMaterialDetailList.add(outMaterialDetail);
}
wmsServiceFeign.omsOutOrderAddDetail(outMaterialDetailList);
//明细推送必须校验返回值: 失败被降级吞掉时WMS会残留"有单无明细"的出库单, 自动分配报"可用库存为0"难定位
AjaxResult detailAddResult = wmsServiceFeign.omsOutOrderAddDetail(outMaterialDetailList);
if (detailAddResult == null || !"200".equals(String.valueOf(detailAddResult.get("code")))) {
throw new ServiceException("出库单[" + currentOrderNumber + "]WMS出库明细推送失败:"
+ (detailAddResult == null ? "WMS服务不可用或已降级" : detailAddResult.get("msg")));
}
String outOrderNumber = byId.getOutOrderNumber();
com.mhd.system.api.domain.StockOutOrderDTO stockOutOrderDTO = new com.mhd.system.api.domain.StockOutOrderDTO();
@@ -0,0 +1,58 @@
package com.mhd.oms.domain.outEntrustOrder.repository.listener;
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
/**
* 自定义转换器不进行任何类型转换直接返回原始值
* 覆盖EasyExcel默认的StringNumberConverter避免数字格式化错误
*
* @author rcx
* @date 2026-09-14
*/
public class NoConvertConverter implements Converter<String> {
@Override
public Class<?> supportJavaTypeKey() {
return String.class;
}
@Override
public CellDataTypeEnum supportExcelTypeKey() {
// 关键返回NUMBER类型覆盖StringNumberConverter
return CellDataTypeEnum.NUMBER;
}
@Override
public String convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty,
GlobalConfiguration globalConfiguration) {
// 不进行任何格式化直接返回原始值的字符串表示
if (cellData == null) {
return "";
}
// 根据单元格类型获取原始值
try {
CellDataTypeEnum type = cellData.getType();
if (type == CellDataTypeEnum.STRING) {
return cellData.getStringValue() != null ? cellData.getStringValue() : "";
} else if (type == CellDataTypeEnum.NUMBER) {
// 直接转换数字为字符串不进行格式化
return cellData.getNumberValue() != null ? cellData.getNumberValue().toString() : "";
} else if (type == CellDataTypeEnum.BOOLEAN) {
return cellData.getBooleanValue() != null ? cellData.getBooleanValue().toString() : "";
} else if (type == CellDataTypeEnum.EMPTY) {
return "";
} else {
// 其他类型尝试获取字符串值
return cellData.getStringValue() != null ? cellData.getStringValue() : "";
}
} catch (Exception e) {
// 如果获取失败返回空字符串
return "";
}
}
}
@@ -0,0 +1,481 @@
package com.mhd.oms.domain.outEntrustOrder.repository.listener;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.read.metadata.ReadSheet;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.TypeReference;
import com.mhd.common.core.domain.po.SysDictDataVo;
import com.mhd.common.core.domain.po.UserPo;
import com.mhd.common.core.exception.ServiceException;
import com.mhd.common.core.utils.StringUtils;
import com.mhd.common.core.web.domain.AjaxResult;
import com.mhd.common.security.utils.SecurityUtils;
import com.mhd.oms.domain.outEntrustOrder.repository.mapper.OutEntrustOrderMapper;
import com.mhd.oms.domain.outEntrustOrder.repository.todo.OutEntrustOrderDO;
import com.mhd.oms.domain.outEntrustOrder.repository.todo.OutEntrustOrderDetailDO;
import com.mhd.oms.domain.reservationStockInOrder.repository.po.MaterialBaseInfoPO;
import com.mhd.system.api.SystemServiceFeign;
import com.mhd.system.api.UserServiceFeign;
import com.mhd.system.api.model.LoginUser;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* 出仓委托单Excel解析服务
* 模板与WMS出库管理导入一致前11行为表头第12行起为明细
*
* @author rcx
* @date 2026-09-14
*/
@Slf4j
@Service
public class OutEntrustExcelParseService {
@Resource
private SystemServiceFeign systemServiceFeign;
@Resource
private UserServiceFeign userServiceFeign;
@Autowired
private OutEntrustOrderMapper outEntrustOrderMapper;
/**
* 解析结果委托单对象 + 校验错误信息(空串表示通过)
*/
@Getter
public static class ParseResult {
private final OutEntrustOrderDO order;
private final StringBuilder errorMsg;
public ParseResult(OutEntrustOrderDO order, StringBuilder errorMsg) {
this.order = order;
this.errorMsg = errorMsg;
}
}
/**
* 解析出仓委托单Excel(不落库落库由应用服务走新增链路)
*
* @param file 上传的Excel文件
* @param warehouseName 仓库名称(导入弹窗选择)
* @param warehouseCode 仓库编码
* @param warehouseId 仓库ID
* @return 解析结果委托单对象 + 校验错误信息错误信息非空时不应落库
*/
public ParseResult parseOutEntrustOrderExcel(MultipartFile file, String warehouseName, String warehouseCode, Long warehouseId) throws IOException {
OutEntrustHeaderListener headerListener = new OutEntrustHeaderListener();
ReadSheet headerSheet = EasyExcel.readSheet(0)
.headRowNumber(0)
.build();
ExcelReader headerReader = EasyExcel.read(file.getInputStream())
.autoCloseStream(false)
.registerReadListener(headerListener)
.registerConverter(new NoConvertConverter())
.headRowNumber(0)
.build();
// 通过监听器控制只读取前11行
headerReader.read(headerSheet);
InputStream itemInputStream = file.getInputStream();
OutEntrustItemListener itemListener = new OutEntrustItemListener();
// 商品明细需要跳过前11行,从第12行开始读取
ExcelReader itemReader = EasyExcel.read(itemInputStream)
.autoCloseStream(false)
.registerReadListener(itemListener)
.registerConverter(new NoConvertConverter())
.headRowNumber(12)
.build();
ReadSheet itemSheet = EasyExcel.readSheet(0).build();
itemReader.read(itemSheet);
itemReader.finish();
List<OutEntrustOrderDetailDO> itemList = itemListener.getItemList();
OutEntrustOrderDO outEntrustOrder = headerListener.getOutEntrustOrder();
// 提前获取货主ID用于查询物料基础信息时过滤
String customerName = outEntrustOrder.getShipperName();
UserPo shipperUser = resolveCustomerShipperUserPoForImport(customerName);
Long shipperId = shipperUser != null ? shipperUser.getUserId() : null;
LoginUser loginUser = SecurityUtils.getLoginUser();
Long topOrganizationId = loginUser != null && loginUser.getUserPo() != null
? loginUser.getUserPo().getTopOrganizationId() : null;
initList(itemList, shipperId, topOrganizationId);
outEntrustOrder.setMaterialDetailList(itemList);
outEntrustOrder.setWarehouseName(warehouseName);
outEntrustOrder.setWarehouseCode(warehouseCode);
outEntrustOrder.setWarehouseId(warehouseId);
//业务员默认当前登录人
if (loginUser != null) {
outEntrustOrder.setSalesmanId(String.valueOf(loginUser.getUserid()));
outEntrustOrder.setSalesmanName(loginUser.getUserPo() != null ? loginUser.getUserPo().getUserName() : loginUser.getUsername());
}
//货主信息回填(名称/编号以用户中心为准)
if (shipperUser != null) {
outEntrustOrder.setShipperId(shipperUser.getUserId());
outEntrustOrder.setShipperCode(shipperUser.getUserMemberCode());
outEntrustOrder.setShipperName(shipperUser.getUserName());
}
initDict(outEntrustOrder);
//校验必填字段
StringBuilder errorMsg = check(outEntrustOrder);
// 日期单元格有值但解析失败与物料一致性错误同等对待阻断导入并明确提示杜绝静默丢日期
for (String dateError : headerListener.getDateParseErrors()) {
errorMsg.append(dateError).append("; ");
}
return new ParseResult(outEntrustOrder, errorMsg);
}
/**
* 校验导入明细是否已在当前货主下维护物料基础信息
*/
private StringBuilder validateShipperMaterialDetails(OutEntrustOrderDO outEntrustOrder) {
StringBuilder sb = new StringBuilder();
List<OutEntrustOrderDetailDO> details = outEntrustOrder.getMaterialDetailList();
if (details == null || details.isEmpty()) {
return sb;
}
Long shipperId = outEntrustOrder.getShipperId();
for (OutEntrustOrderDetailDO row : details) {
if (row.getMaterialBaseInfoId() != null) {
continue;
}
boolean hasIdentifier = StringUtils.isNotBlank(row.getMaterialNo())
|| StringUtils.isNotBlank(row.getSkucode())
|| StringUtils.isNotBlank(row.getCommodityName());
if (!hasIdentifier) {
continue;
}
String desc = buildImportMaterialRowDesc(row);
if (ObjectUtil.isEmpty(shipperId)) {
sb.append("请先确认客户(货主)信息;无法校验以下物料是否已在货主下维护:").append(desc).append("; ");
} else {
sb.append("当前货主下未维护以下物料:").append(desc).append("; ");
}
}
return sb;
}
/**
* 提示文案只强调 Excel 导入列商品名称商品名称为空时用导入料号/SKU 辅助说明
*/
private String buildImportMaterialRowDesc(OutEntrustOrderDetailDO row) {
String commodityName = StringUtils.trimToEmpty(row.getCommodityName());
if (StringUtils.isNotBlank(commodityName)) {
return "商品名称「" + commodityName + "";
}
String materialNo = StringUtils.trimToEmpty(row.getMaterialNo());
String sku = StringUtils.trimToEmpty(row.getSkucode());
StringBuilder b = new StringBuilder("导入商品名称为空");
if (StringUtils.isNotBlank(materialNo)) {
b.append(",料号「").append(materialNo).append("");
}
if (StringUtils.isNotBlank(sku)) {
b.append("SKU「").append(sku).append("");
}
return b.toString();
}
private StringBuilder check(OutEntrustOrderDO outEntrustOrder) {
StringBuilder errorMsg = new StringBuilder();
errorMsg.append(validateShipperMaterialDetails(outEntrustOrder));
if (outEntrustOrder.getMaterialDetailList() == null || outEntrustOrder.getMaterialDetailList().isEmpty()) {
errorMsg.append("物料明细列表不能为空或匹配不上物料信息; ");
}
if (StringUtils.isEmpty(outEntrustOrder.getToReceiver())) errorMsg.append("To不能为空; ");
if (StringUtils.isEmpty(outEntrustOrder.getCarrier())) errorMsg.append("承运商不能为空; ");
if (StringUtils.isEmpty(outEntrustOrder.getTransportModeCode())) errorMsg.append("运输方式不能为空; ");
if (StringUtils.isEmpty(outEntrustOrder.getSupervisionModeCode())) errorMsg.append("监督方式不能为空; ");
if (StringUtils.isEmpty(outEntrustOrder.getDepartureArrivalCountry())) errorMsg.append("出发/到达国家不能为空; ");
if (StringUtils.isEmpty(outEntrustOrder.getContactPerson())) errorMsg.append("联系人不能为空; ");
if (ObjectUtil.isEmpty(outEntrustOrder.getShipperId())) errorMsg.append("客户名称不能为空或匹配不上客户信息; ");
if (StringUtils.isEmpty(outEntrustOrder.getDeclareCustoms())) errorMsg.append("申报地关区不能为空; ");
if (StringUtils.isEmpty(outEntrustOrder.getEntryExitCustoms())) errorMsg.append("进出境关别不能为空; ");
return errorMsg;
}
/**
* @description 初始化物料明细列表查询物料基础信息并设置物料基础信息ID(与WMS出库管理导入逻辑一致料号 > SKU码 > 商品名称)
* @param itemList 出仓委托单明细列表
* @param shipperId 货主ID用于过滤物料基础信息确保查询到的是该货主绑定的物料
* @param topOrganizationId 一级组织ID用于过滤物料基础信息
*/
private void initList(List<OutEntrustOrderDetailDO> itemList, Long shipperId, Long topOrganizationId) {
for (OutEntrustOrderDetailDO detail : itemList) {
// 未识别货主时不查物料避免匹配到其他货主的物料基础信息
if (shipperId == null) {
continue;
}
// 根据明细表中的字段查询物料基础信息优先料号 > 商品SKU码 > 商品名称
MaterialBaseInfoPO materialBaseInfoPO = null;
// 优先使用料号查询materialNo对应materialCode
String materialNo = detail.getMaterialNo();
if (StringUtils.isNotBlank(materialNo)) {
List<MaterialBaseInfoPO> materialList = outEntrustOrderMapper.getMaterialByMaterialCode(materialNo, shipperId, topOrganizationId);
if (materialList != null && !materialList.isEmpty()) {
materialBaseInfoPO = materialList.get(0);
// ==== 料号与规格型号/SKU码一致性校验任何一项不符直接报对应错误 ====
// 双方trim后必须完全一致Excel留空而档案有值或Excel填了值而档案为空均视为不一致报错
String excelSpec = StringUtils.trimToEmpty(detail.getSpecificationModel());
String dbSpec = StringUtils.trimToEmpty(materialBaseInfoPO.getSpecificationModel());
if (!excelSpec.equals(dbSpec)) {
throw new ServiceException(String.format("导入失败:料号「%s」规格型号与物料档案不一致(Excel:%s / 档案:%s",
materialNo, excelSpec.isEmpty() ? "" : excelSpec, dbSpec.isEmpty() ? "" : dbSpec));
}
String excelSku = StringUtils.trimToEmpty(detail.getSkucode());
String dbSku = StringUtils.trimToEmpty(materialBaseInfoPO.getBarCode());
if (!excelSku.equals(dbSku)) {
throw new ServiceException(String.format("导入失败:料号「%s」SKU码与物料档案不一致(Excel:%s / 档案:%s",
materialNo, excelSku.isEmpty() ? "" : excelSku, dbSku.isEmpty() ? "" : dbSku));
}
} else {
// 料号已填写但在该货主下查不到 直接报错与WMS出库导入一致不再被SKU码/商品名称静默兜底
throw new ServiceException(String.format("导入失败:料号「%s」在当前客户(货主)下不存在,请核对料号或在物料管理中维护", materialNo));
}
}
// 如果料号查询不到使用商品SKU码查询skucode对应barCode
if (materialBaseInfoPO == null && StringUtils.isNotBlank(detail.getSkucode())) {
List<MaterialBaseInfoPO> materialList = outEntrustOrderMapper.getMaterialByBarCode(detail.getSkucode(), shipperId, topOrganizationId);
if (materialList != null && !materialList.isEmpty()) {
materialBaseInfoPO = materialList.get(0);
}
}
// 如果料号和SKU码都查询不到使用商品名称查询commodityName对应materialName
if (materialBaseInfoPO == null && StringUtils.isNotBlank(detail.getCommodityName())) {
List<MaterialBaseInfoPO> materialList = outEntrustOrderMapper.getMaterialByMaterialName(detail.getCommodityName(), shipperId, topOrganizationId);
if (materialList != null && !materialList.isEmpty()) {
materialBaseInfoPO = materialList.get(0);
}
}
// 设置物料基础信息ID并从物料基础信息中回填料号物料编码物料名称等信息
if (materialBaseInfoPO != null) {
detail.setMaterialBaseInfoId(materialBaseInfoPO.getMaterialBaseInfoId());
// 单价统一从物料档案回填
detail.setUnitPrice(materialBaseInfoPO.getUnitPrice());
// 如果Excel中没有料号从物料基础信息中回填
if (StringUtils.isBlank(detail.getMaterialNo())) {
detail.setMaterialNo(materialBaseInfoPO.getMaterialCode());
}
// 回填物料编码
if (StringUtils.isBlank(detail.getMaterialCode())) {
detail.setMaterialCode(materialBaseInfoPO.getMaterialCode());
}
// 回填商品编号
if (StringUtils.isBlank(detail.getCommodityNo())) {
detail.setCommodityNo(materialBaseInfoPO.getMaterialCode());
}
// 回填物料名称如果Excel中的商品名称为空使用物料基础信息中的名称
if (StringUtils.isBlank(detail.getCommodityName())) {
detail.setCommodityName(materialBaseInfoPO.getMaterialName());
}
// 回填物料名称到materialName字段
detail.setMaterialName(materialBaseInfoPO.getMaterialName());
// 回填条形码
detail.setBarCode(materialBaseInfoPO.getBarCode());
// 如果Excel中没有SKU码从物料基础信息中回填
if (StringUtils.isBlank(detail.getSkucode())) {
detail.setSkucode(materialBaseInfoPO.getBarCode());
}
}
// 单位优先使用Excel中的"单位"字段如果为空则使用"申报单位"如果还为空则从物料主数据获取与WMS出库导入处理一致
String unit = StringUtils.isNotBlank(detail.getUnit()) ? detail.getUnit() : null;
if (StringUtils.isBlank(unit) && StringUtils.isNotBlank(detail.getDeclareUnit())) {
unit = detail.getDeclareUnit();
}
if (StringUtils.isBlank(unit) && materialBaseInfoPO != null && StringUtils.isNotBlank(materialBaseInfoPO.getUnitName())) {
unit = materialBaseInfoPO.getUnitName();
}
detail.setUnit(unit);
}
}
private void initDict(OutEntrustOrderDO outEntrustOrder) {
if (ObjectUtil.isNotNull(outEntrustOrder.getContainerNoName())) {
AjaxResult containerNo = systemServiceFeign.selectListByDictType("cabinetNo");
if (ObjectUtil.isNotNull(containerNo) && "200".equals(String.valueOf(containerNo.get("code")))) {
List<SysDictDataVo> containerNoList = JSON.parseArray(JSONObject.toJSONString(containerNo.get("data")), SysDictDataVo.class);
for (SysDictDataVo sysDictDataVo : containerNoList) {
if (outEntrustOrder.getContainerNoName().equals(sysDictDataVo.getDictLabel())) {
outEntrustOrder.setContainerNo(sysDictDataVo.getDictValue());
break;
}
}
}
}
if (ObjectUtil.isNotNull(outEntrustOrder.getTransportModeName())) {
AjaxResult transportMode = systemServiceFeign.selectListByDictType("transportMode");
if (ObjectUtil.isNotNull(transportMode) && "200".equals(String.valueOf(transportMode.get("code")))) {
List<SysDictDataVo> transportModeList = JSON.parseArray(JSONObject.toJSONString(transportMode.get("data")), SysDictDataVo.class);
for (SysDictDataVo sysDictDataVo : transportModeList) {
if (outEntrustOrder.getTransportModeName().equals(sysDictDataVo.getDictLabel())) {
outEntrustOrder.setTransportModeCode(sysDictDataVo.getDictValue());
break;
}
}
}
}
if (ObjectUtil.isNotNull(outEntrustOrder.getSupervisionModeName())) {
AjaxResult supervisionMode = systemServiceFeign.selectListByDictType("controlType");
if (ObjectUtil.isNotNull(supervisionMode) && "200".equals(String.valueOf(supervisionMode.get("code")))) {
List<SysDictDataVo> supervisionModeList = JSON.parseArray(JSONObject.toJSONString(supervisionMode.get("data")), SysDictDataVo.class);
for (SysDictDataVo sysDictDataVo : supervisionModeList) {
if (outEntrustOrder.getSupervisionModeName().equals(sysDictDataVo.getDictLabel())) {
outEntrustOrder.setSupervisionModeCode(sysDictDataVo.getDictValue());
break;
}
}
}
}
}
/**
* 导入出库单解析出的货主用户是否属于当前登录用户同一组织organizationId
* 仅当登录用户带有 organizationId 时启用过滤否则保持与旧行为一致
*/
private boolean shipperMatchesImporterOrganization(UserPo userPo, Long loginOrganizationId) {
if (loginOrganizationId == null) {
return true;
}
if (userPo == null) {
return false;
}
Long oid = userPo.getOrganizationId();
return oid != null && loginOrganizationId.equals(oid);
}
/**
* 按客户名称或数字ID解析货主用户逻辑与WMS出库管理导入一致getInfoByName 货主列表兜底均做 organizationId 校验
*/
private UserPo resolveCustomerShipperUserPoForImport(String customerName) {
if (StringUtils.isEmpty(customerName)) {
return null;
}
Long loginOrganizationId = null;
LoginUser loginUser = SecurityUtils.getLoginUser();
if (loginUser != null && loginUser.getUserPo() != null) {
loginOrganizationId = loginUser.getUserPo().getOrganizationId();
}
String shipperName = customerName.trim();
if (StringUtils.isEmpty(shipperName)) {
return null;
}
// 纯数字按用户ID查询
if (shipperName.matches("\\d+")) {
try {
Long possibleId = Long.valueOf(shipperName);
AjaxResult ajaxResult = userServiceFeign.getInfo(possibleId);
if ("200".equals(String.valueOf(ajaxResult.get("code"))) && ajaxResult.get("data") != null) {
UserPo userPo = JSON.parseObject(JSONObject.toJSONString(ajaxResult.get("data")), UserPo.class);
if (userPo != null && shipperMatchesImporterOrganization(userPo, loginOrganizationId)) {
return userPo;
}
}
} catch (Exception e) {
// ignore
}
return null;
}
// 按名称查询用户中心
try {
AjaxResult ajaxResult = userServiceFeign.getInfoByName(shipperName);
if ("200".equals(String.valueOf(ajaxResult.get("code"))) && ajaxResult.get("data") != null) {
UserPo userPo = JSON.parseObject(JSONObject.toJSONString(ajaxResult.get("data")), UserPo.class);
if (userPo != null && shipperMatchesImporterOrganization(userPo, loginOrganizationId)) {
return userPo;
}
}
} catch (Exception e) {
// 继续货主列表兜底
}
// 货主列表兜底与WMS出库导入一致
try {
AjaxResult shipperListResult = userServiceFeign.userShipperListAll();
if ("200".equals(String.valueOf(shipperListResult.get("code"))) && shipperListResult.get("data") != null) {
Object data = shipperListResult.get("data");
List<Map<String, Object>> shipperList = null;
if (data instanceof List) {
List<Object> rawList = (List<Object>) data;
shipperList = new ArrayList<>();
for (Object item : rawList) {
if (item instanceof Map) {
Map<String, Object> mapItem = (Map<String, Object>) item;
shipperList.add(mapItem);
} else {
Map<String, Object> mapItem = JSON.parseObject(JSONObject.toJSONString(item), new TypeReference<Map<String, Object>>() {
});
shipperList.add(mapItem);
}
}
} else {
String jsonStr = JSONObject.toJSONString(data);
shipperList = JSON.parseObject(jsonStr, new TypeReference<List<Map<String, Object>>>() {
});
}
if (shipperList != null && !shipperList.isEmpty()) {
for (Map<String, Object> shipper : shipperList) {
Object userId = shipper.get("userId");
Object userName = shipper.get("userName");
if (userName == null) {
userName = shipper.get("name");
}
if (userName == null) {
userName = shipper.get("shipperName");
}
boolean matched = false;
if (StringUtils.isNotEmpty(shipperName) && userName != null) {
String normalizedUserName = String.valueOf(userName).trim();
if (shipperName.equals(normalizedUserName) || normalizedUserName.contains(shipperName)) {
matched = true;
}
}
if (matched && userId != null) {
Long candidateId = Long.valueOf(String.valueOf(userId));
AjaxResult infoRes = userServiceFeign.getInfo(candidateId);
if ("200".equals(String.valueOf(infoRes.get("code"))) && infoRes.get("data") != null) {
UserPo up = JSON.parseObject(JSONObject.toJSONString(infoRes.get("data")), UserPo.class);
if (shipperMatchesImporterOrganization(up, loginOrganizationId)) {
return up;
}
}
}
}
}
}
} catch (Exception e) {
// ignore
}
return null;
}
}
@@ -0,0 +1,201 @@
package com.mhd.oms.domain.outEntrustOrder.repository.listener;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.mhd.oms.domain.outEntrustOrder.repository.todo.OutEntrustOrderDO;
import lombok.Getter;
import org.apache.poi.ss.usermodel.DateUtil; // Excel序列号转日期POI已随EasyExcel引入
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 出仓委托单导入-表头信息监听器处理前11行与WMS出库管理导入模板一致
*
* @author rcx
* @date 2026-09-14
*/
@Getter
public class OutEntrustHeaderListener extends AnalysisEventListener<Map<Integer, Object>> {
private final OutEntrustOrderDO outEntrustOrder = new OutEntrustOrderDO();
private int rowNum = 0;
/** 日期单元格有值但格式无法识别的收集器(类上已有@Getter,自动生成getDateParseErrors() */
private final List<String> dateParseErrors = new ArrayList<>();
private final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
@Override
public void invoke(Map<Integer, Object> data, AnalysisContext context) {
// 将Map<Integer, Object>转换为Map<Integer, String>
Map<Integer, String> stringData = convertToStringMap(data);
// 将Map转换为List再转为数组
List<String> dataList = mapToList(stringData);
Object[] rowData = listToArray(dataList);
String stringValue1 = getStringValue(rowData[0]);
if (stringValue1 != null && !stringValue1.isEmpty()) {
if ("备注:".equals(stringValue1)) {
outEntrustOrder.setRemark(getStringValue(rowData[1]));
}
}
// 根据行号提取对应信息
switch (rowNum) {
case 4: // 第5行to仓库联系人车型及车牌委托编码
outEntrustOrder.setToReceiver(getStringValue(rowData[2]));
outEntrustOrder.setContactPerson(getStringValue(rowData[5]));
outEntrustOrder.setVehicleModelPlate(getStringValue(rowData[12]));
outEntrustOrder.setEntrustNo(getStringValue(rowData[16]));
break;
case 5: // 第6行承运方tel柜号司机签名
outEntrustOrder.setCarrier(getStringValue(rowData[2]));
outEntrustOrder.setTel(getStringValue(rowData[5]));
outEntrustOrder.setContainerNoName(getStringValue(rowData[12]));
outEntrustOrder.setDriverSign(getStringValue(rowData[16]));
break;
case 6: // 第7行出仓日期fax运输方式申报地关区
Date outboundDate = parseDateSmart(getStringValue(rowData[2]), "出仓日期");
if (outboundDate != null) {
outEntrustOrder.setOutboundDate(outboundDate);
}
outEntrustOrder.setFax(getStringValue(rowData[5]));
outEntrustOrder.setTransportModeName(getStringValue(rowData[12]));
outEntrustOrder.setDeclareCustoms(getStringValue(rowData[16]));
break;
case 7: // 第8行生产商客户名称报关姓名及联系方式完成时间
outEntrustOrder.setManufacturer(getStringValue(rowData[2]));
outEntrustOrder.setShipperName(getStringValue(rowData[5]));
outEntrustOrder.setCustomsDeclarerInfo(getStringValue(rowData[12]));
Date completeTime = parseDateSmart(getStringValue(rowData[16]), "完成时间");
if (completeTime != null) {
outEntrustOrder.setCompleteTime(completeTime);
}
break;
case 8: // 第9行监管方式送货地址海关是否查验货物进出境关别
outEntrustOrder.setSupervisionModeName(getStringValue(rowData[2]));
outEntrustOrder.setDeliveryAddr(getStringValue(rowData[5]));
String stringValue = getStringValue(rowData[12]);
outEntrustOrder.setCustomsInspectionFlag(stringValue);
outEntrustOrder.setEntryExitCustoms(getStringValue(rowData[16]));
break;
case 9: // 第10行下单日期起运国
Date orderDate = parseDateSmart(getStringValue(rowData[2]), "下单日期");
if (orderDate != null) {
outEntrustOrder.setOrderDate(orderDate);
}
outEntrustOrder.setDepartureArrivalCountry(getStringValue(rowData[5]));
break;
}
rowNum++;
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 解析完成后的处理
}
/**
* 将List转换为数组
*/
private Object[] listToArray(List<String> list) {
// 创建一个足够大的数组来容纳最多30列数据
Object[] array = new Object[30];
for (int i = 0; i < 30 && i < list.size(); i++) {
array[i] = list.get(i);
}
return array;
}
/**
* 将Object的Map转换为String的Map
*/
private Map<Integer, String> convertToStringMap(Map<Integer, Object> dataMap) {
if (dataMap == null) {
return new HashMap<>();
}
Map<Integer, String> stringMap = new HashMap<>();
for (Map.Entry<Integer, Object> entry : dataMap.entrySet()) {
Object value = entry.getValue();
String stringValue = value != null ? value.toString() : "";
stringMap.put(entry.getKey(), stringValue);
}
return stringMap;
}
/**
* 将Map转换为List
*/
private List<String> mapToList(Map<Integer, String> map) {
if (map == null) {
return new ArrayList<>();
}
// 找到最大索引以确定列表大小
int maxSize = map.keySet().stream().mapToInt(Integer::intValue).max().orElse(-1) + 1;
maxSize = Math.max(maxSize, 30); // 至少30个位置
List<String> list = new ArrayList<>(Collections.nCopies(maxSize, ""));
for (Map.Entry<Integer, String> entry : map.entrySet()) {
if (entry.getKey() >= 0 && entry.getKey() < maxSize) {
String value = entry.getValue();
list.set(entry.getKey(), value != null ? value : "");
}
}
return list;
}
/**
* 转换为字符串处理null值
*/
private String getStringValue(Object value) {
return value == null ? "" : value.toString().trim();
}
/**
* 智能解析日期兼容 Excel 日期单元格序列号 + 常见文本格式
* 解析失败记入 dateParseErrors由解析服务汇总返回给用户不再静默丢失
* @param raw 单元格原始字符串
* @param fieldLabel 字段中文名用于错误提示
*/
private Date parseDateSmart(String raw, String fieldLabel) {
String s = raw == null ? "" : raw.trim();
if (s.isEmpty()) {
return null; // 单元格为空保持原行为不算错误
}
// 1) Excel 日期序列号NoConvertConverter 会把真日期单元格转成纯数字字符串 46234 = 2026-08-20
// 区间 20000~60000 对应 1954~2064 避免把普通数字误判成日期
if (s.matches("^\\d{1,5}(\\.\\d+)?$")) {
try {
double serial = Double.parseDouble(s);
if (serial >= 20000 && serial <= 60000) {
return DateUtil.getJavaDate(serial);
}
} catch (NumberFormatException ignore) {
}
}
// 2) 常见文本格式逐个尝试
String[] patterns = {
"yyyy-MM-dd", "yyyy/M/d", "yyyy.M.d", "yyyy年M月d日",
"yyyy-MM-dd HH:mm:ss", "yyyy/M/d H:mm:ss", "yyyy-MM-dd H:mm"
};
for (String p : patterns) {
try {
SimpleDateFormat sdf = new SimpleDateFormat(p);
sdf.setLenient(false);
return sdf.parse(s);
} catch (ParseException ignore) {
}
}
dateParseErrors.add(fieldLabel + "" + s + "」格式无法识别,请改为日期格式或 2026-08-20 / 2026/8/20 / 2026年8月20日 等格式");
return null;
}
}

Some files were not shown because too many files have changed in this diff Show More