Compare commits

...

36 Commits

Author SHA1 Message Date
195e90df5e fix(settlement): 修复经销商订单结算任务中的分润收入计算问题
- 注释掉三级经销商佣金计算逻辑以解决结算异常
- 保留直接推荐奖和二级分润收入的正常计算流程
- 防止因三级佣金计算导致的订单结算失败问题
2026-02-04 17:49:00 +08:00
c5da6f371b feat(order): 分离订单退款功能到独立接口并优化水票统计
- 将订单退款逻辑从update方法中分离到独立的refund接口
- 添加退款相关操作权限控制和参数验证
- 实现申请退款和同意退款两种状态的分别处理
- 新增水票总数统计功能,包括service、mapper和controller层实现
- 修改佣金注释文本从"第3级佣金"为"分润收入"
- 优化订单更新逻辑,禁止通过普通更新接口进行退款操作
2026-02-04 17:38:00 +08:00
51d3a029cc feat(shop): 添加订单支付功能支持
- 新增 OrderPrepayRequest DTO 用于处理支付请求参数
- 实现 prepay 接口支持 /pay、/prepay、/repay 多路径兼容
- 添加用户登录验证和租户权限校验机制
- 集成微信支付创建订单功能并返回支付信息
- 实现订单状态验证包括已支付、已删除、已过期等状态检查
- 支持通过订单ID或订单号查询并处理支付请求
- 添加支付类型参数处理和默认值设置逻辑
2026-02-04 15:57:34 +08:00
30c7e72a80 fix(order): 修复订单处理中的空指针异常和状态比较问题
- 添加 Objects 工具类导入用于安全的对象比较
- 修复 shopOrderNow 为空时的空指针异常
- 使用 Objects.equals 替换直接的 equals 比较避免 NPE
- 为发货状态变更逻辑添加清晰的注释说明
- 修复支付状态检查中的布尔值比较逻辑
2026-02-04 15:47:23 +08:00
36bf931274 feat(order): 更新订单发货状态为已发货
- 在订单状态更新时同步设置发货状态为"已发货"
- 确保订单处理流程中发货状态的一致性
2026-02-04 11:18:53 +08:00
58c755b715 feat(tickets): 实现套票购买量和赠送量的分离管理
- 购买量(buyQty)立即设置为可用状态
- 赠送量(giftQty)设置为冻结状态并按计划释放
- 修改总数量计算逻辑,将购买量和赠送量直接相加
- 更新套票记录中的可用数量和冻结数量字段
- 调整释放计划构建方法,仅基于赠送量进行释放规划
- 更新套票日志记录,区分可用量和冻结量的变化追踪
2026-02-04 10:24:46 +08:00
937e707890 refactor(glt): 优化套票发放服务逻辑
- 引入 IssueOutcome 枚举替代布尔返回值,提高代码可读性
- 恢复并完善订单时间条件查询逻辑
- 移除调试用的 System.out.println 语句
- 实现订单状态更新机制,发放完成后将订单置为已完成
- 增强幂等性处理,支持已处理订单的跳过逻辑
- 统一异常情况处理,各类失败场景返回对应枚举值
- 添加详细的注释说明业务逻辑和处理流程
2026-02-04 10:12:55 +08:00
9a79aff47d feat(ticket): 增加套票实体扩展字段和关联查询功能
- 在GltUserTicket实体中增加templateName、payPrice、goodsName、nickname、avatar、phone等扩展字段
- 在GltUserTicketLog实体中增加nickname、avatar、phone等用户信息扩展字段
- 在GltUserTicketRelease实体中增加nickname、avatar、phone等用户信息扩展字段
- 修改GltUserTicketMapper.xml实现用户表、模板表、订单表的LEFT JOIN关联查询
- 修改GltUserTicketLogMapper.xml实现用户表LEFT JOIN关联查询并优化搜索条件
- 修改GltUserTicketReleaseMapper.xml实现用户表LEFT JOIN关联查询并调整搜索逻辑
- 临时注释掉套票发放服务中的时间条件过滤逻辑
- 添加调试日志输出订单数量信息
2026-02-04 02:44:33 +08:00
d393de816f feat(ticket): 添加套票发放定时任务和核心服务
- 实现 GltTicketIssue10584Task 定时任务,每分钟扫描今日订单并生成套票账户
- 创建 GltTicketIssueService 服务,处理从订单生成用户套票和释放计划的完整流程
- 支持幂等处理,防止重复发放套票
- 实现月度释放计划生成功能,支持首期立即释放或次月释放模式
- 添加多租户支持和并发控制,确保任务执行安全
- 集成订单状态检查、套票模板验证和发放流水记录功能
2026-02-03 21:25:13 +08:00
27baa6ecf7 refactor(order): 移除套票发放相关代码
- 删除套票发放注释代码(冻结/可用、分期释放功能)
- 清理订单支付成功后的冗余业务逻辑
- 优化订单服务实现类的代码结构
2026-02-03 20:45:50 +08:00
d7a6b7cc94 feat(ticket): 实现套票分期释放功能核心数据结构
- 修改 GltUserTicketReleaseParam 中 id 和 userTicketId 类型从 Long 改为 Integer
- 移除 ShopOrderServiceImpl 中的 shopTicketBizService 依赖注入
- 注释掉订单支付成功后的套票发放调用
- 添加套票功能开发计划文档,定义套票模板、用户套票账户、释放计划和变更流水的核心概念
- 设计并创建套票相关数据库表,包括套票模板表、用户套票账户表、释放计划表和变更流水表
2026-02-03 20:37:11 +08:00
24133ef8a8 feat(glt): 添加水票功能模块
- 新增 GltTicketTemplate 实体类定义水票基础属性
- 实现 GltTicketTemplateController 提供水票管理API接口
- 创建 GltTicketTemplateMapper 和 XML 映射文件实现数据访问
- 定义 GltTicketTemplateParam 查询参数类
- 实现 GltTicketTemplateService 业务逻辑层接口
- 添加 GltUserTicket 实体类管理用户水票信息
- 实现 GltUserTicketController 控制器提供用户水票管理功能
- 新增 GltUserTicketLog 实体类记录消费日志
- 实现 GltUserTicketLogController 提供消费日志管理接口
- 完善相关 Mapper、Service 层接口及实现类
- 集成 Swagger 注解提供 API 文档支持
- 添加安全权限控制注解实现接口权限验证
2026-02-03 19:39:20 +08:00
35c155a1da feat(car): 实现车辆管理硬删除功能
- 在控制器中将删除方法改为硬删除(物理删除),绕过逻辑删除注解
- 在数据访问层添加硬删除SQL映射方法,支持单条和批量删除
- 在服务层定义硬删除接口方法并实现具体逻辑
- 添加空值校验确保删除操作的安全性
- 注释说明硬删除与逻辑删除的区别和用途
2026-02-02 18:33:54 +08:00
fc00728729 feat(order): 添加店铺ID字段到订单创建请求
- 在OrderCreateRequest DTO中新增storeId字段
- 为新字段添加Swagger文档注解支持
- 扩展订单创建功能以支持店铺维度的业务逻辑
2026-02-01 09:57:01 +08:00
0a466153f7 fix(order): 修复订单查询中的配送员表关联错误
- 修正 ShopOrderMapper.xml 中的表关联,将 shop_rider 表改为 shop_store_rider 表
- 移除 ShopStoreRiderController 中多余的权限验证注解
- 移除 ShopWarehouseController 中多余的权限验证注解
2026-02-01 02:35:28 +08:00
f364d180ea feat(order): 添加订单实体关联字段和查询功能
- 在ShopOrder实体中添加店铺ID、店铺名称、配送员ID、配送员名称、仓库ID、仓库名称字段
- 添加送达拍照记录字段用于记录配送完成时的照片
- 修改ShopOrderMapper.xml中的关联查询SQL,增加店铺、配送员、仓库表的LEFT JOIN关联
- 添加店铺名称、配送员名称、仓库名称的别名查询映射
- 在ShopOrderParam参数类中添加店铺ID、配送员ID、仓库ID查询条件字段
- 更新动态SQL条件判断,支持按店铺ID、配送员ID、仓库ID进行筛选查询
2026-02-01 01:45:15 +08:00
7f7b7527a0 feat(withdraw): 优化分销商提现流程并支持微信收款确认
- 添加提现方式必填校验,确保 payType 不为空
- 调整资金安全机制,申请后统一进入待审核状态(10),审核通过后用户主动领取
- 移除旧的微信提现逻辑,简化基础提现功能
- 增加防御性代码,防止前端未传字段时被更新为 NULL
- 修改审核通过逻辑,仅标记为 20 状态,等待用户主动领取
- 阻止后台直接设置微信提现已打款状态(40),需用户领取后自动完成
- 添加非微信转账场景的打款凭证上传要求
- 新增 receive 接口供用户领取提现,返回微信收款确认页 package_info
- 新增 receive-success 回调接口将状态置为已打款(40)
2026-01-31 22:21:11 +08:00
940e96f59d refactor(withdraw): 移除微信提现特殊处理逻辑
- 删除了微信提现改为"小程序拉起收款确认页"的特殊处理代码
- 移除了支付类型为10时自动设置申请状态为20的逻辑
- 清理了相关的时间戳设置和条件判断代码
2026-01-31 21:52:23 +08:00
5fe3801a4d refactor(shop): 重构店铺相关实体和参数类
- 将 ShopStore 中的 shopName 和 shopAddress 字段重命名为 name 和 address
- 在 ShopStore 中新增 location、district 和 points 字段
- 在 ShopStoreRider 中将 dealerId 重命名为 storeId,并新增 storeName 字段
- 更新 ShopStoreRiderMapper.xml 以关联查询门店名称
- 将 ShopStoreRiderParam 和 ShopStoreUserParam 中的 dealerId 重命名为 storeId
- 修改 application-prod.yml 中的微信支付场景信息,将岗位类型改为配送员,报酬说明改为12月份配送费
2026-01-31 21:37:53 +08:00
40aecd7c22 refactor(payment): 移除微信转账服务中的用户确认字段
- 移除了 initiateSingleTransferInternal 方法中的 userConfirm 参数
- 删除了 TransferSceneReportInfo 内部类中的 userConfirm 字段
- 移除了请求体中设置 userConfirm 字段的逻辑
- 更新了日志输出格式,移除 userConfirm 相关信息
- 添加注释说明微信侧对未定义字段的严格校验规则
- 保持方法兼容性以支持小程序拉起确认页功能
2026-01-31 16:24:46 +08:00
49998c71e4 feat(withdraw): 实现微信小程序提现确认功能
- 在ShopDealerWithdrawController中添加微信提现流程的完整实现
- 新增initiateSingleTransferWithUserConfirm方法支持小程序拉起收款确认页
- 添加用户openid验证和package_info返回逻辑
- 实现事务回滚机制处理支付异常情况
- 增加提现金额验证和分销商信息校验
- 添加详细的错误处理和用户提示信息
- 更新WxTransferService支持用户确认模式的转账接口
2026-01-31 16:16:35 +08:00
f9c693533c feat(credit): 添加数据状态字段并标记历史导入数据为失效
- 在CreditAdministrativeLicense实体中添加dataStatus字段
- 为行政许可控制器中的历史导入数据统一标记为"失效"
- 为失信被执行人控制器中的历史导入数据统一标记为"失效"
- 为法院庭审控制器中的历史导入数据统一标记为"失效"
- 为最终版本控制器中的历史导入数据统一标记为"失效"
- 为工商登记控制器中的历史导入数据统一标记为"失效"
- 为判决债务人控制器中的历史导入数据统一标记为"失效"
- 为司法文书控制器中的历史导入数据统一标记为"失效"
- 为信用修复控制器中的历史导入数据统一标记为"失效"
2026-01-31 13:17:10 +08:00
dff8b8f645 feat(judicial-document): 添加文书类型和涉案金额字段支持
- 在CreditJudicialDocument实体中新增type字段
- 在CreditJudicialDocumentImportParam参数中新增type、involvedAmount2和dataStatus字段
- 更新convertImportParamToEntity方法以处理新字段映射
- 优化涉案金额取值逻辑,优先使用involvedAmount2字段
- 完善Excel导入功能以支持新字段的数据映射
2026-01-31 12:51:59 +08:00
175708716c feat(excel): 支持导入发生时间字段并优化表头匹配
- 添加 occurrenceTime2 字段支持导入发生时间数据
- 实现表头单元格标准化处理,移除多余空格和特殊字符
- 解决因表头包含空白字符导致的列映射失败问题
- 支持对 "原告/上诉人" 等包含特殊分隔符的表头进行标准化
- 通过 WorkbookFactory 读取并重新写入 Excel 文件实现表头清理
2026-01-31 02:17:52 +08:00
ede52b6309 feat(import): 完善信用数据导入功能
- 新增数据类型和数据状态字段支持
- 添加原告/上诉人、被告/被上诉人等当事人字段
- 增加涉案金额、法院、发生时间等业务字段
- 实现新旧字段兼容性处理逻辑
- 更新导入模板示例数据配置
- 优化导入参数验证规则
- 扩展实体类字段映射关系
2026-01-31 01:42:04 +08:00
7c0df4fd08 feat(batch-import): 扩展批量导入支持多列企业名称匹配
- 新增 PARTY_SPLIT_PATTERN 正则表达式用于分割当事人名称
- 实现 refreshCompanyIdByCompanyNames 方法支持多列名称匹配
- 添加 splitPartyNames 工具方法处理当事人名称分割
- 优化公司ID刷新逻辑支持原告/被告等多个当事人字段
- 更新信用公示登记控制器使用多列名称
2026-01-31 01:13:16 +08:00
ae2eac39a0 refactor(credit): 移除多余的案号字段处理逻辑
- 删除了 CreditGqdjImportParam 中的 caseNumber3 字段及其 Excel 注解
- 移除了控制器中对 caseNumber3 的所有验证和处理逻辑
- 简化了案号字段的空值检查条件
- 更新了导入参数的过滤条件以匹配新的字段结构
- 优化了案号设置的条件判断流程
2026-01-31 00:08:29 +08:00
5753163c0e feat(shop): 添加仓库管理功能
- 创建ShopWarehouse实体类,包含仓库基本信息字段
- 实现ShopWarehouseController控制器,提供CRUD和批量操作接口
- 开发ShopWarehouseService业务接口及其实现类
- 配置ShopWarehouseMapper数据访问层和XML映射文件
- 添加ShopWarehouseParam查询参数类
- 集成权限控制、分页查询和关联查询功能
- 实现仓库类型的增删改查和批量处理逻辑
2026-01-30 19:08:58 +08:00
0cd1cb26f1 feat(shop): 新增门店、配送员和店员管理功能
- 创建 ShopStore 实体类,包含门店基本信息字段
- 实现 ShopStoreController 提供门店的增删改查和分页功能
- 添加 ShopStoreMapper 和对应的 XML 映射文件
- 创建 ShopStoreParam 查询参数类
- 创建 ShopStoreRider 实体类,包含配送员详细信息
- 实现 ShopStoreRiderController 管理配送员相关操作
- 添加 ShopStoreRiderMapper 和 XML 映射配置
- 创建 ShopStoreRiderParam 查询参数类
- 实现 ShopStoreRiderService 业务逻辑层接口及其实现
- 创建 ShopStoreUser 实体类用于管理店员信息
- 实现 ShopStoreUserController 提供店员管理功能
- 添加相应的 Service 层接口和服务实现类
- 配置权限控制注解和 Swagger 文档注解
- 实现批量操作功能包括批量添加、修改和删除
- 添加分页查询和列表查询的关联查询功能
2026-01-30 16:21:21 +08:00
2059c90047 fix(data-import): 修复股权冻结导入功能中的参数映射和模板兼容性问题
- 修复了多个信用相关模块中的appellee参数映射错误
- 为Excel导入功能添加了多模板兼容支持,包括案号、暗号等不同字段名
- 增强了Excel导入的容错能力,支持多种表头配置和异常处理
- 扩展了超链接提取功能,支持从多个可能的列名获取URL信息
- 添加了fallback机制以处理不同上游数据源的字段映射差异
- 改进了空行过滤逻辑,提高了数据导入准确性
2026-01-30 14:16:56 +08:00
4da2a84421 fix(data-import): 修复股权冻结导入功能中的参数映射和模板兼容性问题
- 修复了多个信用相关模块中的appellee参数映射错误
- 为Excel导入功能添加了多模板兼容支持,包括案号、暗号等不同字段名
- 增强了Excel导入的容错能力,支持多种表头配置和异常处理
- 扩展了超链接提取功能,支持从多个可能的列名获取URL信息
- 添加了fallback机制以处理不同上游数据源的字段映射差异
- 改进了空行过滤逻辑,提高了数据导入准确性
2026-01-30 14:16:33 +08:00
5ac0eef8a6 feat(mapper): 更新多个信用模块的关键词搜索功能
- 在CreditBreachOfTrustMapper中添加案件编号关键词搜索
- 在CreditCaseFilingMapper中添加案件编号关键词搜索
- 在CreditCourtAnnouncementMapper中添加案件编号关键词搜索
- 在CreditCourtSessionMapper中添加案件编号关键词搜索
- 在CreditCustomerMapper中添加客户名称关键词搜索
- 在CreditDeliveryNoticeMapper中添加案件编号关键词搜索
- 在CreditExternalMapper中修复外部数据关键词搜索参数
- 在CreditJudgmentDebtorMapper中添加案件编号关键词搜索
- 在CreditJudicialDocumentMapper中添加案件编号关键词搜索
- 在CreditMediationMapper中添加案件编号关键词搜索
- 统一各mapper中的SQL查询格式化缩进
2026-01-30 13:01:53 +08:00
79b2d584dc feat(mapper): 更新多个信用模块的关键词搜索功能
- 在CreditBreachOfTrustMapper中添加案件编号关键词搜索
- 在CreditCaseFilingMapper中添加案件编号关键词搜索
- 在CreditCourtAnnouncementMapper中添加案件编号关键词搜索
- 在CreditCourtSessionMapper中添加案件编号关键词搜索
- 在CreditCustomerMapper中添加客户名称关键词搜索
- 在CreditDeliveryNoticeMapper中添加案件编号关键词搜索
- 在CreditExternalMapper中修复外部数据关键词搜索参数
- 在CreditJudgmentDebtorMapper中添加案件编号关键词搜索
- 在CreditJudicialDocumentMapper中添加案件编号关键词搜索
- 在CreditMediationMapper中添加案件编号关键词搜索
- 统一各mapper中的SQL查询格式化缩进
2026-01-30 12:55:29 +08:00
0af3b6467d feat(mapper): 更新多个信用模块的关键词搜索功能
- 在CreditBreachOfTrustMapper中添加案件编号关键词搜索
- 在CreditCaseFilingMapper中添加案件编号关键词搜索
- 在CreditCourtAnnouncementMapper中添加案件编号关键词搜索
- 在CreditCourtSessionMapper中添加案件编号关键词搜索
- 在CreditCustomerMapper中添加客户名称关键词搜索
- 在CreditDeliveryNoticeMapper中添加案件编号关键词搜索
- 在CreditExternalMapper中修复外部数据关键词搜索参数
- 在CreditJudgmentDebtorMapper中添加案件编号关键词搜索
- 在CreditJudicialDocumentMapper中添加案件编号关键词搜索
- 在CreditMediationMapper中添加案件编号关键词搜索
- 统一各mapper中的SQL查询格式化缩进
2026-01-30 12:44:42 +08:00
e2f3b444ae feat(mapper): 扩展关键词搜索功能支持更多字段
- 在CreditAdministrativeLicenseMapper中添加code字段搜索
- 在CreditBankruptcyMapper中添加code字段搜索
- 在CreditBranchMapper中添加name和curator字段搜索
- 在CreditHistoricalLegalPersonMapper中添加name字段搜索
- 在CreditJudiciaryMapper中添加code字段搜索
- 在CreditPatentMapper中添加public_no和register_no字段搜索
- 在CreditSuspectedRelationshipMapper中添加name字段搜索
2026-01-30 10:48:43 +08:00
20a24a46c4 feat(community): 添加小区管理功能模块
- 新增 ShopCommunity 实体类,定义小区基本信息字段
- 创建 ShopCommunityController 控制器,提供完整的 CRUD 操作接口
- 实现 ShopCommunityService 服务层接口及其实现类
- 配置 ShopCommunityMapper 数据访问层及对应的 XML 映射文件
- 添加 ShopCommunityParam 查询参数类
- 修改 ShopDealerUser 实体增加小区和店铺相关字段
- 更新 ShopDealerUserMapper.xml 添加店铺名称关联查询
2026-01-30 10:30:42 +08:00
137 changed files with 6558 additions and 281 deletions

1
.gitignore vendored
View File

@@ -42,3 +42,4 @@ ehthumbs.db
Thumbs.db
/file/
/websoft-modules.log
/tmp/

View File

@@ -0,0 +1,31 @@
# 套票(冻结/可用、分期释放)功能开发计划
## 目标
- 为“买N送M例如买1送4、起售例如20桶起售、按月释放例如每月释放10桶、可用未用完叠加”的套票/权益,提供后端可配置、可发放、可释放、可消费的能力。
## 核心概念
- 套票模板:按商品(goodsId)配置买赠规则、起售/起送、释放规则(每期释放数或释放期数)、首期释放时机。
- 用户套票账户:记录总量、可用量、冻结量、已用量、已释放量;绑定订单(用于幂等与追溯)。
- 释放计划:每期一条,到期后把冻结转可用(可用未用完自然叠加)。
- 变更流水:发放/释放/消费等都记录流水,便于对账与排查。
## 关键默认规则(可在模板里改)
- 仅赠送量进入套票账户默认不包含“购买量”本身如需“买20送80=总100”可在模板设置 `includeBuyQty=true`
- 首期释放:默认“支付成功当日/当刻”释放(`firstReleaseMode=0`);如需“下个月同日释放”,设 `firstReleaseMode=1`
- 每期释放:默认按 `monthlyReleaseQty`;如配置了 `releasePeriods`,则平均分摊并处理余数。
## 开发步骤(建议按顺序)
1. 需求确认与接入点确认(订单哪个节点发放、桶票如何消费/核销、退款是否回滚、释放日期规则)。
2. 数据表设计与SQL输出模板/账户/释放计划/流水)。
3. 实现套票模板后台CRUD接口。
4. 支付成功接入:在订单支付成功后发放套票账户+生成释放计划(幂等)。
5. 定时任务释放:扫描到期释放计划,执行“冻结->可用”转账(幂等)。
6. 消费扣减对用户可用量做扣减支持跨多套票账户FIFO扣减并落流水。
7. 测试与验收:至少覆盖(买赠计算、分期拆分、叠加逻辑、幂等、并发扣减)。
## 待确认点(不确认也可先按默认实现)
- 套票数量=赠送量?还是(购买量+赠送量)?
- “释放日期”是按支付时间的“日”还是固定每月某一天31号跨月如何处理
- 退款/取消:是否回滚未使用的可用/冻结?已释放但未用如何处理?
- 消费场景:在哪个业务点扣减(下单抵扣/提货核销/线下核销)?

View File

@@ -0,0 +1,121 @@
-- 套票(冻结/可用、分期释放)相关表
-- 说明:项目使用了 MyBatis-Plus 的 tenant 拦截;表内显式保留 tenant_id 字段并建议建立联合唯一索引。
-- 1) 套票模板(按商品配置)
CREATE TABLE IF NOT EXISTS shop_ticket_template (
id INT AUTO_INCREMENT PRIMARY KEY,
goods_id INT NOT NULL,
name VARCHAR(100) NOT NULL,
enabled TINYINT(1) NOT NULL DEFAULT 1,
unit_name VARCHAR(20) NOT NULL DEFAULT '',
-- 起售/起送
min_buy_qty INT NOT NULL DEFAULT 1,
start_send_qty INT NOT NULL DEFAULT 1,
-- 买赠买1送4 => gift_multiplier=4
gift_multiplier INT NOT NULL DEFAULT 0,
-- 是否把购买量也计入套票总量(默认仅计入赠送量)
include_buy_qty TINYINT(1) NOT NULL DEFAULT 0,
-- 释放规则:二选一
-- A) 每期释放数量默认每月释放10
monthly_release_qty INT NOT NULL DEFAULT 10,
-- B) 总共释放多少期(若配置>0则按期数平均分摊
release_periods INT NULL,
-- 首期释放时机0=支付成功当刻1=下个月同日
first_release_mode INT NOT NULL DEFAULT 0,
comments VARCHAR(255) NULL,
sort_number INT NOT NULL DEFAULT 0,
user_id INT NULL,
deleted INT NOT NULL DEFAULT 0,
tenant_id INT NOT NULL,
create_time DATETIME NULL,
update_time DATETIME NULL,
UNIQUE KEY uk_ticket_template_tenant_goods (tenant_id, goods_id),
KEY idx_ticket_template_tenant (tenant_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 2) 用户套票账户(一次购买通常生成一条)
CREATE TABLE IF NOT EXISTS shop_user_ticket (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
template_id INT NOT NULL,
goods_id INT NOT NULL,
user_id INT NOT NULL,
order_id INT NULL,
order_no VARCHAR(64) NULL,
order_goods_id INT NULL,
total_qty INT NOT NULL DEFAULT 0,
available_qty INT NOT NULL DEFAULT 0,
frozen_qty INT NOT NULL DEFAULT 0,
used_qty INT NOT NULL DEFAULT 0,
released_qty INT NOT NULL DEFAULT 0,
status INT NOT NULL DEFAULT 0,
deleted INT NOT NULL DEFAULT 0,
tenant_id INT NOT NULL,
create_time DATETIME NULL,
update_time DATETIME NULL,
KEY idx_user_ticket_user (tenant_id, user_id),
KEY idx_user_ticket_order (tenant_id, order_no),
UNIQUE KEY uk_user_ticket_order_goods (tenant_id, template_id, order_no, order_goods_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 3) 释放计划/释放记录(每期一条,幂等执行)
CREATE TABLE IF NOT EXISTS shop_user_ticket_release (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_ticket_id BIGINT NOT NULL,
user_id INT NOT NULL,
period_no INT NOT NULL,
release_qty INT NOT NULL,
release_time DATETIME NOT NULL,
status INT NOT NULL DEFAULT 0, -- 0待释放 1已释放 2作废
released_time DATETIME NULL,
tenant_id INT NOT NULL,
create_time DATETIME NULL,
update_time DATETIME NULL,
UNIQUE KEY uk_ticket_release_period (tenant_id, user_ticket_id, period_no),
KEY idx_ticket_release_due (status, release_time),
KEY idx_ticket_release_user (tenant_id, user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 4) 套票变更流水(发放/释放/消费/回滚等)
CREATE TABLE IF NOT EXISTS shop_user_ticket_log (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_ticket_id BIGINT NOT NULL,
user_id INT NOT NULL,
change_type INT NOT NULL, -- 10发放 20释放 30消费 40回滚/退款
change_available INT NOT NULL DEFAULT 0,
change_frozen INT NOT NULL DEFAULT 0,
change_used INT NOT NULL DEFAULT 0,
available_after INT NOT NULL DEFAULT 0,
frozen_after INT NOT NULL DEFAULT 0,
used_after INT NOT NULL DEFAULT 0,
order_id INT NULL,
order_no VARCHAR(64) NULL,
remark VARCHAR(255) NULL,
tenant_id INT NOT NULL,
create_time DATETIME NULL,
update_time DATETIME NULL,
KEY idx_ticket_log_user (tenant_id, user_id),
KEY idx_ticket_log_ticket (tenant_id, user_ticket_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

View File

@@ -22,6 +22,7 @@ import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.regex.Pattern;
/**
* credit 模块 Excel 导入批处理支持:
@@ -32,6 +33,7 @@ import java.util.function.Supplier;
public class BatchImportSupport {
private final TransactionTemplate requiresNewTx;
private static final Pattern PARTY_SPLIT_PATTERN = Pattern.compile("[,;;、\\n\\r\\t/|]+");
public BatchImportSupport(PlatformTransactionManager transactionManager) {
TransactionTemplate template = new TransactionTemplate(transactionManager);
@@ -71,7 +73,7 @@ public class BatchImportSupport {
/**
* 按企业名称匹配 CreditCompany(name / matchName) 并回填 companyId。
*
* <p>默认仅更新 companyId=0 的记录onlyNull=trueonlyNull=false 时会覆盖更新(仅当 companyId 不同)。</p>
* <p>默认仅更新 companyId 为空/0 的记录onlyNull=trueonlyNull=false 时会覆盖更新(仅当 companyId 不同)。</p>
*
* <p>注意:为避免跨租户误更新,当 currentTenantId 为空时会按记录自身 tenantId 维度匹配,
* tenantId 为空的记录将被跳过并计入 notFound。</p>
@@ -90,15 +92,80 @@ public class BatchImportSupport {
BiConsumer<T, Boolean> hasDataSetter,
SFunction<T, Integer> tenantIdGetter,
Supplier<T> patchFactory) {
// Keep existing API; delegate to the multi-column implementation.
return refreshCompanyIdByCompanyNames(service,
creditCompanyService,
currentTenantId,
onlyNull,
limit,
idGetter,
idSetter,
companyIdGetter,
companyIdSetter,
hasDataGetter,
hasDataSetter,
tenantIdGetter,
patchFactory,
nameGetter);
}
/**
* 按多列“当事人/企业名称”匹配 CreditCompany(name / matchName) 并回填 companyId。
*
* <p>按传入列顺序优先匹配:原告/上诉人 &gt; 被告/被上诉人 &gt; 其他当事人/第三人等。</p>
*
* <p>同一列若匹配到多个不同企业则视为歧义;若最终无法得到唯一 companyId则跳过并计入 ambiguous/notFound。</p>
*/
@SafeVarargs
public final <T> CompanyIdRefreshStats refreshCompanyIdByCompanyNames(IService<T> service,
CreditCompanyService creditCompanyService,
Integer currentTenantId,
Boolean onlyNull,
Integer limit,
SFunction<T, Integer> idGetter,
BiConsumer<T, Integer> idSetter,
SFunction<T, Integer> companyIdGetter,
BiConsumer<T, Integer> companyIdSetter,
SFunction<T, Boolean> hasDataGetter,
BiConsumer<T, Boolean> hasDataSetter,
SFunction<T, Integer> tenantIdGetter,
Supplier<T> patchFactory,
SFunction<T, String>... nameGetters) {
boolean onlyNullFlag = (onlyNull == null) || Boolean.TRUE.equals(onlyNull);
if (nameGetters == null || nameGetters.length == 0) {
return new CompanyIdRefreshStats(false, 0, 0, 0, 0);
}
// 1) 读取待处理数据(仅取必要字段,避免一次性拉全表字段)
@SuppressWarnings({"rawtypes", "unchecked"})
SFunction<T, ?>[] selectColumns = (SFunction<T, ?>[]) new SFunction[4 + nameGetters.length];
int colIdx = 0;
selectColumns[colIdx++] = idGetter;
selectColumns[colIdx++] = companyIdGetter;
selectColumns[colIdx++] = hasDataGetter;
selectColumns[colIdx++] = tenantIdGetter;
for (SFunction<T, String> ng : nameGetters) {
selectColumns[colIdx++] = ng;
}
var query = service.lambdaQuery()
.select(idGetter, nameGetter, companyIdGetter, hasDataGetter, tenantIdGetter)
.select(selectColumns)
.eq(currentTenantId != null, tenantIdGetter, currentTenantId)
.isNotNull(nameGetter);
.and(w -> {
// Only process rows that have at least one name column populated.
for (int i = 0; i < nameGetters.length; i++) {
if (i == 0) {
w.isNotNull(nameGetters[i]);
} else {
w.or().isNotNull(nameGetters[i]);
}
}
});
if (onlyNullFlag) {
query.eq(companyIdGetter, 0);
// Historically some tables used 0 as the "unset" companyId, while others left it NULL.
// Treat both as "unset" so refresh won't silently do nothing.
query.and(w -> w.isNull(companyIdGetter).or().eq(companyIdGetter, 0));
}
if (limit != null && limit > 0) {
query.last("limit " + Math.min(limit, 200000));
@@ -146,9 +213,15 @@ public class BatchImportSupport {
LinkedHashMap<String, Integer> ambiguousByName = new LinkedHashMap<>();
LinkedHashSet<String> nameSet = new LinkedHashSet<>();
for (T row : tenantRows) {
String name = normalizeCompanyName(row != null ? nameGetter.apply(row) : null);
if (name != null) {
nameSet.add(name);
if (row == null) {
continue;
}
for (SFunction<T, String> ng : nameGetters) {
for (String name : splitPartyNames(ng.apply(row))) {
if (name != null) {
nameSet.add(name);
}
}
}
}
List<String> allNames = new ArrayList<>(nameSet);
@@ -174,20 +247,44 @@ public class BatchImportSupport {
// 3.2) 更新当前租户下的数据 companyId
for (T row : tenantRows) {
String key = normalizeCompanyName(row != null ? nameGetter.apply(row) : null);
if (key == null) {
if (row == null) {
continue;
}
Integer amb = ambiguousByName.get(key);
if (amb != null && amb > 0) {
ambiguous++;
continue;
Integer companyId = null;
boolean hasAmbiguousName = false;
for (SFunction<T, String> ng : nameGetters) {
LinkedHashSet<Integer> idsForColumn = new LinkedHashSet<>();
for (String key : splitPartyNames(ng.apply(row))) {
if (key == null) {
continue;
}
Integer amb = ambiguousByName.get(key);
if (amb != null && amb > 0) {
hasAmbiguousName = true;
continue;
}
Integer cid = companyIdByName.get(key);
if (cid != null) {
idsForColumn.add(cid);
}
}
if (idsForColumn.size() == 1) {
companyId = idsForColumn.iterator().next();
break;
}
if (idsForColumn.size() > 1) {
// Multiple companies matched within one column (e.g. multiple plaintiffs) -> ambiguous.
hasAmbiguousName = true;
}
}
Integer companyId = companyIdByName.get(key);
if (companyId == null) {
notFound++;
if (hasAmbiguousName) {
ambiguous++;
} else {
notFound++;
}
continue;
}
matched++;
@@ -196,7 +293,7 @@ public class BatchImportSupport {
Boolean oldHasData = row != null ? hasDataGetter.apply(row) : null;
boolean needUpdate;
if (onlyNullFlag) {
needUpdate = oldCompanyId != null && oldCompanyId == 0;
needUpdate = (oldCompanyId == null) || oldCompanyId == 0;
} else {
needUpdate = oldCompanyId == null || !companyId.equals(oldCompanyId);
}
@@ -710,6 +807,30 @@ public class BatchImportSupport {
return v.isEmpty() ? null : v;
}
/**
* Split a "party names" cell into normalized company name candidates.
* Supports common separators used in Excel/web copy (comma/semicolon/Chinese list delimiter/newlines).
*/
private static List<String> splitPartyNames(String raw) {
List<String> result = new ArrayList<>();
String v = normalizeCompanyName(raw);
if (v == null) {
return result;
}
String[] parts = PARTY_SPLIT_PATTERN.split(v);
if (parts == null || parts.length == 0) {
result.add(v);
return result;
}
for (String p : parts) {
String item = normalizeCompanyName(p);
if (item != null) {
result.add(item);
}
}
return result;
}
private static void addCompanyNameMapping(Map<String, Integer> idByName,
Map<String, Integer> ambiguousByName,
String key,

View File

@@ -464,6 +464,8 @@ public class CreditAdministrativeLicenseController extends BaseController {
if (item.getDeleted() == null) {
item.setDeleted(0);
}
// 历史导入的数据统一标记为“失效”
item.setDataStatus("失效");
String dedupKey = !ImportHelper.isBlank(item.getCode()) ? ("CODE:" + item.getCode()) : ("NAME:" + item.getName());
latestByKey.put(dedupKey, item);

View File

@@ -426,6 +426,8 @@ public class CreditBreachOfTrustController extends BaseController {
if (item.getDeleted() == null) {
item.setDeleted(0);
}
// 历史导入的数据统一标记为“失效”
item.setDataStatus("失效");
latestByCaseNumber.put(item.getCaseNumber(), item);
latestRowByCaseNumber.put(item.getCaseNumber(), excelRowNumber);
@@ -567,10 +569,13 @@ public class CreditBreachOfTrustController extends BaseController {
List<CreditBreachOfTrustImportParam> templateList = new ArrayList<>();
CreditBreachOfTrustImportParam example = new CreditBreachOfTrustImportParam();
example.setDataType("失信被执行人");
example.setCaseNumber("2024示例案号");
example.setPlaintiffAppellant("原告示例");
example.setAppellee("被告示例");
example.setOtherPartiesThirdParty("第三人示例");
example.setInvolvedAmount("20,293.91");
example.setDataStatus("正常");
example.setOccurrenceTime("2024-01-01");
example.setCourtName("示例法院");
example.setReleaseDate("2024-01-01");
@@ -591,18 +596,39 @@ public class CreditBreachOfTrustController extends BaseController {
}
return ImportHelper.isBlank(param.getCaseNumber())
&& ImportHelper.isBlank(param.getPlaintiffAppellant())
&& ImportHelper.isBlank(param.getAppellee());
&& ImportHelper.isBlank(param.getPlaintiffAppellant2())
&& ImportHelper.isBlank(param.getAppellee())
&& ImportHelper.isBlank(param.getAppellee2());
}
private CreditBreachOfTrust convertImportParamToEntity(CreditBreachOfTrustImportParam param) {
CreditBreachOfTrust entity = new CreditBreachOfTrust();
entity.setDataType(param.getDataType());
entity.setCaseNumber(param.getCaseNumber());
entity.setPlaintiffAppellant(param.getPlaintiffAppellant());
entity.setAppellee(param.getAppellee());
entity.setInvolvedAmount(param.getInvolvedAmount());
entity.setOccurrenceTime(param.getOccurrenceTime());
entity.setCourtName(param.getCourtName());
String plaintiffAppellant = !ImportHelper.isBlank(param.getPlaintiffAppellant2())
? param.getPlaintiffAppellant2()
: param.getPlaintiffAppellant();
entity.setPlaintiffAppellant(plaintiffAppellant);
String appellee = !ImportHelper.isBlank(param.getAppellee2())
? param.getAppellee2()
: param.getAppellee();
entity.setAppellee(appellee);
entity.setOtherPartiesThirdParty(param.getOtherPartiesThirdParty());
entity.setDataStatus(param.getDataStatus());
entity.setInvolvedAmount(!ImportHelper.isBlank(param.getInvolvedAmount2())
? param.getInvolvedAmount2()
: param.getInvolvedAmount());
entity.setOccurrenceTime(!ImportHelper.isBlank(param.getOccurrenceTime2())
? param.getOccurrenceTime2()
: param.getOccurrenceTime());
entity.setCourtName(!ImportHelper.isBlank(param.getCourtName2())
? param.getCourtName2()
: param.getCourtName());
entity.setReleaseDate(param.getReleaseDate());
entity.setComments(param.getComments());

View File

@@ -364,9 +364,14 @@ public class CreditCaseFilingController extends BaseController {
List<CreditCaseFilingImportParam> templateList = new ArrayList<>();
CreditCaseFilingImportParam example = new CreditCaseFilingImportParam();
example.setDataType("立案信息");
example.setPlaintiffAppellant("原告示例");
example.setAppellee("被告示例");
example.setOtherPartiesThirdParty2("第三人示例");
example.setInvolvedAmount("100000");
example.setDataStatus("正常");
example.setCaseNumber("2024示例案号");
example.setCauseOfAction("案由示例");
example.setOtherPartiesThirdParty("当事人示例");
example.setCourtName("示例法院");
example.setOccurrenceTime("2024-01-01");
example.setComments("备注信息");
@@ -386,21 +391,50 @@ public class CreditCaseFilingController extends BaseController {
return true;
}
return ImportHelper.isBlank(param.getCaseNumber())
&& ImportHelper.isBlank(param.getPlaintiffAppellant())
&& ImportHelper.isBlank(param.getAppellee())
&& ImportHelper.isBlank(param.getCauseOfAction())
&& ImportHelper.isBlank(param.getOtherPartiesThirdParty())
&& ImportHelper.isBlank(param.getOtherPartiesThirdParty2())
&& ImportHelper.isBlank(param.getCourtName())
&& ImportHelper.isBlank(param.getCourtName2())
&& ImportHelper.isBlank(param.getOccurrenceTime())
&& ImportHelper.isBlank(param.getOccurrenceTime2())
&& ImportHelper.isBlank(param.getInvolvedAmount())
&& ImportHelper.isBlank(param.getInvolvedAmount2())
&& ImportHelper.isBlank(param.getDataStatus())
&& ImportHelper.isBlank(param.getDataType())
&& ImportHelper.isBlank(param.getComments());
}
private CreditCaseFiling convertImportParamToEntity(CreditCaseFilingImportParam param) {
CreditCaseFiling entity = new CreditCaseFiling();
// Template compatibility: prefer new columns when present.
String occurrenceTime = !ImportHelper.isBlank(param.getOccurrenceTime2())
? param.getOccurrenceTime2()
: param.getOccurrenceTime();
String otherPartiesThirdParty = !ImportHelper.isBlank(param.getOtherPartiesThirdParty2())
? param.getOtherPartiesThirdParty2()
: param.getOtherPartiesThirdParty();
String courtName = !ImportHelper.isBlank(param.getCourtName2())
? param.getCourtName2()
: param.getCourtName();
String involvedAmount = !ImportHelper.isBlank(param.getInvolvedAmount2())
? param.getInvolvedAmount2()
: param.getInvolvedAmount();
entity.setDataType(param.getDataType());
entity.setPlaintiffAppellant(param.getPlaintiffAppellant());
entity.setAppellee(param.getAppellee());
entity.setDataStatus(param.getDataStatus());
entity.setCaseNumber(param.getCaseNumber());
entity.setCauseOfAction(param.getCauseOfAction());
entity.setOtherPartiesThirdParty(param.getOtherPartiesThirdParty());
entity.setCourtName(param.getCourtName());
entity.setOccurrenceTime(param.getOccurrenceTime());
entity.setOtherPartiesThirdParty(otherPartiesThirdParty);
entity.setCourtName(courtName);
entity.setOccurrenceTime(occurrenceTime);
entity.setInvolvedAmount(involvedAmount);
entity.setComments(param.getComments());
return entity;

View File

@@ -366,10 +366,14 @@ public class CreditCourtAnnouncementController extends BaseController {
CreditCourtAnnouncementImportParam example = new CreditCourtAnnouncementImportParam();
example.setDataType("法院公告");
example.setPlaintiffAppellant("原告示例");
example.setAppellee("被告示例");
example.setOtherPartiesThirdParty("第三人示例");
example.setOccurrenceTime("2024-01-01");
example.setCaseNumber("2024示例案号");
example.setCauseOfAction("案由示例");
example.setCourtName("示例法院");
example.setInvolvedAmount("100000");
example.setDataStatus("正常");
example.setComments("备注信息");
templateList.add(example);
@@ -393,12 +397,33 @@ public class CreditCourtAnnouncementController extends BaseController {
private CreditCourtAnnouncement convertImportParamToEntity(CreditCourtAnnouncementImportParam param) {
CreditCourtAnnouncement entity = new CreditCourtAnnouncement();
entity.setDataType(param.getDataType());
entity.setPlaintiffAppellant(param.getPlaintiffAppellant());
entity.setOtherPartiesThirdParty(param.getOtherPartiesThirdParty());
entity.setOccurrenceTime(param.getOccurrenceTime());
String dataType = !ImportHelper.isBlank(param.getDataType2())
? param.getDataType2()
: param.getDataType();
String plaintiffAppellant = !ImportHelper.isBlank(param.getPlaintiffAppellant2())
? param.getPlaintiffAppellant2()
: param.getPlaintiffAppellant();
String otherPartiesThirdParty = !ImportHelper.isBlank(param.getOtherPartiesThirdParty2())
? param.getOtherPartiesThirdParty2()
: param.getOtherPartiesThirdParty();
String involvedAmount = !ImportHelper.isBlank(param.getInvolvedAmount2())
? param.getInvolvedAmount2()
: param.getInvolvedAmount();
String occurrenceTime = !ImportHelper.isBlank(param.getOccurrenceTime2())
? param.getOccurrenceTime2()
: param.getOccurrenceTime();
entity.setDataType(dataType);
entity.setPlaintiffAppellant(plaintiffAppellant);
entity.setAppellee(param.getAppellee());
entity.setOtherPartiesThirdParty(otherPartiesThirdParty);
entity.setOccurrenceTime(occurrenceTime);
entity.setCaseNumber(param.getCaseNumber());
entity.setCauseOfAction(param.getCauseOfAction());
entity.setCourtName(param.getCourtName());
entity.setInvolvedAmount(involvedAmount);
entity.setDataStatus(param.getDataStatus());
entity.setComments(param.getComments());
return entity;

View File

@@ -430,6 +430,8 @@ public class CreditCourtSessionController extends BaseController {
if (item.getDeleted() == null) {
item.setDeleted(0);
}
// 历史导入的数据统一标记为“失效”
item.setDataStatus("失效");
latestByCaseNumber.put(item.getCaseNumber(), item);
latestRowByCaseNumber.put(item.getCaseNumber(), excelRowNumber);
@@ -571,11 +573,16 @@ public class CreditCourtSessionController extends BaseController {
List<CreditCourtSessionImportParam> templateList = new ArrayList<>();
CreditCourtSessionImportParam example = new CreditCourtSessionImportParam();
example.setOtherPartiesThirdParty("当事人");
example.setDataType("开庭公告");
example.setPlaintiffAppellant("原告示例");
example.setAppellee("被告示例");
example.setOtherPartiesThirdParty2("第三人示例");
example.setCaseNumber("2024示例案号");
example.setCauseOfAction("案由示例");
example.setCourtName("示例法院");
example.setOccurrenceTime("2024-01-01");
example.setInvolvedAmount("100000");
example.setDataStatus("正常");
example.setComments("备注信息");
templateList.add(example);
@@ -599,12 +606,29 @@ public class CreditCourtSessionController extends BaseController {
private CreditCourtSession convertImportParamToEntity(CreditCourtSessionImportParam param) {
CreditCourtSession entity = new CreditCourtSession();
entity.setOtherPartiesThirdParty(param.getOtherPartiesThirdParty());
entity.setOccurrenceTime(param.getOccurrenceTime());
entity.setCaseNumber(param.getCaseNumber());
entity.setCauseOfAction(param.getCauseOfAction());
entity.setCourtName(param.getCourtName());
entity.setComments(param.getComments());
// Template compatibility: prefer new columns ("发生时间"/"其他当事人/第三人") when present.
String occurrenceTime = !ImportHelper.isBlank(param.getOccurrenceTime2())
? param.getOccurrenceTime2()
: param.getOccurrenceTime();
String otherPartiesThirdParty = !ImportHelper.isBlank(param.getOtherPartiesThirdParty2())
? param.getOtherPartiesThirdParty2()
: param.getOtherPartiesThirdParty();
String involvedAmount = !ImportHelper.isBlank(param.getInvolvedAmount2())
? param.getInvolvedAmount2()
: param.getInvolvedAmount();
entity.setDataType(param.getDataType());
entity.setPlaintiffAppellant(param.getPlaintiffAppellant());
entity.setAppellee(param.getAppellee());
entity.setDataStatus(param.getDataStatus());
entity.setInvolvedAmount(involvedAmount);
entity.setOtherPartiesThirdParty(otherPartiesThirdParty);
entity.setOccurrenceTime(occurrenceTime);
entity.setCaseNumber(param.getCaseNumber());
entity.setCauseOfAction(param.getCauseOfAction());
entity.setCourtName(param.getCourtName());
entity.setComments(param.getComments());
return entity;
}

View File

@@ -365,7 +365,12 @@ public class CreditDeliveryNoticeController extends BaseController {
List<CreditDeliveryNoticeImportParam> templateList = new ArrayList<>();
CreditDeliveryNoticeImportParam example = new CreditDeliveryNoticeImportParam();
example.setOtherPartiesThirdParty("第三人示例");
example.setDataType("送达公告");
example.setPlaintiffAppellant("原告示例");
example.setAppellee("被告示例");
example.setOtherPartiesThirdParty2("第三人示例");
example.setInvolvedAmount("100000");
example.setDataStatus("正常");
example.setOccurrenceTime("2024-01-01");
example.setCaseNumber("2024示例案号");
example.setCauseOfAction("案由示例");
@@ -387,17 +392,40 @@ public class CreditDeliveryNoticeController extends BaseController {
return true;
}
return ImportHelper.isBlank(param.getCaseNumber())
&& ImportHelper.isBlank(param.getCauseOfAction());
&& ImportHelper.isBlank(param.getCauseOfAction())
&& ImportHelper.isBlank(param.getOtherPartiesThirdParty())
&& ImportHelper.isBlank(param.getOtherPartiesThirdParty2())
&& ImportHelper.isBlank(param.getPlaintiffAppellant())
&& ImportHelper.isBlank(param.getAppellee());
}
private CreditDeliveryNotice convertImportParamToEntity(CreditDeliveryNoticeImportParam param) {
CreditDeliveryNotice entity = new CreditDeliveryNotice();
entity.setOtherPartiesThirdParty(param.getOtherPartiesThirdParty());
entity.setOccurrenceTime(param.getOccurrenceTime());
String occurrenceTime = !ImportHelper.isBlank(param.getOccurrenceTime2())
? param.getOccurrenceTime2()
: param.getOccurrenceTime();
String otherPartiesThirdParty = !ImportHelper.isBlank(param.getOtherPartiesThirdParty2())
? param.getOtherPartiesThirdParty2()
: param.getOtherPartiesThirdParty();
String courtName = !ImportHelper.isBlank(param.getCourtName2())
? param.getCourtName2()
: param.getCourtName();
String involvedAmount = !ImportHelper.isBlank(param.getInvolvedAmount2())
? param.getInvolvedAmount2()
: param.getInvolvedAmount();
entity.setDataType(param.getDataType());
entity.setPlaintiffAppellant(param.getPlaintiffAppellant());
entity.setAppellee(param.getAppellee());
entity.setInvolvedAmount(involvedAmount);
entity.setDataStatus(param.getDataStatus());
entity.setOtherPartiesThirdParty(otherPartiesThirdParty);
entity.setOccurrenceTime(occurrenceTime);
entity.setCaseNumber(param.getCaseNumber());
entity.setCauseOfAction(param.getCauseOfAction());
entity.setCourtName(param.getCourtName());
entity.setCourtName(courtName);
entity.setComments(param.getComments());
return entity;

View File

@@ -430,6 +430,8 @@ public class CreditFinalVersionController extends BaseController {
if (item.getDeleted() == null) {
item.setDeleted(0);
}
// 历史导入的数据统一标记为“失效”
item.setDataStatus("失效");
latestByCaseNumber.put(item.getCaseNumber(), item);
latestRowByCaseNumber.put(item.getCaseNumber(), excelRowNumber);
@@ -574,7 +576,9 @@ public class CreditFinalVersionController extends BaseController {
example.setCaseNumber("2024示例案号");
example.setPlaintiffAppellant("原告示例");
example.setAppellee("被告示例");
example.setOtherPartiesThirdParty("第三人示例");
example.setInvolvedAmount("20,293.91");
example.setDataStatus("正常");
example.setCourtName("示例法院");
example.setOccurrenceTime("2024-01-01");
example.setFinalDate("2024-01-01");
@@ -600,13 +604,34 @@ public class CreditFinalVersionController extends BaseController {
private CreditFinalVersion convertImportParamToEntity(CreditFinalVersionImportParam param) {
CreditFinalVersion entity = new CreditFinalVersion();
String plaintiffAppellant = !ImportHelper.isBlank(param.getPlaintiffAppellant2())
? param.getPlaintiffAppellant2()
: param.getPlaintiffAppellant();
String appellee = !ImportHelper.isBlank(param.getAppellee2())
? param.getAppellee2()
: param.getAppellee();
String otherPartiesThirdParty = !ImportHelper.isBlank(param.getOtherPartiesThirdParty())
? param.getOtherPartiesThirdParty()
: param.getOtherPartiesThirdParty2();
String involvedAmount = !ImportHelper.isBlank(param.getInvolvedAmount2())
? param.getInvolvedAmount2()
: param.getInvolvedAmount();
String courtName = !ImportHelper.isBlank(param.getCourtName2())
? param.getCourtName2()
: param.getCourtName();
String occurrenceTime = !ImportHelper.isBlank(param.getOccurrenceTime2())
? param.getOccurrenceTime2()
: param.getOccurrenceTime();
entity.setCaseNumber(param.getCaseNumber());
entity.setPlaintiffAppellant(param.getPlaintiffAppellant());
entity.setAppellee(param.getAppellee());
entity.setPlaintiffAppellant(plaintiffAppellant);
entity.setAppellee(appellee);
entity.setUnfulfilledAmount(param.getUnfulfilledAmount());
entity.setInvolvedAmount(param.getInvolvedAmount());
entity.setCourtName(param.getCourtName());
entity.setOccurrenceTime(param.getOccurrenceTime());
entity.setInvolvedAmount(involvedAmount);
entity.setOtherPartiesThirdParty(otherPartiesThirdParty);
entity.setDataStatus(param.getDataStatus());
entity.setCourtName(courtName);
entity.setOccurrenceTime(occurrenceTime);
entity.setFinalDate(param.getFinalDate());
entity.setComments(param.getComments());

View File

@@ -145,9 +145,9 @@ public class CreditGqdjController extends BaseController {
}
/**
* 根据企业名称匹配企业并更新 companyId匹配 CreditCompany.name / CreditCompany.matchName
* 根据当事人/企业名称匹配企业并更新 companyId匹配 CreditCompany.name / CreditCompany.matchName
*
* <p>默认仅更新 companyId=0 的记录;如需覆盖更新,传 onlyNull=false。</p>
* <p>默认仅更新 companyId 为空/0 的记录;如需覆盖更新,传 onlyNull=false。</p>
*/
@PreAuthorize("hasAuthority('credit:creditGqdj:update')")
@OperationLog
@@ -160,7 +160,8 @@ public class CreditGqdjController extends BaseController {
User loginUser = getLoginUser();
Integer currentTenantId = loginUser != null ? loginUser.getTenantId() : null;
BatchImportSupport.CompanyIdRefreshStats stats = batchImportSupport.refreshCompanyIdByCompanyName(
// Match companyId by any party/company-name column (e.g. plaintiff/appellant, defendant/appellee).
BatchImportSupport.CompanyIdRefreshStats stats = batchImportSupport.refreshCompanyIdByCompanyNames(
creditGqdjService,
creditCompanyService,
currentTenantId,
@@ -168,13 +169,14 @@ public class CreditGqdjController extends BaseController {
limit,
CreditGqdj::getId,
CreditGqdj::setId,
CreditGqdj::getAppellee,
CreditGqdj::getCompanyId,
CreditGqdj::setCompanyId,
CreditGqdj::getHasData,
CreditGqdj::setHasData,
CreditGqdj::getTenantId,
CreditGqdj::new
CreditGqdj::new,
CreditGqdj::getPlaintiffAppellant,
CreditGqdj::getAppellee
);
if (!stats.anyDataRead) {
@@ -197,13 +199,39 @@ public class CreditGqdjController extends BaseController {
try {
int sheetIndex = ExcelImportSupport.findSheetIndex(file, "股权冻结", 0);
ExcelImportSupport.ImportResult<CreditGqdjImportParam> importResult = ExcelImportSupport.read(
file, CreditGqdjImportParam.class, this::isEmptyImportRow, sheetIndex);
// Prefer the "best" header configuration; many upstream files have extra title rows or multi-row headers.
ExcelImportSupport.ImportResult<CreditGqdjImportParam> importResult = ExcelImportSupport.readBest(
file,
CreditGqdjImportParam.class,
this::isEmptyImportRow,
// Score rows that look like real data (at least has a case number in either column).
p -> p != null
&& (!ImportHelper.isBlank(p.getCaseNumber())
|| !ImportHelper.isBlank(p.getCaseNumber2())
),
sheetIndex
);
List<CreditGqdjImportParam> list = importResult.getData();
int usedTitleRows = importResult.getTitleRows();
int usedHeadRows = importResult.getHeadRows();
int usedSheetIndex = importResult.getSheetIndex();
if (CollectionUtils.isEmpty(list)) {
// Fallback: try other sheets if the named/default sheet doesn't match.
importResult = ExcelImportSupport.readAnySheetBest(
file,
CreditGqdjImportParam.class,
this::isEmptyImportRow,
p -> p != null
&& (!ImportHelper.isBlank(p.getCaseNumber())
|| !ImportHelper.isBlank(p.getCaseNumber2()))
);
list = importResult.getData();
usedTitleRows = importResult.getTitleRows();
usedHeadRows = importResult.getHeadRows();
usedSheetIndex = importResult.getSheetIndex();
}
if (CollectionUtils.isEmpty(list)) {
return fail("未读取到数据,请确认模板表头与示例格式一致", null);
}
@@ -211,10 +239,18 @@ public class CreditGqdjController extends BaseController {
User loginUser = getLoginUser();
Integer currentUserId = loginUser != null ? loginUser.getUserId() : null;
Integer currentTenantId = loginUser != null ? loginUser.getTenantId() : null;
// easypoi 默认不会读取单元格超链接地址url 通常挂在“执行通知文书号”列的超链接中,需要额外读取回填。
// easypoi 默认不会读取单元格超链接地址url 通常挂在“案号/执行通知文书号”列的超链接中,需要额外读取回填。
String caseNumberHeader = "执行通知文书号";
Map<String, String> urlByCaseNumber = ExcelImportSupport.readHyperlinksByHeaderKey(
file, usedSheetIndex, usedTitleRows, usedHeadRows, caseNumberHeader);
// Some upstream sources use "案号" as the case number header.
Map<String, String> urlByCaseNumberFromAh = ExcelImportSupport.readHyperlinksByHeaderKey(
file, usedSheetIndex, usedTitleRows, usedHeadRows, "案号");
urlByCaseNumberFromAh.forEach(urlByCaseNumber::putIfAbsent);
// Some upstream sources use "暗号" as the case number header.
Map<String, String> urlByCaseNumberFromAh2 = ExcelImportSupport.readHyperlinksByHeaderKey(
file, usedSheetIndex, usedTitleRows, usedHeadRows, "暗号");
urlByCaseNumberFromAh2.forEach(urlByCaseNumber::putIfAbsent);
// 有些源文件会单独提供“url/网址/链接”等列(可能是纯文本也可能是超链接)
Map<String, String> urlByCaseNumberFromUrlCol = ExcelImportSupport.readKeyValueByHeaders(
file, usedSheetIndex, usedTitleRows, usedHeadRows, caseNumberHeader, "url");
@@ -230,6 +266,40 @@ public class CreditGqdjController extends BaseController {
urlByCaseNumberFromUrlCol = ExcelImportSupport.readKeyValueByHeaders(
file, usedSheetIndex, usedTitleRows, usedHeadRows, caseNumberHeader, "链接");
}
if (urlByCaseNumberFromUrlCol.isEmpty()) {
// Try again with "案号" as the key column name.
urlByCaseNumberFromUrlCol = ExcelImportSupport.readKeyValueByHeaders(
file, usedSheetIndex, usedTitleRows, usedHeadRows, "案号", "url");
if (urlByCaseNumberFromUrlCol.isEmpty()) {
urlByCaseNumberFromUrlCol = ExcelImportSupport.readKeyValueByHeaders(
file, usedSheetIndex, usedTitleRows, usedHeadRows, "案号", "URL");
}
if (urlByCaseNumberFromUrlCol.isEmpty()) {
urlByCaseNumberFromUrlCol = ExcelImportSupport.readKeyValueByHeaders(
file, usedSheetIndex, usedTitleRows, usedHeadRows, "案号", "网址");
}
if (urlByCaseNumberFromUrlCol.isEmpty()) {
urlByCaseNumberFromUrlCol = ExcelImportSupport.readKeyValueByHeaders(
file, usedSheetIndex, usedTitleRows, usedHeadRows, "案号", "链接");
}
}
if (urlByCaseNumberFromUrlCol.isEmpty()) {
// Try again with "暗号" as the key column name.
urlByCaseNumberFromUrlCol = ExcelImportSupport.readKeyValueByHeaders(
file, usedSheetIndex, usedTitleRows, usedHeadRows, "暗号", "url");
if (urlByCaseNumberFromUrlCol.isEmpty()) {
urlByCaseNumberFromUrlCol = ExcelImportSupport.readKeyValueByHeaders(
file, usedSheetIndex, usedTitleRows, usedHeadRows, "暗号", "URL");
}
if (urlByCaseNumberFromUrlCol.isEmpty()) {
urlByCaseNumberFromUrlCol = ExcelImportSupport.readKeyValueByHeaders(
file, usedSheetIndex, usedTitleRows, usedHeadRows, "暗号", "网址");
}
if (urlByCaseNumberFromUrlCol.isEmpty()) {
urlByCaseNumberFromUrlCol = ExcelImportSupport.readKeyValueByHeaders(
file, usedSheetIndex, usedTitleRows, usedHeadRows, "暗号", "链接");
}
}
final int chunkSize = 500;
final int mpBatchSize = 500;
@@ -399,8 +469,15 @@ public class CreditGqdjController extends BaseController {
return fail("未读取到数据,请确认文件中存在“历史股权冻结”选项卡且表头与示例格式一致", null);
}
ExcelImportSupport.ImportResult<CreditGqdjImportParam> importResult = ExcelImportSupport.read(
file, CreditGqdjImportParam.class, this::isEmptyImportRow, sheetIndex);
ExcelImportSupport.ImportResult<CreditGqdjImportParam> importResult = ExcelImportSupport.readBest(
file,
CreditGqdjImportParam.class,
this::isEmptyImportRow,
p -> p != null
&& (!ImportHelper.isBlank(p.getCaseNumber())
|| !ImportHelper.isBlank(p.getCaseNumber2())),
sheetIndex
);
List<CreditGqdjImportParam> list = importResult.getData();
int usedTitleRows = importResult.getTitleRows();
int usedHeadRows = importResult.getHeadRows();
@@ -417,6 +494,12 @@ public class CreditGqdjController extends BaseController {
String caseNumberHeader = "执行通知文书号";
Map<String, String> urlByCaseNumber = ExcelImportSupport.readHyperlinksByHeaderKey(
file, usedSheetIndex, usedTitleRows, usedHeadRows, caseNumberHeader);
Map<String, String> urlByCaseNumberFromAh = ExcelImportSupport.readHyperlinksByHeaderKey(
file, usedSheetIndex, usedTitleRows, usedHeadRows, "案号");
urlByCaseNumberFromAh.forEach(urlByCaseNumber::putIfAbsent);
Map<String, String> urlByCaseNumberFromAh2 = ExcelImportSupport.readHyperlinksByHeaderKey(
file, usedSheetIndex, usedTitleRows, usedHeadRows, "暗号");
urlByCaseNumberFromAh2.forEach(urlByCaseNumber::putIfAbsent);
Map<String, String> urlByCaseNumberFromUrlCol = ExcelImportSupport.readKeyValueByHeaders(
file, usedSheetIndex, usedTitleRows, usedHeadRows, caseNumberHeader, "url");
if (urlByCaseNumberFromUrlCol.isEmpty()) {
@@ -431,6 +514,38 @@ public class CreditGqdjController extends BaseController {
urlByCaseNumberFromUrlCol = ExcelImportSupport.readKeyValueByHeaders(
file, usedSheetIndex, usedTitleRows, usedHeadRows, caseNumberHeader, "链接");
}
if (urlByCaseNumberFromUrlCol.isEmpty()) {
urlByCaseNumberFromUrlCol = ExcelImportSupport.readKeyValueByHeaders(
file, usedSheetIndex, usedTitleRows, usedHeadRows, "案号", "url");
if (urlByCaseNumberFromUrlCol.isEmpty()) {
urlByCaseNumberFromUrlCol = ExcelImportSupport.readKeyValueByHeaders(
file, usedSheetIndex, usedTitleRows, usedHeadRows, "案号", "URL");
}
if (urlByCaseNumberFromUrlCol.isEmpty()) {
urlByCaseNumberFromUrlCol = ExcelImportSupport.readKeyValueByHeaders(
file, usedSheetIndex, usedTitleRows, usedHeadRows, "案号", "网址");
}
if (urlByCaseNumberFromUrlCol.isEmpty()) {
urlByCaseNumberFromUrlCol = ExcelImportSupport.readKeyValueByHeaders(
file, usedSheetIndex, usedTitleRows, usedHeadRows, "案号", "链接");
}
if (urlByCaseNumberFromUrlCol.isEmpty()) {
urlByCaseNumberFromUrlCol = ExcelImportSupport.readKeyValueByHeaders(
file, usedSheetIndex, usedTitleRows, usedHeadRows, "暗号", "url");
if (urlByCaseNumberFromUrlCol.isEmpty()) {
urlByCaseNumberFromUrlCol = ExcelImportSupport.readKeyValueByHeaders(
file, usedSheetIndex, usedTitleRows, usedHeadRows, "暗号", "URL");
}
if (urlByCaseNumberFromUrlCol.isEmpty()) {
urlByCaseNumberFromUrlCol = ExcelImportSupport.readKeyValueByHeaders(
file, usedSheetIndex, usedTitleRows, usedHeadRows, "暗号", "网址");
}
if (urlByCaseNumberFromUrlCol.isEmpty()) {
urlByCaseNumberFromUrlCol = ExcelImportSupport.readKeyValueByHeaders(
file, usedSheetIndex, usedTitleRows, usedHeadRows, "暗号", "链接");
}
}
}
LinkedHashMap<String, CreditGqdj> latestByCaseNumber = new LinkedHashMap<>();
LinkedHashMap<String, Integer> latestRowByCaseNumber = new LinkedHashMap<>();
@@ -472,6 +587,8 @@ public class CreditGqdjController extends BaseController {
if (item.getDeleted() == null) {
item.setDeleted(0);
}
// 历史导入的数据统一标记为“失效”
item.setDataStatus("失效");
latestByCaseNumber.put(item.getCaseNumber(), item);
latestRowByCaseNumber.put(item.getCaseNumber(), excelRowNumber);
@@ -635,22 +752,61 @@ public class CreditGqdjController extends BaseController {
if (param == null) {
return true;
}
return ImportHelper.isBlank(param.getCaseNumber());
// Don't over-filter here: if some columns are mapped but case number header differs,
// we still want to read the row and report "案号不能为空" instead of "未读取到数据".
return ImportHelper.isBlank(param.getCaseNumber())
&& ImportHelper.isBlank(param.getCaseNumber2())
&& ImportHelper.isBlank(param.getAppellee())
&& ImportHelper.isBlank(param.getAppellee2())
&& ImportHelper.isBlank(param.getPlaintiffAppellant())
&& ImportHelper.isBlank(param.getPlaintiffAppellant2())
&& ImportHelper.isBlank(param.getInvolvedAmount())
&& ImportHelper.isBlank(param.getCourtName())
&& ImportHelper.isBlank(param.getDataType())
&& ImportHelper.isBlank(param.getDataStatus())
&& ImportHelper.isBlank(param.getDataStatus2())
&& ImportHelper.isBlank(param.getFreezeDateStart())
&& ImportHelper.isBlank(param.getFreezeDateEnd())
&& ImportHelper.isBlank(param.getFreezeDateStart2())
&& ImportHelper.isBlank(param.getFreezeDateEnd2())
&& ImportHelper.isBlank(param.getPublicDate());
}
private CreditGqdj convertImportParamToEntity(CreditGqdjImportParam param) {
CreditGqdj entity = new CreditGqdj();
entity.setCaseNumber(param.getCaseNumber());
entity.setAppellee(param.getAppellee());
entity.setPlaintiffAppellant(param.getPlaintiffAppellant());
// Template compatibility: some sources use alternate headers for the same columns.
String appellee = !ImportHelper.isBlank(param.getAppellee()) ? param.getAppellee() : param.getAppellee2();
String plaintiffAppellant = !ImportHelper.isBlank(param.getPlaintiffAppellant())
? param.getPlaintiffAppellant()
: param.getPlaintiffAppellant2();
if (!ImportHelper.isBlank(param.getCaseNumber2())) {
entity.setCaseNumber(param.getCaseNumber2());
} else {
entity.setCaseNumber(param.getCaseNumber());
}
entity.setAppellee(appellee);
entity.setPlaintiffAppellant(plaintiffAppellant);
entity.setInvolvedAmount(param.getInvolvedAmount());
entity.setCourtName(param.getCourtName());
entity.setDataStatus(param.getDataStatus());
if (!ImportHelper.isBlank(param.getDataStatus2())) {
entity.setDataStatus(param.getDataStatus2());
} else {
entity.setDataStatus(param.getDataStatus());
}
entity.setDataType("股权冻结");
entity.setPublicDate(param.getPublicDate());
entity.setFreezeDateStart(param.getFreezeDateStart());
entity.setFreezeDateEnd(param.getFreezeDateEnd());
if (!ImportHelper.isBlank(param.getFreezeDateStart2())) {
entity.setFreezeDateStart(param.getFreezeDateStart2());
} else {
entity.setFreezeDateStart(param.getFreezeDateStart());
}
if (!ImportHelper.isBlank(param.getFreezeDateEnd2())) {
entity.setFreezeDateEnd(param.getFreezeDateEnd2());
} else {
entity.setFreezeDateEnd(param.getFreezeDateEnd());
}
return entity;
}

View File

@@ -525,6 +525,8 @@ public class CreditJudgmentDebtorController extends BaseController {
if (item.getDeleted() == null) {
item.setDeleted(0);
}
// 历史导入的数据统一标记为“失效”
item.setDataStatus("失效");
latestByCaseNumber.put(item.getCaseNumber(), item);
latestRowByCaseNumber.put(item.getCaseNumber(), excelRowNumber);

View File

@@ -442,6 +442,8 @@ public class CreditJudicialDocumentController extends BaseController {
if (item.getDeleted() == null) {
item.setDeleted(0);
}
// 历史导入的数据统一标记为“失效”
item.setDataStatus("失效");
latestByCaseNumber.put(item.getCaseNumber(), item);
latestRowByCaseNumber.put(item.getCaseNumber(), excelRowNumber);
@@ -614,12 +616,18 @@ public class CreditJudicialDocumentController extends BaseController {
private CreditJudicialDocument convertImportParamToEntity(CreditJudicialDocumentImportParam param) {
CreditJudicialDocument entity = new CreditJudicialDocument();
String involvedAmount = !ImportHelper.isBlank(param.getInvolvedAmount2())
? param.getInvolvedAmount2()
: param.getInvolvedAmount();
entity.setTitle(param.getTitle());
entity.setType(param.getType());
entity.setDataStatus(param.getDataStatus());
entity.setOtherPartiesThirdParty(param.getOtherPartiesThirdParty());
entity.setOccurrenceTime(param.getOccurrenceTime());
entity.setCaseNumber(param.getCaseNumber());
entity.setCauseOfAction(param.getCauseOfAction());
entity.setInvolvedAmount(param.getInvolvedAmount());
entity.setInvolvedAmount(involvedAmount);
// Excel导入字段映射补全否则对应数据库字段(defendant_appellee/release_date)会一直为空
entity.setDefendantAppellee(param.getDefendantAppellee());
entity.setReleaseDate(param.getReleaseDate());

View File

@@ -391,12 +391,24 @@ public class CreditMediationController extends BaseController {
private CreditMediation convertImportParamToEntity(CreditMediationImportParam param) {
CreditMediation entity = new CreditMediation();
entity.setOtherPartiesThirdParty(param.getOtherPartiesThirdParty());
entity.setOccurrenceTime(param.getOccurrenceTime());
// Template compatibility: prefer new columns ("发生时间"/"其他当事人/第三人"), fallback to legacy ones ("立案日期"/"当事人").
String occurrenceTime = !ImportHelper.isBlank(param.getOccurrenceTime2())
? param.getOccurrenceTime2()
: param.getOccurrenceTime();
String otherPartiesThirdParty = !ImportHelper.isBlank(param.getOtherPartiesThirdParty2())
? param.getOtherPartiesThirdParty2()
: param.getOtherPartiesThirdParty();
entity.setOccurrenceTime(occurrenceTime);
entity.setOtherPartiesThirdParty(otherPartiesThirdParty);
entity.setCaseNumber(param.getCaseNumber());
entity.setCauseOfAction(param.getCauseOfAction());
entity.setCourtName(param.getCourtName());
entity.setComments(param.getComments());
entity.setPlaintiffAppellant(param.getPlaintiffAppellant());
entity.setAppellee(param.getAppellee());
entity.setInvolvedAmount(param.getInvolvedAmount());
entity.setDataStatus(param.getDataStatus());
return entity;
}

View File

@@ -430,6 +430,8 @@ public class CreditXgxfController extends BaseController {
if (item.getDeleted() == null) {
item.setDeleted(0);
}
// 历史导入的数据统一标记为“失效”
item.setDataStatus("失效");
latestByCaseNumber.put(item.getCaseNumber(), item);
latestRowByCaseNumber.put(item.getCaseNumber(), excelRowNumber);
@@ -603,11 +605,24 @@ public class CreditXgxfController extends BaseController {
entity.setCaseNumber(param.getCaseNumber());
entity.setType(param.getType());
entity.setDataType(param.getDataType());
entity.setPlaintiffUser(param.getPlaintiffUser());
entity.setDefendantUser(param.getDefendantUser());
entity.setOtherPartiesThirdParty(param.getOtherPartiesThirdParty());
entity.setPlaintiffAppellant(param.getPlaintiffAppellant());
entity.setDataStatus(param.getDataStatus());
entity.setAppellee(param.getAppellee());
entity.setInvolvedAmount(param.getInvolvedAmount());
entity.setOccurrenceTime(param.getOccurrenceTime());
entity.setCourtName(param.getCourtName());
// 兼容不同模板字段:如果 *2 有值则以 *2 为准写入主字段
entity.setInvolvedAmount(!ImportHelper.isBlank(param.getInvolvedAmount2())
? param.getInvolvedAmount2()
: param.getInvolvedAmount());
entity.setOccurrenceTime(!ImportHelper.isBlank(param.getOccurrenceTime2())
? param.getOccurrenceTime2()
: param.getOccurrenceTime());
entity.setCourtName(!ImportHelper.isBlank(param.getCourtName2())
? param.getCourtName2()
: param.getCourtName());
entity.setReleaseDate(param.getReleaseDate());
entity.setComments(param.getComments());

View File

@@ -15,6 +15,8 @@ import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.Collections;
import java.util.HashMap;
@@ -114,15 +116,55 @@ public class ExcelImportSupport {
int usedTitleRows = 0;
int usedHeadRows = 0;
int[][] tryConfigs = new int[][]{{1, 1}, {0, 1}, {2, 1}, {3, 1}, {0, 2}, {0, 3}, {0, 4}};
Exception lastFailure = null;
boolean anyImported = false;
for (int[] config : tryConfigs) {
list = filterEmptyRows(importSheet(file, clazz, config[0], config[1], sheetIndex), emptyRowPredicate);
try {
list = filterEmptyRows(importSheet(file, clazz, config[0], config[1], sheetIndex), emptyRowPredicate);
anyImported = true;
} catch (Exception e) {
// Some source files can trigger easypoi/POI runtime exceptions for certain title/head row combinations.
// Keep trying other configurations instead of failing the whole import.
lastFailure = e;
continue;
}
if (!CollectionUtils.isEmpty(list)) {
usedTitleRows = config[0];
usedHeadRows = config[1];
break;
}
}
// Fallback for upstream files with extra banner/title rows or wider multi-row headers.
// Keep the default fast path above for common templates.
if (CollectionUtils.isEmpty(list)) {
final int maxTitleRows = 10;
final int maxHeadRows = 6;
outer:
for (int titleRows = 0; titleRows <= maxTitleRows; titleRows++) {
for (int headRows = 1; headRows <= maxHeadRows; headRows++) {
try {
list = filterEmptyRows(importSheet(file, clazz, titleRows, headRows, sheetIndex), emptyRowPredicate);
anyImported = true;
} catch (Exception e) {
lastFailure = e;
continue;
}
if (!CollectionUtils.isEmpty(list)) {
usedTitleRows = titleRows;
usedHeadRows = headRows;
break outer;
}
}
}
}
if (list == null) {
if (!anyImported && lastFailure != null) {
throw lastFailure;
}
list = Collections.emptyList();
}
return new ImportResult<>(list, usedTitleRows, usedHeadRows, sheetIndex);
}
@@ -135,11 +177,20 @@ public class ExcelImportSupport {
int bestHeadRows = 0;
int bestScore = -1;
int bestSize = -1;
Exception lastFailure = null;
boolean anyImported = false;
int[][] tryConfigs = new int[][]{{1, 1}, {0, 1}, {2, 1}, {3, 1}, {0, 2}, {0, 3}, {0, 4}, {1, 2}, {1, 3}, {1, 4}, {2, 2}, {2, 3}, {2, 4}, {3, 2}, {3, 3}, {3, 4}};
for (int[] config : tryConfigs) {
List<T> list = filterEmptyRows(importSheet(file, clazz, config[0], config[1], sheetIndex), emptyRowPredicate);
List<T> list;
try {
list = filterEmptyRows(importSheet(file, clazz, config[0], config[1], sheetIndex), emptyRowPredicate);
anyImported = true;
} catch (Exception e) {
lastFailure = e;
continue;
}
if (CollectionUtils.isEmpty(list)) {
continue;
}
@@ -163,10 +214,52 @@ public class ExcelImportSupport {
}
}
// Fallback scan: broaden the search space for messy source files (extra title rows, multi-row headers).
if (bestList == null) {
final int maxTitleRows = 10;
final int maxHeadRows = 6;
for (int titleRows = 0; titleRows <= maxTitleRows; titleRows++) {
for (int headRows = 1; headRows <= maxHeadRows; headRows++) {
List<T> list;
try {
list = filterEmptyRows(importSheet(file, clazz, titleRows, headRows, sheetIndex), emptyRowPredicate);
anyImported = true;
} catch (Exception e) {
lastFailure = e;
continue;
}
if (CollectionUtils.isEmpty(list)) {
continue;
}
int score = 0;
if (scoreRowPredicate != null) {
for (T row : list) {
if (scoreRowPredicate.test(row)) {
score++;
}
}
}
int size = list.size();
if (score > bestScore || (score == bestScore && size > bestSize)) {
bestList = list;
bestTitleRows = titleRows;
bestHeadRows = headRows;
bestScore = score;
bestSize = size;
}
}
}
}
if (bestList != null) {
return new ImportResult<>(bestList, bestTitleRows, bestHeadRows, sheetIndex);
}
if (!anyImported && lastFailure != null) {
throw lastFailure;
}
return read(file, clazz, emptyRowPredicate, sheetIndex);
}
@@ -176,7 +269,76 @@ public class ExcelImportSupport {
importParams.setHeadRows(headRows);
importParams.setStartSheetIndex(sheetIndex);
importParams.setSheetNum(1);
return ExcelImportUtil.importExcel(file.getInputStream(), clazz, importParams);
// Easypoi matches headers by exact string. Some upstream files contain extra spaces/tabs/newlines in header cells
// (e.g. "原告 / 上诉人" or "原告/上诉人\t"), which makes specific columns silently not mapped.
// Normalize header cells first to make imports robust.
try (InputStream is = file.getInputStream(); Workbook workbook = WorkbookFactory.create(is)) {
if (workbook.getNumberOfSheets() > sheetIndex) {
Sheet sheet = workbook.getSheetAt(sheetIndex);
if (sheet != null) {
normalizeHeaderCells(sheet, titleRows, headRows);
}
}
try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
workbook.write(bos);
try (ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray())) {
return ExcelImportUtil.importExcel(bis, clazz, importParams);
}
}
}
}
private static void normalizeHeaderCells(Sheet sheet, int titleRows, int headRows) {
if (sheet == null || headRows <= 0) {
return;
}
int headerStart = Math.max(titleRows, 0);
int headerEnd = headerStart + headRows - 1;
for (int r = headerStart; r <= headerEnd; r++) {
Row row = sheet.getRow(r);
if (row == null) {
continue;
}
short last = row.getLastCellNum();
for (int c = 0; c < last; c++) {
Cell cell = row.getCell(c);
if (cell == null) {
continue;
}
String text = null;
CellType type = cell.getCellType();
if (type == CellType.STRING) {
text = cell.getStringCellValue();
} else if (type == CellType.FORMULA && cell.getCachedFormulaResultType() == CellType.STRING) {
text = cell.getStringCellValue();
} else {
continue;
}
String normalized = normalizeHeaderText(text);
if (normalized != null && !normalized.equals(text)) {
cell.setCellValue(normalized);
}
}
}
}
private static String normalizeHeaderText(String text) {
if (text == null) {
return null;
}
// Remove common invisible whitespace characters, including full-width space.
return text
.replace("", "/")
.replace(" ", "")
.replace("\t", "")
.replace("\r", "")
.replace("\n", "")
.replace("\u00A0", "")
.replace(" ", "")
.trim();
}
private static <T> List<T> filterEmptyRows(List<T> rawList, Predicate<T> emptyRowPredicate) {

View File

@@ -62,6 +62,9 @@ public class CreditAdministrativeLicense implements Serializable {
@Schema(description = "是否有数据")
private Boolean hasData;
@Schema(description = "数据状态")
private String dataStatus;
@Schema(description = "备注")
private String comments;

View File

@@ -30,39 +30,38 @@ public class CreditDeliveryNotice implements Serializable {
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
@Schema(description = "案号")
private String caseNumber;
@Schema(description = "案号")
private String caseNumber;
@Schema(description = "链接地址")
private String url;
@Schema(description = "链接地址")
private String url;
@Schema(description = "案由")
private String causeOfAction;
@Schema(description = "案由")
private String causeOfAction;
@Schema(description = "当事人")
private String otherPartiesThirdParty;
@Schema(description = "当事人")
private String otherPartiesThirdParty;
@Schema(description = "法院")
private String courtName;
@Schema(description = "法院")
private String courtName;
@Schema(description = "发布日期")
private String occurrenceTime;
@Schema(description = "发布日期")
private String occurrenceTime;
// @Schema(description = "数据类型")
// private String dataType;
//
// @Schema(description = "原告/上诉人")
// private String plaintiffAppellant;
//
// @Schema(description = "被告/被上诉人")
// @TableField("Appellee")
// private String appellee;
@Schema(description = "数据类型")
private String dataType;
// @Schema(description = "涉案金额")
// private String involvedAmount;
//
// @Schema(description = "数据状态")
// private String dataStatus;
@Schema(description = "原告/上诉人")
private String plaintiffAppellant;
@Schema(description = "被告/被上诉人")
private String appellee;
@Schema(description = "涉案金额")
private String involvedAmount;
@Schema(description = "数据状态")
private String dataStatus;
@Schema(description = "企业ID")
private Integer companyId;

View File

@@ -48,6 +48,9 @@ public class CreditFinalVersion implements Serializable {
@Schema(description = "执行标的(元)")
private String involvedAmount;
@Schema(description = "其他当事人/第三人")
private String otherPartiesThirdParty;
@Schema(description = "执行法院")
private String courtName;
@@ -57,6 +60,9 @@ public class CreditFinalVersion implements Serializable {
@Schema(description = "终本日期")
private String finalDate;
@Schema(description = "数据状态")
private String dataStatus;
@Schema(description = "企业ID")
private Integer companyId;

View File

@@ -31,7 +31,7 @@ public class CreditGqdj implements Serializable {
private Integer id;
@Schema(description = "执行通知文书号")
private String caseNumber;
private String caseNumber;;
@Schema(description = "被执行人")
private String appellee;

View File

@@ -33,6 +33,9 @@ public class CreditJudicialDocument implements Serializable {
@Schema(description = "文书标题")
private String title;
@Schema(description = "文书类型")
private String type;
@Schema(description = "案号")
private String caseNumber;

View File

@@ -107,4 +107,8 @@ public class CreditMediation implements Serializable {
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;
@Schema(description = "发生时间")
@TableField(exist = false)
private String occurrenceTime2;
}

View File

@@ -75,6 +75,7 @@
<if test="param.keywords != null">
AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%')
OR b.name LIKE CONCAT('%', #{param.keywords}, '%')
OR a.code LIKE CONCAT('%', #{param.keywords}, '%')
)
</if>
</where>

View File

@@ -63,7 +63,8 @@
</if>
<if test="param.keywords != null">
AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%')
OR b.name LIKE CONCAT('%', #{param.keywords}, '%')
OR b.name LIKE CONCAT('%', #{param.keywords}, '%')
OR a.code LIKE CONCAT('%', #{param.keywords}, '%')
)
</if>
</where>

View File

@@ -62,7 +62,9 @@
</if>
<if test="param.keywords != null">
AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%')
OR b.name LIKE CONCAT('%', #{param.keywords}, '%')
OR b.name LIKE CONCAT('%', #{param.keywords}, '%')
OR a.name LIKE CONCAT('%', #{param.keywords}, '%')
OR a.curator LIKE CONCAT('%', #{param.keywords}, '%')
)
</if>
</where>

View File

@@ -19,7 +19,7 @@
AND a.plaintiff_appellant LIKE CONCAT('%', #{param.plaintiffAppellant}, '%')
</if>
<if test="param.appellee != null">
AND a.appellee LIKE CONCAT('%', #{param.defendant appellee}, '%')
AND a.appellee LIKE CONCAT('%', #{param.appellee}, '%')
</if>
<if test="param.occurrenceTime != null">
AND a.occurrence_time LIKE CONCAT('%', #{param.occurrenceTime}, '%')
@@ -64,6 +64,7 @@
AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%')
OR a.case_number = #{param.keywords}
OR b.name LIKE CONCAT('%', #{param.keywords}, '%')
OR a.case_number LIKE CONCAT('%', #{param.keywords}, '%')
)
</if>
</where>

View File

@@ -22,7 +22,7 @@
AND a.plaintiff_appellant LIKE CONCAT('%', #{param.plaintiffAppellant}, '%')
</if>
<if test="param.appellee != null">
AND a.appellee LIKE CONCAT('%', #{param.defendant appellee}, '%')
AND a.appellee LIKE CONCAT('%', #{param.appellee}, '%')
</if>
<if test="param.otherPartiesThirdParty != null">
AND a.other_parties_third_party LIKE CONCAT('%', #{param.otherPartiesThirdParty}, '%')
@@ -75,6 +75,7 @@
<if test="param.keywords != null">
AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%')
OR b.name LIKE CONCAT('%', #{param.keywords}, '%')
OR a.case_number LIKE CONCAT('%', #{param.keywords}, '%')
)
</if>
</where>

View File

@@ -65,8 +65,8 @@
</if>
<if test="param.keywords != null">
AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%')
OR a.name LIKE CONCAT('%', #{param.keywords}, '%')
OR b.name LIKE CONCAT('%', #{param.keywords}, '%')
OR a.name LIKE CONCAT('%', #{param.keywords}, '%')
OR b.name LIKE CONCAT('%', #{param.keywords}, '%')
)
</if>
</where>

View File

@@ -22,7 +22,7 @@
AND a.plaintiff_appellant LIKE CONCAT('%', #{param.plaintiffAppellant}, '%')
</if>
<if test="param.appellee != null">
AND a.appellee LIKE CONCAT('%', #{param.defendant appellee}, '%')
AND a.appellee LIKE CONCAT('%', #{param.appellee}, '%')
</if>
<if test="param.otherPartiesThirdParty != null">
AND a.other_parties_third_party LIKE CONCAT('%', #{param.otherPartiesThirdParty}, '%')
@@ -75,6 +75,7 @@
<if test="param.keywords != null">
AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%')
OR b.name LIKE CONCAT('%', #{param.keywords}, '%')
OR a.case_number LIKE CONCAT('%', #{param.keywords}, '%')
)
</if>
</where>

View File

@@ -22,7 +22,7 @@
AND a.plaintiff_appellant LIKE CONCAT('%', #{param.plaintiffAppellant}, '%')
</if>
<if test="param.appellee != null">
AND a.appellee LIKE CONCAT('%', #{param.defendant appellee}, '%')
AND a.appellee LIKE CONCAT('%', #{param.appellee}, '%')
</if>
<if test="param.otherPartiesThirdParty != null">
AND a.other_parties_third_party LIKE CONCAT('%', #{param.otherPartiesThirdParty}, '%')
@@ -75,6 +75,7 @@
<if test="param.keywords != null">
AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%')
OR b.name LIKE CONCAT('%', #{param.keywords}, '%')
OR a.case_number LIKE CONCAT('%', #{param.keywords}, '%')
)
</if>
</where>

View File

@@ -59,7 +59,8 @@
</if>
<if test="param.keywords != null">
AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%')
OR b.name LIKE CONCAT('%', #{param.keywords}, '%')
OR b.name LIKE CONCAT('%', #{param.keywords}, '%')
OR a.name LIKE CONCAT('%', #{param.keywords}, '%')
)
</if>
</where>

View File

@@ -60,6 +60,7 @@
<if test="param.keywords != null">
AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%')
OR b.name LIKE CONCAT('%', #{param.keywords}, '%')
OR a.case_number LIKE CONCAT('%', #{param.keywords}, '%')
)
</if>
</where>

View File

@@ -87,6 +87,7 @@
<if test="param.keywords != null">
AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%')
OR b.name LIKE CONCAT('%', #{param.keywords}, '%')
OR a.name LIKE CONCAT('%', #{param.name}, '%')
)
</if>
</where>

View File

@@ -19,7 +19,7 @@
AND a.plaintiff_appellant LIKE CONCAT('%', #{param.plaintiffAppellant}, '%')
</if>
<if test="param.appellee != null">
AND a.appellee LIKE CONCAT('%', #{param.defendant appellee}, '%')
AND a.appellee LIKE CONCAT('%', #{param.appellee}, '%')
</if>
<if test="param.occurrenceTime != null">
AND a.occurrence_time LIKE CONCAT('%', #{param.occurrenceTime}, '%')

View File

@@ -22,7 +22,7 @@
AND a.plaintiff_appellant LIKE CONCAT('%', #{param.plaintiffAppellant}, '%')
</if>
<if test="param.appellee != null">
AND a.appellee LIKE CONCAT('%', #{param.defendant appellee}, '%')
AND a.appellee LIKE CONCAT('%', #{param.appellee}, '%')
</if>
<if test="param.caseNumber != null">
AND a.case_number LIKE CONCAT('%', #{param.caseNumber}, '%')

View File

@@ -56,7 +56,8 @@
</if>
<if test="param.keywords != null">
AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%')
OR b.name LIKE CONCAT('%', #{param.keywords}, '%')
OR b.name LIKE CONCAT('%', #{param.keywords}, '%')
OR a.name LIKE CONCAT('%', #{param.keywords}, '%')
)
</if>
</where>

View File

@@ -67,6 +67,7 @@
AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%')
OR a.case_number = #{param.keywords}
OR b.name LIKE CONCAT('%', #{param.keywords}, '%')
OR a.case_number LIKE CONCAT('%', #{param.keywords}, '%')
)
</if>
</where>

View File

@@ -62,7 +62,8 @@
</if>
<if test="param.keywords != null">
AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%')
OR b.name LIKE CONCAT('%', #{param.keywords}, '%')
OR b.name LIKE CONCAT('%', #{param.keywords}, '%')
OR a.case_number LIKE CONCAT('%', #{param.keywords}, '%')
)
</if>
</where>

View File

@@ -96,6 +96,7 @@
<if test="param.keywords != null">
AND (a.name LIKE CONCAT('%', #{param.keywords}, '%')
OR b.name LIKE CONCAT('%', #{param.keywords}, '%')
OR a.code LIKE CONCAT('%', #{param.keywords}, '%')
)
</if>
</where>

View File

@@ -22,7 +22,7 @@
AND a.plaintiff_appellant LIKE CONCAT('%', #{param.plaintiffAppellant}, '%')
</if>
<if test="param.appellee != null">
AND a.appellee LIKE CONCAT('%', #{param.defendant appellee}, '%')
AND a.appellee LIKE CONCAT('%', #{param.appellee}, '%')
</if>
<if test="param.otherPartiesThirdParty != null">
AND a.other_parties_third_party LIKE CONCAT('%', #{param.otherPartiesThirdParty}, '%')
@@ -75,6 +75,7 @@
<if test="param.keywords != null">
AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%')
OR b.name LIKE CONCAT('%', #{param.keywords}, '%')
OR a.case_number LIKE CONCAT('%', #{param.keywords}, '%')
)
</if>
</where>

View File

@@ -74,7 +74,9 @@
</if>
<if test="param.keywords != null">
AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%')
OR b.name LIKE CONCAT('%', #{param.keywords}, '%')
OR b.name LIKE CONCAT('%', #{param.keywords}, '%')
OR a.public_no LIKE CONCAT('%', #{param.keywords}, '%')
OR a.register_no LIKE CONCAT('%', #{param.keywords}, '%')
)
</if>
</where>

View File

@@ -71,7 +71,8 @@
</if>
<if test="param.keywords != null">
AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%')
OR b.name LIKE CONCAT('%', #{param.keywords}, '%')
OR b.name LIKE CONCAT('%', #{param.keywords}, '%')
OR a.name LIKE CONCAT('%', #{param.keywords}, '%')
)
</if>
</where>

View File

@@ -91,7 +91,7 @@
AND (a.name LIKE CONCAT('%', #{param.keywords}, '%')
OR a.procurement_name LIKE CONCAT('%', #{param.keywords}, '%')
OR a.winning_name LIKE CONCAT('%', #{param.keywords}, '%')
OR b.name LIKE CONCAT('%', #{param.keywords}, '%')
OR b.name LIKE CONCAT('%', #{param.keywords}, '%')
)
</if>
</where>

View File

@@ -22,7 +22,7 @@
AND a.plaintiff_appellant LIKE CONCAT('%', #{param.plaintiffAppellant}, '%')
</if>
<if test="param.appellee != null">
AND a.appellee LIKE CONCAT('%', #{param.defendant appellee}, '%')
AND a.appellee LIKE CONCAT('%', #{param.appellee}, '%')
</if>
<if test="param.otherPartiesThirdParty != null">
AND a.other_parties_third_party LIKE CONCAT('%', #{param.otherPartiesThirdParty}, '%')

View File

@@ -1,8 +1,6 @@
package com.gxwebsoft.credit.param;
import cn.afterturn.easypoi.excel.annotation.Excel;
import com.baomidou.mybatisplus.annotation.TableField;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
@@ -14,6 +12,8 @@ import java.io.Serializable;
public class CreditBreachOfTrustImportParam implements Serializable {
private static final long serialVersionUID = 1L;
@Excel(name = "数据类型")
private String dataType;
@Excel(name = "案号")
private String caseNumber;
@@ -21,18 +21,39 @@ public class CreditBreachOfTrustImportParam implements Serializable {
@Excel(name = "失信被执行人")
private String plaintiffAppellant;
@Excel(name = "原告/上诉人")
private String plaintiffAppellant2;
@Excel(name = "疑似申请执行人")
private String appellee;
@Excel(name = "被告/被上诉人")
private String appellee2;
@Excel(name = "其他当事人/第三人")
private String otherPartiesThirdParty;
@Excel(name = "数据状态")
private String dataStatus;
@Excel(name = "涉案金额(元)")
private String involvedAmount;
@Excel(name = "涉案金额")
private String involvedAmount2;
@Excel(name = "执行法院")
private String courtName;
@Excel(name = "法院")
private String courtName2;
@Excel(name = "立案日期")
private String occurrenceTime;
@Excel(name = "发生时间")
private String occurrenceTime2;
@Excel(name = "发布日期")
private String releaseDate;

View File

@@ -12,6 +12,18 @@ import java.io.Serializable;
public class CreditCaseFilingImportParam implements Serializable {
private static final long serialVersionUID = 1L;
@Excel(name = "数据类型")
private String dataType;
@Excel(name = "原告/上诉人")
private String plaintiffAppellant;
@Excel(name = "被告/被上诉人")
private String appellee;
@Excel(name = "数据状态")
private String dataStatus;
@Excel(name = "案号")
private String caseNumber;
@@ -21,12 +33,27 @@ public class CreditCaseFilingImportParam implements Serializable {
@Excel(name = "当事人")
private String otherPartiesThirdParty;
@Excel(name = "其他当事人/第三人")
private String otherPartiesThirdParty2;
@Excel(name = "法院")
private String courtName;
@Excel(name = "执行法院")
private String courtName2;
@Excel(name = "立案日期")
private String occurrenceTime;
@Excel(name = "发生时间")
private String occurrenceTime2;
@Excel(name = "涉案金额")
private String involvedAmount;
@Excel(name = "涉案金额(元)")
private String involvedAmount2;
@Excel(name = "备注")
private String comments;

View File

@@ -1,7 +1,6 @@
package com.gxwebsoft.credit.param;
import cn.afterturn.easypoi.excel.annotation.Excel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
@@ -22,15 +21,42 @@ public class CreditCourtAnnouncementImportParam implements Serializable {
@Excel(name = "当事人")
private String otherPartiesThirdParty;
@Excel(name = "其他当事人/第三人")
private String otherPartiesThirdParty2;
@Excel(name = "公告类型")
private String dataType;
@Excel(name = "数据类型")
private String dataType2;
@Excel(name = "公告人")
private String plaintiffAppellant;
@Excel(name = "原告/上诉人")
private String plaintiffAppellant2;
@Excel(name = "被告/被上诉人")
private String appellee;
@Excel(name = "涉案金额")
private String involvedAmount;
@Excel(name = "涉案金额(元)")
private String involvedAmount2;
@Excel(name = "法院")
private String courtName;
@Excel(name = "数据状态")
private String dataStatus;
@Excel(name = "刊登日期")
private String occurrenceTime;
@Excel(name = "发生时间")
private String occurrenceTime2;
@Excel(name = "备注")
private String comments;

View File

@@ -12,6 +12,9 @@ import java.io.Serializable;
public class CreditCourtSessionImportParam implements Serializable {
private static final long serialVersionUID = 1L;
@Excel(name = "数据类型")
private String dataType;
@Excel(name = "案号")
private String caseNumber;
@@ -21,12 +24,33 @@ public class CreditCourtSessionImportParam implements Serializable {
@Excel(name = "当事人")
private String otherPartiesThirdParty;
@Excel(name = "其他当事人/第三人")
private String otherPartiesThirdParty2;
@Excel(name = "原告/上诉人")
private String plaintiffAppellant;
@Excel(name = "被告/被上诉人")
private String appellee;
@Excel(name = "涉案金额")
private String involvedAmount;
@Excel(name = "涉案金额(元)")
private String involvedAmount2;
@Excel(name = "数据状态")
private String dataStatus;
@Excel(name = "法院")
private String courtName;
@Excel(name = "开庭时间")
private String occurrenceTime;
@Excel(name = "发生时间")
private String occurrenceTime2;
@Excel(name = "备注")
private String comments;

View File

@@ -12,6 +12,24 @@ import java.io.Serializable;
public class CreditDeliveryNoticeImportParam implements Serializable {
private static final long serialVersionUID = 1L;
@Excel(name = "数据类型")
private String dataType;
@Excel(name = "原告/上诉人")
private String plaintiffAppellant;
@Excel(name = "被告/被上诉人")
private String appellee;
@Excel(name = "数据状态")
private String dataStatus;
@Excel(name = "涉案金额")
private String involvedAmount;
@Excel(name = "涉案金额(元)")
private String involvedAmount2;
@Excel(name = "案号")
private String caseNumber;
@@ -21,12 +39,21 @@ public class CreditDeliveryNoticeImportParam implements Serializable {
@Excel(name = "当事人")
private String otherPartiesThirdParty;
@Excel(name = "其他当事人/第三人")
private String otherPartiesThirdParty2;
@Excel(name = "法院")
private String courtName;
@Excel(name = "执行法院")
private String courtName2;
@Excel(name = "发布日期")
private String occurrenceTime;
@Excel(name = "发生时间")
private String occurrenceTime2;
@Excel(name = "备注")
private String comments;

View File

@@ -1,8 +1,6 @@
package com.gxwebsoft.credit.param;
import cn.afterturn.easypoi.excel.annotation.Excel;
import com.baomidou.mybatisplus.annotation.TableField;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
@@ -20,21 +18,45 @@ public class CreditFinalVersionImportParam implements Serializable {
@Excel(name = "被执行人")
private String appellee;
@Excel(name = "被告/被上诉人")
private String appellee2;
@Excel(name = "疑似申请执行人")
private String plaintiffAppellant;
@Excel(name = "原告/上诉人")
private String plaintiffAppellant2;
@Excel(name = "其他当事人/第三人")
private String otherPartiesThirdParty;
@Excel(name = "当事人")
private String otherPartiesThirdParty2;
@Excel(name = "数据状态")
private String dataStatus;
@Excel(name = "未履行金额(元)")
private String unfulfilledAmount;
@Excel(name = "执行标的(元)")
private String involvedAmount;
@Excel(name = "涉案金额")
private String involvedAmount2;
@Excel(name = "执行法院")
private String courtName;
@Excel(name = "法院")
private String courtName2;
@Excel(name = "立案日期")
private String occurrenceTime;
@Excel(name = "发生时间")
private String occurrenceTime2;
@Excel(name = "终本日期")
private String finalDate;

View File

@@ -16,12 +16,24 @@ public class CreditGqdjImportParam implements Serializable {
@Excel(name = "执行通知文书号")
private String caseNumber;
// Some upstream sources use "案号" instead of "执行通知文书号".
@Excel(name = "案号")
private String caseNumber2;
@Excel(name = "被执行人")
private String appellee;
// Some upstream sources use "被执行人名称" as the executed person column.
@Excel(name = "被执行人名称")
private String appellee2;
@Excel(name = "冻结股权标的企业")
private String plaintiffAppellant;
// Some upstream sources use "冻结股权标的企业名称" as the target company column.
@Excel(name = "冻结股权标的企业名称")
private String plaintiffAppellant2;
@Excel(name = "被执行人持有股权、其他投资权益数额")
private String involvedAmount;
@@ -34,12 +46,22 @@ public class CreditGqdjImportParam implements Serializable {
@Excel(name = "状态")
private String dataStatus;
// Some upstream sources use "数据状态" as the status column.
@Excel(name = "数据状态")
private String dataStatus2;
@Excel(name = "冻结日期自")
private String freezeDateStart;
@Excel(name = "冻结日期至")
private String freezeDateEnd;
@Excel(name = "冻结开始日期")
private String freezeDateStart2;
@Excel(name = "冻结结束日期")
private String freezeDateEnd2;
@Excel(name = "公示日期")
private String publicDate;
}

View File

@@ -16,6 +16,9 @@ public class CreditJudicialDocumentImportParam implements Serializable {
@Excel(name = "文书标题")
private String title;
@Excel(name = "文书类型")
private String type;
@Excel(name = "案号")
private String caseNumber;
@@ -28,6 +31,9 @@ public class CreditJudicialDocumentImportParam implements Serializable {
@Excel(name = "案件金额(元)")
private String involvedAmount;
@Excel(name = "涉案金额")
private String involvedAmount2;
@Excel(name = "裁判结果")
private String defendantAppellee;
@@ -40,6 +46,9 @@ public class CreditJudicialDocumentImportParam implements Serializable {
@Excel(name = "法院")
private String courtName;
@Excel(name = "数据状态")
private String dataStatus;
@Excel(name = "备注")
private String comments;

View File

@@ -1,6 +1,7 @@
package com.gxwebsoft.credit.param;
import cn.afterturn.easypoi.excel.annotation.Excel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
@@ -30,4 +31,21 @@ public class CreditMediationImportParam implements Serializable {
@Excel(name = "备注")
private String comments;
@Excel(name = "原告/上诉人")
private String plaintiffAppellant;
@Excel(name = "被告/被上诉人")
private String appellee;
@Excel(name = "数据状态")
private String dataStatus;
@Excel(name = "涉案金额")
private String involvedAmount;
@Excel(name = "发生时间")
private String occurrenceTime2;
@Excel(name = "其他当事人/第三人")
private String otherPartiesThirdParty2;
}

View File

@@ -31,9 +31,15 @@ public class CreditXgxfImportParam implements Serializable {
@Excel(name = "涉案金额(元)")
private String involvedAmount;
@Excel(name = "涉案金额")
private String involvedAmount2;
@Excel(name = "立案日期")
private String occurrenceTime;
@Excel(name = "发生时间")
private String occurrenceTime2;
@Excel(name = "执行法院")
private String courtName;
@@ -43,4 +49,19 @@ public class CreditXgxfImportParam implements Serializable {
@Excel(name = "备注")
private String comments;
@Excel(name = "原告/上诉人")
private String plaintiffUser;
@Excel(name = "被告/被上诉人")
private String defendantUser;
@Excel(name = "其他当事人/第三人")
private String otherPartiesThirdParty;
@Excel(name = "数据状态")
private String dataStatus;
@Excel(name = "法院")
private String courtName2;
}

View File

@@ -0,0 +1,128 @@
package com.gxwebsoft.glt.controller;
import com.gxwebsoft.common.core.web.BaseController;
import com.gxwebsoft.common.system.entity.User;
import com.gxwebsoft.glt.service.GltTicketTemplateService;
import com.gxwebsoft.glt.entity.GltTicketTemplate;
import com.gxwebsoft.glt.param.GltTicketTemplateParam;
import com.gxwebsoft.common.core.web.ApiResult;
import com.gxwebsoft.common.core.web.PageResult;
import com.gxwebsoft.common.core.web.PageParam;
import com.gxwebsoft.common.core.web.BatchParam;
import com.gxwebsoft.common.core.annotation.OperationLog;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
/**
* 水票控制器
*
* @author 科技小王子
* @since 2026-02-03 18:55:55
*/
@Tag(name = "水票管理")
@RestController
@RequestMapping("/api/glt/glt-ticket-template")
public class GltTicketTemplateController extends BaseController {
@Resource
private GltTicketTemplateService gltTicketTemplateService;
@Operation(summary = "分页查询水票")
@GetMapping("/page")
public ApiResult<PageResult<GltTicketTemplate>> page(GltTicketTemplateParam param) {
// 使用关联查询
return success(gltTicketTemplateService.pageRel(param));
}
@PreAuthorize("hasAuthority('glt:gltTicketTemplate:list')")
@Operation(summary = "查询全部水票")
@GetMapping()
public ApiResult<List<GltTicketTemplate>> list(GltTicketTemplateParam param) {
// 使用关联查询
return success(gltTicketTemplateService.listRel(param));
}
@PreAuthorize("hasAuthority('glt:gltTicketTemplate:list')")
@Operation(summary = "根据id查询水票")
@GetMapping("/{id}")
public ApiResult<GltTicketTemplate> get(@PathVariable("id") Integer id) {
// 使用关联查询
return success(gltTicketTemplateService.getByIdRel(id));
}
@PreAuthorize("hasAuthority('glt:gltTicketTemplate:save')")
@OperationLog
@Operation(summary = "添加水票")
@PostMapping()
public ApiResult<?> save(@RequestBody GltTicketTemplate gltTicketTemplate) {
// 记录当前登录用户id
User loginUser = getLoginUser();
if (loginUser != null) {
gltTicketTemplate.setUserId(loginUser.getUserId());
}
if (gltTicketTemplateService.save(gltTicketTemplate)) {
return success("添加成功");
}
return fail("添加失败");
}
@PreAuthorize("hasAuthority('glt:gltTicketTemplate:update')")
@OperationLog
@Operation(summary = "修改水票")
@PutMapping()
public ApiResult<?> update(@RequestBody GltTicketTemplate gltTicketTemplate) {
if (gltTicketTemplateService.updateById(gltTicketTemplate)) {
return success("修改成功");
}
return fail("修改失败");
}
@PreAuthorize("hasAuthority('glt:gltTicketTemplate:remove')")
@OperationLog
@Operation(summary = "删除水票")
@DeleteMapping("/{id}")
public ApiResult<?> remove(@PathVariable("id") Integer id) {
if (gltTicketTemplateService.removeById(id)) {
return success("删除成功");
}
return fail("删除失败");
}
@PreAuthorize("hasAuthority('glt:gltTicketTemplate:save')")
@OperationLog
@Operation(summary = "批量添加水票")
@PostMapping("/batch")
public ApiResult<?> saveBatch(@RequestBody List<GltTicketTemplate> list) {
if (gltTicketTemplateService.saveBatch(list)) {
return success("添加成功");
}
return fail("添加失败");
}
@PreAuthorize("hasAuthority('glt:gltTicketTemplate:update')")
@OperationLog
@Operation(summary = "批量修改水票")
@PutMapping("/batch")
public ApiResult<?> removeBatch(@RequestBody BatchParam<GltTicketTemplate> batchParam) {
if (batchParam.update(gltTicketTemplateService, "id")) {
return success("修改成功");
}
return fail("修改失败");
}
@PreAuthorize("hasAuthority('glt:gltTicketTemplate:remove')")
@OperationLog
@Operation(summary = "批量删除水票")
@DeleteMapping("/batch")
public ApiResult<?> removeBatch(@RequestBody List<Integer> ids) {
if (gltTicketTemplateService.removeByIds(ids)) {
return success("删除成功");
}
return fail("删除失败");
}
}

View File

@@ -0,0 +1,137 @@
package com.gxwebsoft.glt.controller;
import com.gxwebsoft.common.core.web.BaseController;
import com.gxwebsoft.glt.service.GltUserTicketService;
import com.gxwebsoft.glt.entity.GltUserTicket;
import com.gxwebsoft.glt.param.GltUserTicketParam;
import com.gxwebsoft.common.core.web.ApiResult;
import com.gxwebsoft.common.core.web.PageResult;
import com.gxwebsoft.common.core.web.BatchParam;
import com.gxwebsoft.common.core.annotation.OperationLog;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 我的水票控制器
*
* @author 科技小王子
* @since 2026-02-03 18:55:55
*/
@Tag(name = "我的水票管理")
@RestController
@RequestMapping("/api/glt/glt-user-ticket")
public class GltUserTicketController extends BaseController {
@Resource
private GltUserTicketService gltUserTicketService;
@Operation(summary = "分页查询我的水票")
@GetMapping("/page")
public ApiResult<PageResult<GltUserTicket>> page(GltUserTicketParam param) {
// 使用关联查询
return success(gltUserTicketService.pageRel(param));
}
@Operation(summary = "我的水票总数")
@GetMapping("/my-total")
public ApiResult<?> myTotal() {
Integer userId = getLoginUserId();
if (userId == null) {
return fail("未登录");
}
Integer totalQty = gltUserTicketService.sumTotalQtyByUserId(userId);
Map<String, Object> data = new HashMap<>();
data.put("userId", userId);
data.put("totalQty", totalQty);
return success(data);
}
@PreAuthorize("hasAuthority('glt:gltUserTicket:list')")
@Operation(summary = "查询全部我的水票")
@GetMapping()
public ApiResult<List<GltUserTicket>> list(GltUserTicketParam param) {
// 使用关联查询
return success(gltUserTicketService.listRel(param));
}
@PreAuthorize("hasAuthority('glt:gltUserTicket:list')")
@Operation(summary = "根据id查询我的水票")
@GetMapping("/{id}")
public ApiResult<GltUserTicket> get(@PathVariable("id") Integer id) {
// 使用关联查询
return success(gltUserTicketService.getByIdRel(id));
}
@PreAuthorize("hasAuthority('glt:gltUserTicket:save')")
@OperationLog
@Operation(summary = "添加我的水票")
@PostMapping()
public ApiResult<?> save(@RequestBody GltUserTicket gltUserTicket) {
if (gltUserTicketService.save(gltUserTicket)) {
return success("添加成功");
}
return fail("添加失败");
}
@PreAuthorize("hasAuthority('glt:gltUserTicket:update')")
@OperationLog
@Operation(summary = "修改我的水票")
@PutMapping()
public ApiResult<?> update(@RequestBody GltUserTicket gltUserTicket) {
if (gltUserTicketService.updateById(gltUserTicket)) {
return success("修改成功");
}
return fail("修改失败");
}
@PreAuthorize("hasAuthority('glt:gltUserTicket:remove')")
@OperationLog
@Operation(summary = "删除我的水票")
@DeleteMapping("/{id}")
public ApiResult<?> remove(@PathVariable("id") Integer id) {
if (gltUserTicketService.removeById(id)) {
return success("删除成功");
}
return fail("删除失败");
}
@PreAuthorize("hasAuthority('glt:gltUserTicket:save')")
@OperationLog
@Operation(summary = "批量添加我的水票")
@PostMapping("/batch")
public ApiResult<?> saveBatch(@RequestBody List<GltUserTicket> list) {
if (gltUserTicketService.saveBatch(list)) {
return success("添加成功");
}
return fail("添加失败");
}
@PreAuthorize("hasAuthority('glt:gltUserTicket:update')")
@OperationLog
@Operation(summary = "批量修改我的水票")
@PutMapping("/batch")
public ApiResult<?> removeBatch(@RequestBody BatchParam<GltUserTicket> batchParam) {
if (batchParam.update(gltUserTicketService, "id")) {
return success("修改成功");
}
return fail("修改失败");
}
@PreAuthorize("hasAuthority('glt:gltUserTicket:remove')")
@OperationLog
@Operation(summary = "批量删除我的水票")
@DeleteMapping("/batch")
public ApiResult<?> removeBatch(@RequestBody List<Integer> ids) {
if (gltUserTicketService.removeByIds(ids)) {
return success("删除成功");
}
return fail("删除失败");
}
}

View File

@@ -0,0 +1,129 @@
package com.gxwebsoft.glt.controller;
import com.gxwebsoft.common.core.web.BaseController;
import com.gxwebsoft.common.system.entity.User;
import com.gxwebsoft.glt.service.GltUserTicketLogService;
import com.gxwebsoft.glt.entity.GltUserTicketLog;
import com.gxwebsoft.glt.param.GltUserTicketLogParam;
import com.gxwebsoft.common.core.web.ApiResult;
import com.gxwebsoft.common.core.web.PageResult;
import com.gxwebsoft.common.core.web.PageParam;
import com.gxwebsoft.common.core.web.BatchParam;
import com.gxwebsoft.common.core.annotation.OperationLog;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
/**
* 消费日志控制器
*
* @author 科技小王子
* @since 2026-02-03 18:55:55
*/
@Tag(name = "消费日志管理")
@RestController
@RequestMapping("/api/glt/glt-user-ticket-log")
public class GltUserTicketLogController extends BaseController {
@Resource
private GltUserTicketLogService gltUserTicketLogService;
@PreAuthorize("hasAuthority('glt:gltUserTicketLog:list')")
@Operation(summary = "分页查询消费日志")
@GetMapping("/page")
public ApiResult<PageResult<GltUserTicketLog>> page(GltUserTicketLogParam param) {
// 使用关联查询
return success(gltUserTicketLogService.pageRel(param));
}
@PreAuthorize("hasAuthority('glt:gltUserTicketLog:list')")
@Operation(summary = "查询全部消费日志")
@GetMapping()
public ApiResult<List<GltUserTicketLog>> list(GltUserTicketLogParam param) {
// 使用关联查询
return success(gltUserTicketLogService.listRel(param));
}
@PreAuthorize("hasAuthority('glt:gltUserTicketLog:list')")
@Operation(summary = "根据id查询消费日志")
@GetMapping("/{id}")
public ApiResult<GltUserTicketLog> get(@PathVariable("id") Integer id) {
// 使用关联查询
return success(gltUserTicketLogService.getByIdRel(id));
}
@PreAuthorize("hasAuthority('glt:gltUserTicketLog:save')")
@OperationLog
@Operation(summary = "添加消费日志")
@PostMapping()
public ApiResult<?> save(@RequestBody GltUserTicketLog gltUserTicketLog) {
// 记录当前登录用户id
User loginUser = getLoginUser();
if (loginUser != null) {
gltUserTicketLog.setUserId(loginUser.getUserId());
}
if (gltUserTicketLogService.save(gltUserTicketLog)) {
return success("添加成功");
}
return fail("添加失败");
}
@PreAuthorize("hasAuthority('glt:gltUserTicketLog:update')")
@OperationLog
@Operation(summary = "修改消费日志")
@PutMapping()
public ApiResult<?> update(@RequestBody GltUserTicketLog gltUserTicketLog) {
if (gltUserTicketLogService.updateById(gltUserTicketLog)) {
return success("修改成功");
}
return fail("修改失败");
}
@PreAuthorize("hasAuthority('glt:gltUserTicketLog:remove')")
@OperationLog
@Operation(summary = "删除消费日志")
@DeleteMapping("/{id}")
public ApiResult<?> remove(@PathVariable("id") Integer id) {
if (gltUserTicketLogService.removeById(id)) {
return success("删除成功");
}
return fail("删除失败");
}
@PreAuthorize("hasAuthority('glt:gltUserTicketLog:save')")
@OperationLog
@Operation(summary = "批量添加消费日志")
@PostMapping("/batch")
public ApiResult<?> saveBatch(@RequestBody List<GltUserTicketLog> list) {
if (gltUserTicketLogService.saveBatch(list)) {
return success("添加成功");
}
return fail("添加失败");
}
@PreAuthorize("hasAuthority('glt:gltUserTicketLog:update')")
@OperationLog
@Operation(summary = "批量修改消费日志")
@PutMapping("/batch")
public ApiResult<?> removeBatch(@RequestBody BatchParam<GltUserTicketLog> batchParam) {
if (batchParam.update(gltUserTicketLogService, "id")) {
return success("修改成功");
}
return fail("修改失败");
}
@PreAuthorize("hasAuthority('glt:gltUserTicketLog:remove')")
@OperationLog
@Operation(summary = "批量删除消费日志")
@DeleteMapping("/batch")
public ApiResult<?> removeBatch(@RequestBody List<Integer> ids) {
if (gltUserTicketLogService.removeByIds(ids)) {
return success("删除成功");
}
return fail("删除失败");
}
}

View File

@@ -0,0 +1,129 @@
package com.gxwebsoft.glt.controller;
import com.gxwebsoft.common.core.web.BaseController;
import com.gxwebsoft.common.system.entity.User;
import com.gxwebsoft.glt.service.GltUserTicketReleaseService;
import com.gxwebsoft.glt.entity.GltUserTicketRelease;
import com.gxwebsoft.glt.param.GltUserTicketReleaseParam;
import com.gxwebsoft.common.core.web.ApiResult;
import com.gxwebsoft.common.core.web.PageResult;
import com.gxwebsoft.common.core.web.PageParam;
import com.gxwebsoft.common.core.web.BatchParam;
import com.gxwebsoft.common.core.annotation.OperationLog;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
/**
* 水票释放控制器
*
* @author 科技小王子
* @since 2026-02-03 18:55:55
*/
@Tag(name = "水票释放管理")
@RestController
@RequestMapping("/api/glt/glt-user-ticket-release")
public class GltUserTicketReleaseController extends BaseController {
@Resource
private GltUserTicketReleaseService gltUserTicketReleaseService;
@PreAuthorize("hasAuthority('glt:gltUserTicketRelease:list')")
@Operation(summary = "分页查询水票释放")
@GetMapping("/page")
public ApiResult<PageResult<GltUserTicketRelease>> page(GltUserTicketReleaseParam param) {
// 使用关联查询
return success(gltUserTicketReleaseService.pageRel(param));
}
@PreAuthorize("hasAuthority('glt:gltUserTicketRelease:list')")
@Operation(summary = "查询全部水票释放")
@GetMapping()
public ApiResult<List<GltUserTicketRelease>> list(GltUserTicketReleaseParam param) {
// 使用关联查询
return success(gltUserTicketReleaseService.listRel(param));
}
@PreAuthorize("hasAuthority('glt:gltUserTicketRelease:list')")
@Operation(summary = "根据id查询水票释放")
@GetMapping("/{id}")
public ApiResult<GltUserTicketRelease> get(@PathVariable("id") Integer id) {
// 使用关联查询
return success(gltUserTicketReleaseService.getByIdRel(id));
}
@PreAuthorize("hasAuthority('glt:gltUserTicketRelease:save')")
@OperationLog
@Operation(summary = "添加水票释放")
@PostMapping()
public ApiResult<?> save(@RequestBody GltUserTicketRelease gltUserTicketRelease) {
// 记录当前登录用户id
User loginUser = getLoginUser();
if (loginUser != null) {
gltUserTicketRelease.setUserId(loginUser.getUserId());
}
if (gltUserTicketReleaseService.save(gltUserTicketRelease)) {
return success("添加成功");
}
return fail("添加失败");
}
@PreAuthorize("hasAuthority('glt:gltUserTicketRelease:update')")
@OperationLog
@Operation(summary = "修改水票释放")
@PutMapping()
public ApiResult<?> update(@RequestBody GltUserTicketRelease gltUserTicketRelease) {
if (gltUserTicketReleaseService.updateById(gltUserTicketRelease)) {
return success("修改成功");
}
return fail("修改失败");
}
@PreAuthorize("hasAuthority('glt:gltUserTicketRelease:remove')")
@OperationLog
@Operation(summary = "删除水票释放")
@DeleteMapping("/{id}")
public ApiResult<?> remove(@PathVariable("id") Integer id) {
if (gltUserTicketReleaseService.removeById(id)) {
return success("删除成功");
}
return fail("删除失败");
}
@PreAuthorize("hasAuthority('glt:gltUserTicketRelease:save')")
@OperationLog
@Operation(summary = "批量添加水票释放")
@PostMapping("/batch")
public ApiResult<?> saveBatch(@RequestBody List<GltUserTicketRelease> list) {
if (gltUserTicketReleaseService.saveBatch(list)) {
return success("添加成功");
}
return fail("添加失败");
}
@PreAuthorize("hasAuthority('glt:gltUserTicketRelease:update')")
@OperationLog
@Operation(summary = "批量修改水票释放")
@PutMapping("/batch")
public ApiResult<?> removeBatch(@RequestBody BatchParam<GltUserTicketRelease> batchParam) {
if (batchParam.update(gltUserTicketReleaseService, "id")) {
return success("修改成功");
}
return fail("修改失败");
}
@PreAuthorize("hasAuthority('glt:gltUserTicketRelease:remove')")
@OperationLog
@Operation(summary = "批量删除水票释放")
@DeleteMapping("/batch")
public ApiResult<?> removeBatch(@RequestBody List<Integer> ids) {
if (gltUserTicketReleaseService.removeByIds(ids)) {
return success("删除成功");
}
return fail("删除失败");
}
}

View File

@@ -0,0 +1,88 @@
package com.gxwebsoft.glt.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.TableLogic;
import java.io.Serializable;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import com.fasterxml.jackson.annotation.JsonFormat;
/**
* 水票
*
* @author 科技小王子
* @since 2026-02-03 18:55:54
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Schema(name = "GltTicketTemplate对象", description = "水票")
public class GltTicketTemplate implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
@Schema(description = "关联商品ID")
private Integer goodsId;
@Schema(description = "名称")
private String name;
@Schema(description = "启用")
private Boolean enabled;
@Schema(description = "单位名称")
private String unitName;
@Schema(description = "最小购买数量")
private Integer minBuyQty;
@Schema(description = "起始发送数量")
private Integer startSendQty;
@Schema(description = "买赠买1送4 => gift_multiplier=4")
private Integer giftMultiplier;
@Schema(description = "是否把购买量也计入套票总量(默认仅计入赠送量)")
private Boolean includeBuyQty;
@Schema(description = "每期释放数量默认每月释放10")
private Integer monthlyReleaseQty;
@Schema(description = "总共释放多少期(若配置>0则按期数平均分摊")
private Integer releasePeriods;
@Schema(description = "首期释放时机0=支付成功当刻1=下个月同日")
private Integer firstReleaseMode;
@Schema(description = "用户ID")
private Integer userId;
@Schema(description = "排序(数字越小越靠前)")
private Integer sortNumber;
@Schema(description = "备注")
private String comments;
@Schema(description = "状态, 0正常, 1冻结")
private Integer status;
@Schema(description = "是否删除, 0否, 1是")
@TableLogic
private Integer deleted;
@Schema(description = "租户id")
private Integer tenantId;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
@Schema(description = "修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,112 @@
package com.gxwebsoft.glt.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.TableLogic;
import java.io.Serializable;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import com.fasterxml.jackson.annotation.JsonFormat;
/**
* 我的水票
*
* @author 科技小王子
* @since 2026-02-03 18:55:55
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Schema(name = "GltUserTicket对象", description = "我的水票")
public class GltUserTicket implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
@Schema(description = "模板ID")
private Integer templateId;
@Schema(description = "模板名称")
@TableField(exist = false)
private String templateName;
@Schema(description = "商品ID")
private Integer goodsId;
@Schema(description = "购买价格")
@TableField(exist = false)
private BigDecimal payPrice;
@Schema(description = "商品名称")
@TableField(exist = false)
private String goodsName;
@Schema(description = "订单ID")
private Integer orderId;
@Schema(description = "订单编号")
private String orderNo;
@Schema(description = "订单商品ID")
private Integer orderGoodsId;
@Schema(description = "总数量")
private Integer totalQty;
@Schema(description = "可用数量")
private Integer availableQty;
@Schema(description = "冻结数量")
private Integer frozenQty;
@Schema(description = "已使用数量")
private Integer usedQty;
@Schema(description = "已释放数量")
private Integer releasedQty;
@Schema(description = "用户ID")
private Integer userId;
@Schema(description = "用户昵称")
@TableField(exist = false)
private String nickname;
@Schema(description = "用户头像")
@TableField(exist = false)
private String avatar;
@Schema(description = "用户手机号")
@TableField(exist = false)
private String phone;
@Schema(description = "排序(数字越小越靠前)")
private Integer sortNumber;
@Schema(description = "备注")
private String comments;
@Schema(description = "状态, 0正常, 1冻结")
private Integer status;
@Schema(description = "是否删除, 0否, 1是")
@TableLogic
private Integer deleted;
@Schema(description = "租户id")
private Integer tenantId;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
@Schema(description = "修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,98 @@
package com.gxwebsoft.glt.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.TableLogic;
import java.io.Serializable;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import com.fasterxml.jackson.annotation.JsonFormat;
/**
* 消费日志
*
* @author 科技小王子
* @since 2026-02-03 18:55:55
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Schema(name = "GltUserTicketLog对象", description = "消费日志")
public class GltUserTicketLog implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
@Schema(description = "用户水票ID")
private Integer userTicketId;
@Schema(description = "变更类型")
private Integer changeType;
@Schema(description = "可更改")
private Integer changeAvailable;
@Schema(description = "更改冻结状态")
private Integer changeFrozen;
@Schema(description = "已使用更改")
private Integer changeUsed;
@Schema(description = "可用后")
private Integer availableAfter;
@Schema(description = "冻结后")
private Integer frozenAfter;
@Schema(description = "使用后")
private Integer usedAfter;
@Schema(description = "订单ID")
private Integer orderId;
@Schema(description = "订单编号")
private String orderNo;
@Schema(description = "用户ID")
private Integer userId;
@Schema(description = "用户昵称")
@TableField(exist = false)
private String nickname;
@Schema(description = "用户头像")
@TableField(exist = false)
private String avatar;
@Schema(description = "用户手机号")
@TableField(exist = false)
private String phone;
@Schema(description = "排序(数字越小越靠前)")
private Integer sortNumber;
@Schema(description = "备注")
private String comments;
@Schema(description = "状态, 0正常, 1冻结")
private Integer status;
@Schema(description = "是否删除, 0否, 1是")
@TableLogic
private Integer deleted;
@Schema(description = "租户id")
private Integer tenantId;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
@Schema(description = "修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,75 @@
package com.gxwebsoft.glt.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.TableLogic;
import java.io.Serializable;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import com.fasterxml.jackson.annotation.JsonFormat;
/**
* 水票释放
*
* @author 科技小王子
* @since 2026-02-03 18:55:55
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Schema(name = "GltUserTicketRelease对象", description = "水票释放")
public class GltUserTicketRelease implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@Schema(description = "水票ID")
private Long userTicketId;
@Schema(description = "用户ID")
private Integer userId;
@Schema(description = "用户昵称")
@TableField(exist = false)
private String nickname;
@Schema(description = "用户头像")
@TableField(exist = false)
private String avatar;
@Schema(description = "用户手机号")
@TableField(exist = false)
private String phone;
@Schema(description = "周期编号")
private Integer periodNo;
@Schema(description = "释放数量")
private Integer releaseQty;
@Schema(description = "释放时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime releaseTime;
@Schema(description = "状态")
private Integer status;
@Schema(description = "是否删除, 0否, 1是")
@TableLogic
private Integer deleted;
@Schema(description = "租户id")
private Integer tenantId;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
@Schema(description = "修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,37 @@
package com.gxwebsoft.glt.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.gxwebsoft.glt.entity.GltTicketTemplate;
import com.gxwebsoft.glt.param.GltTicketTemplateParam;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 水票Mapper
*
* @author 科技小王子
* @since 2026-02-03 18:55:54
*/
public interface GltTicketTemplateMapper extends BaseMapper<GltTicketTemplate> {
/**
* 分页查询
*
* @param page 分页对象
* @param param 查询参数
* @return List<GltTicketTemplate>
*/
List<GltTicketTemplate> selectPageRel(@Param("page") IPage<GltTicketTemplate> page,
@Param("param") GltTicketTemplateParam param);
/**
* 查询全部
*
* @param param 查询参数
* @return List<User>
*/
List<GltTicketTemplate> selectListRel(@Param("param") GltTicketTemplateParam param);
}

View File

@@ -0,0 +1,37 @@
package com.gxwebsoft.glt.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.gxwebsoft.glt.entity.GltUserTicketLog;
import com.gxwebsoft.glt.param.GltUserTicketLogParam;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 消费日志Mapper
*
* @author 科技小王子
* @since 2026-02-03 18:55:55
*/
public interface GltUserTicketLogMapper extends BaseMapper<GltUserTicketLog> {
/**
* 分页查询
*
* @param page 分页对象
* @param param 查询参数
* @return List<GltUserTicketLog>
*/
List<GltUserTicketLog> selectPageRel(@Param("page") IPage<GltUserTicketLog> page,
@Param("param") GltUserTicketLogParam param);
/**
* 查询全部
*
* @param param 查询参数
* @return List<User>
*/
List<GltUserTicketLog> selectListRel(@Param("param") GltUserTicketLogParam param);
}

View File

@@ -0,0 +1,45 @@
package com.gxwebsoft.glt.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.gxwebsoft.glt.entity.GltUserTicket;
import com.gxwebsoft.glt.param.GltUserTicketParam;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 我的水票Mapper
*
* @author 科技小王子
* @since 2026-02-03 18:55:55
*/
public interface GltUserTicketMapper extends BaseMapper<GltUserTicket> {
/**
* 分页查询
*
* @param page 分页对象
* @param param 查询参数
* @return List<GltUserTicket>
*/
List<GltUserTicket> selectPageRel(@Param("page") IPage<GltUserTicket> page,
@Param("param") GltUserTicketParam param);
/**
* 查询全部
*
* @param param 查询参数
* @return List<User>
*/
List<GltUserTicket> selectListRel(@Param("param") GltUserTicketParam param);
/**
* 统计用户水票总数量sum(total_qty)
*
* @param userId 用户ID
* @return 总数量
*/
Integer sumTotalQtyByUserId(@Param("userId") Integer userId);
}

View File

@@ -0,0 +1,37 @@
package com.gxwebsoft.glt.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.gxwebsoft.glt.entity.GltUserTicketRelease;
import com.gxwebsoft.glt.param.GltUserTicketReleaseParam;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 水票释放Mapper
*
* @author 科技小王子
* @since 2026-02-03 18:55:55
*/
public interface GltUserTicketReleaseMapper extends BaseMapper<GltUserTicketRelease> {
/**
* 分页查询
*
* @param page 分页对象
* @param param 查询参数
* @return List<GltUserTicketRelease>
*/
List<GltUserTicketRelease> selectPageRel(@Param("page") IPage<GltUserTicketRelease> page,
@Param("param") GltUserTicketReleaseParam param);
/**
* 查询全部
*
* @param param 查询参数
* @return List<User>
*/
List<GltUserTicketRelease> selectListRel(@Param("param") GltUserTicketReleaseParam param);
}

View File

@@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.gxwebsoft.glt.mapper.GltTicketTemplateMapper">
<!-- 关联查询sql -->
<sql id="selectSql">
SELECT a.*
FROM glt_ticket_template a
<where>
<if test="param.id != null">
AND a.id = #{param.id}
</if>
<if test="param.goodsId != null">
AND a.goods_id = #{param.goodsId}
</if>
<if test="param.name != null">
AND a.name LIKE CONCAT('%', #{param.name}, '%')
</if>
<if test="param.enabled != null">
AND a.enabled = #{param.enabled}
</if>
<if test="param.unitName != null">
AND a.unit_name LIKE CONCAT('%', #{param.unitName}, '%')
</if>
<if test="param.minBuyQty != null">
AND a.min_buy_qty = #{param.minBuyQty}
</if>
<if test="param.startSendQty != null">
AND a.start_send_qty = #{param.startSendQty}
</if>
<if test="param.giftMultiplier != null">
AND a.gift_multiplier = #{param.giftMultiplier}
</if>
<if test="param.includeBuyQty != null">
AND a.include_buy_qty = #{param.includeBuyQty}
</if>
<if test="param.monthlyReleaseQty != null">
AND a.monthly_release_qty = #{param.monthlyReleaseQty}
</if>
<if test="param.releasePeriods != null">
AND a.release_periods = #{param.releasePeriods}
</if>
<if test="param.firstReleaseMode != null">
AND a.first_release_mode = #{param.firstReleaseMode}
</if>
<if test="param.userId != null">
AND a.user_id = #{param.userId}
</if>
<if test="param.sortNumber != null">
AND a.sort_number = #{param.sortNumber}
</if>
<if test="param.comments != null">
AND a.comments LIKE CONCAT('%', #{param.comments}, '%')
</if>
<if test="param.status != null">
AND a.status = #{param.status}
</if>
<if test="param.deleted != null">
AND a.deleted = #{param.deleted}
</if>
<if test="param.deleted == null">
AND a.deleted = 0
</if>
<if test="param.createTimeStart != null">
AND a.create_time &gt;= #{param.createTimeStart}
</if>
<if test="param.createTimeEnd != null">
AND a.create_time &lt;= #{param.createTimeEnd}
</if>
<if test="param.keywords != null">
AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%')
)
</if>
</where>
</sql>
<!-- 分页查询 -->
<select id="selectPageRel" resultType="com.gxwebsoft.glt.entity.GltTicketTemplate">
<include refid="selectSql"></include>
</select>
<!-- 查询全部 -->
<select id="selectListRel" resultType="com.gxwebsoft.glt.entity.GltTicketTemplate">
<include refid="selectSql"></include>
</select>
</mapper>

View File

@@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.gxwebsoft.glt.mapper.GltUserTicketLogMapper">
<!-- 关联查询sql -->
<sql id="selectSql">
SELECT a.*, u.nickname, u.avatar, u.phone
FROM glt_user_ticket_log a
LEFT JOIN gxwebsoft_core.sys_user u ON a.user_id = u.user_id
<where>
<if test="param.id != null">
AND a.id = #{param.id}
</if>
<if test="param.userTicketId != null">
AND a.user_ticket_id = #{param.userTicketId}
</if>
<if test="param.changeType != null">
AND a.change_type = #{param.changeType}
</if>
<if test="param.changeAvailable != null">
AND a.change_available = #{param.changeAvailable}
</if>
<if test="param.changeFrozen != null">
AND a.change_frozen = #{param.changeFrozen}
</if>
<if test="param.changeUsed != null">
AND a.change_used = #{param.changeUsed}
</if>
<if test="param.availableAfter != null">
AND a.available_after = #{param.availableAfter}
</if>
<if test="param.frozenAfter != null">
AND a.frozen_after = #{param.frozenAfter}
</if>
<if test="param.usedAfter != null">
AND a.used_after = #{param.usedAfter}
</if>
<if test="param.orderId != null">
AND a.order_id = #{param.orderId}
</if>
<if test="param.orderNo != null">
AND a.order_no LIKE CONCAT('%', #{param.orderNo}, '%')
</if>
<if test="param.userId != null">
AND a.user_id = #{param.userId}
</if>
<if test="param.sortNumber != null">
AND a.sort_number = #{param.sortNumber}
</if>
<if test="param.comments != null">
AND a.comments LIKE CONCAT('%', #{param.comments}, '%')
</if>
<if test="param.status != null">
AND a.status = #{param.status}
</if>
<if test="param.deleted != null">
AND a.deleted = #{param.deleted}
</if>
<if test="param.deleted == null">
AND a.deleted = 0
</if>
<if test="param.createTimeStart != null">
AND a.create_time &gt;= #{param.createTimeStart}
</if>
<if test="param.createTimeEnd != null">
AND a.create_time &lt;= #{param.createTimeEnd}
</if>
<if test="param.keywords != null">
AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%')
OR a.user_id = #{param.keywords}
OR a.user_ticket_id = #{param.keywords}
OR a.order_no = #{param.keywords}
)
</if>
</where>
</sql>
<!-- 分页查询 -->
<select id="selectPageRel" resultType="com.gxwebsoft.glt.entity.GltUserTicketLog">
<include refid="selectSql"></include>
</select>
<!-- 查询全部 -->
<select id="selectListRel" resultType="com.gxwebsoft.glt.entity.GltUserTicketLog">
<include refid="selectSql"></include>
</select>
</mapper>

View File

@@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.gxwebsoft.glt.mapper.GltUserTicketMapper">
<!-- 关联查询sql -->
<sql id="selectSql">
SELECT a.*, u.nickname, u.avatar, u.phone, m.name AS templateName, o.pay_price
FROM glt_user_ticket a
LEFT JOIN gxwebsoft_core.sys_user u ON a.user_id = u.user_id
LEFT JOIN glt_ticket_template m ON a.template_id = m.id
LEFT JOIN shop_order o ON a.order_no = o.order_no
<where>
<if test="param.id != null">
AND a.id = #{param.id}
</if>
<if test="param.templateId != null">
AND a.template_id = #{param.templateId}
</if>
<if test="param.goodsId != null">
AND a.goods_id = #{param.goodsId}
</if>
<if test="param.orderId != null">
AND a.order_id = #{param.orderId}
</if>
<if test="param.orderNo != null">
AND a.order_no LIKE CONCAT('%', #{param.orderNo}, '%')
</if>
<if test="param.orderGoodsId != null">
AND a.order_goods_id = #{param.orderGoodsId}
</if>
<if test="param.totalQty != null">
AND a.total_qty = #{param.totalQty}
</if>
<if test="param.availableQty != null">
AND a.available_qty = #{param.availableQty}
</if>
<if test="param.frozenQty != null">
AND a.frozen_qty = #{param.frozenQty}
</if>
<if test="param.usedQty != null">
AND a.used_qty = #{param.usedQty}
</if>
<if test="param.releasedQty != null">
AND a.released_qty = #{param.releasedQty}
</if>
<if test="param.userId != null">
AND a.user_id = #{param.userId}
</if>
<if test="param.sortNumber != null">
AND a.sort_number = #{param.sortNumber}
</if>
<if test="param.comments != null">
AND a.comments LIKE CONCAT('%', #{param.comments}, '%')
</if>
<if test="param.status != null">
AND a.status = #{param.status}
</if>
<if test="param.deleted != null">
AND a.deleted = #{param.deleted}
</if>
<if test="param.deleted == null">
AND a.deleted = 0
</if>
<if test="param.createTimeStart != null">
AND a.create_time &gt;= #{param.createTimeStart}
</if>
<if test="param.createTimeEnd != null">
AND a.create_time &lt;= #{param.createTimeEnd}
</if>
<if test="param.keywords != null">
AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%')
OR a.user_id = #{param.keywords}
OR a.order_no = #{param.keywords}
)
</if>
</where>
</sql>
<!-- 分页查询 -->
<select id="selectPageRel" resultType="com.gxwebsoft.glt.entity.GltUserTicket">
<include refid="selectSql"></include>
</select>
<!-- 查询全部 -->
<select id="selectListRel" resultType="com.gxwebsoft.glt.entity.GltUserTicket">
<include refid="selectSql"></include>
</select>
<!-- 我的水票总数sum(total_qty) -->
<select id="sumTotalQtyByUserId" resultType="java.lang.Integer">
SELECT IFNULL(SUM(total_qty), 0)
FROM glt_user_ticket
WHERE user_id = #{userId}
AND deleted = 0
</select>
</mapper>

View File

@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.gxwebsoft.glt.mapper.GltUserTicketReleaseMapper">
<!-- 关联查询sql -->
<sql id="selectSql">
SELECT a.*, u.nickname, u.avatar, u.phone
FROM glt_user_ticket_release a
LEFT JOIN gxwebsoft_core.sys_user u ON a.user_id = u.user_id
<where>
<if test="param.id != null">
AND a.id = #{param.id}
</if>
<if test="param.userTicketId != null">
AND a.user_ticket_id LIKE CONCAT('%', #{param.userTicketId}, '%')
</if>
<if test="param.userId != null">
AND a.user_id = #{param.userId}
</if>
<if test="param.periodNo != null">
AND a.period_no = #{param.periodNo}
</if>
<if test="param.releaseQty != null">
AND a.release_qty = #{param.releaseQty}
</if>
<if test="param.releaseTime != null">
AND a.release_time LIKE CONCAT('%', #{param.releaseTime}, '%')
</if>
<if test="param.status != null">
AND a.status = #{param.status}
</if>
<if test="param.deleted != null">
AND a.deleted = #{param.deleted}
</if>
<if test="param.deleted == null">
AND a.deleted = 0
</if>
<if test="param.createTimeStart != null">
AND a.create_time &gt;= #{param.createTimeStart}
</if>
<if test="param.createTimeEnd != null">
AND a.create_time &lt;= #{param.createTimeEnd}
</if>
<if test="param.keywords != null">
AND (a.user_ticket_id = #{param.keywords}
OR u.user_id = #{param.keywords}
)
</if>
</where>
</sql>
<!-- 分页查询 -->
<select id="selectPageRel" resultType="com.gxwebsoft.glt.entity.GltUserTicketRelease">
<include refid="selectSql"></include>
</select>
<!-- 查询全部 -->
<select id="selectListRel" resultType="com.gxwebsoft.glt.entity.GltUserTicketRelease">
<include refid="selectSql"></include>
</select>
</mapper>

View File

@@ -0,0 +1,89 @@
package com.gxwebsoft.glt.param;
import java.math.BigDecimal;
import com.gxwebsoft.common.core.annotation.QueryField;
import com.gxwebsoft.common.core.annotation.QueryType;
import com.gxwebsoft.common.core.web.BaseParam;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 水票查询参数
*
* @author 科技小王子
* @since 2026-02-03 18:55:54
*/
@Data
@EqualsAndHashCode(callSuper = false)
@JsonInclude(JsonInclude.Include.NON_NULL)
@Schema(name = "GltTicketTemplateParam对象", description = "水票查询参数")
public class GltTicketTemplateParam extends BaseParam {
private static final long serialVersionUID = 1L;
@QueryField(type = QueryType.EQ)
private Integer id;
@Schema(description = "关联商品ID")
@QueryField(type = QueryType.EQ)
private Integer goodsId;
@Schema(description = "名称")
private String name;
@Schema(description = "启用")
@QueryField(type = QueryType.EQ)
private Boolean enabled;
@Schema(description = "单位名称")
private String unitName;
@Schema(description = "最小购买数量")
@QueryField(type = QueryType.EQ)
private Integer minBuyQty;
@Schema(description = "起始发送数量")
@QueryField(type = QueryType.EQ)
private Integer startSendQty;
@Schema(description = "买赠买1送4 => gift_multiplier=4")
@QueryField(type = QueryType.EQ)
private Integer giftMultiplier;
@Schema(description = "是否把购买量也计入套票总量(默认仅计入赠送量)")
@QueryField(type = QueryType.EQ)
private Boolean includeBuyQty;
@Schema(description = "每期释放数量默认每月释放10")
@QueryField(type = QueryType.EQ)
private Integer monthlyReleaseQty;
@Schema(description = "总共释放多少期(若配置>0则按期数平均分摊")
@QueryField(type = QueryType.EQ)
private Integer releasePeriods;
@Schema(description = "首期释放时机0=支付成功当刻1=下个月同日")
@QueryField(type = QueryType.EQ)
private Integer firstReleaseMode;
@Schema(description = "用户ID")
@QueryField(type = QueryType.EQ)
private Integer userId;
@Schema(description = "排序(数字越小越靠前)")
@QueryField(type = QueryType.EQ)
private Integer sortNumber;
@Schema(description = "备注")
private String comments;
@Schema(description = "状态, 0正常, 1冻结")
@QueryField(type = QueryType.EQ)
private Integer status;
@Schema(description = "是否删除, 0否, 1是")
@QueryField(type = QueryType.EQ)
private Integer deleted;
}

View File

@@ -0,0 +1,86 @@
package com.gxwebsoft.glt.param;
import java.math.BigDecimal;
import com.gxwebsoft.common.core.annotation.QueryField;
import com.gxwebsoft.common.core.annotation.QueryType;
import com.gxwebsoft.common.core.web.BaseParam;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 消费日志查询参数
*
* @author 科技小王子
* @since 2026-02-03 18:55:55
*/
@Data
@EqualsAndHashCode(callSuper = false)
@JsonInclude(JsonInclude.Include.NON_NULL)
@Schema(name = "GltUserTicketLogParam对象", description = "消费日志查询参数")
public class GltUserTicketLogParam extends BaseParam {
private static final long serialVersionUID = 1L;
@QueryField(type = QueryType.EQ)
private Integer id;
@Schema(description = "用户水票ID")
@QueryField(type = QueryType.EQ)
private Integer userTicketId;
@Schema(description = "变更类型")
@QueryField(type = QueryType.EQ)
private Integer changeType;
@Schema(description = "可更改")
@QueryField(type = QueryType.EQ)
private Integer changeAvailable;
@Schema(description = "更改冻结状态")
@QueryField(type = QueryType.EQ)
private Integer changeFrozen;
@Schema(description = "已使用更改")
@QueryField(type = QueryType.EQ)
private Integer changeUsed;
@Schema(description = "可用后")
@QueryField(type = QueryType.EQ)
private Integer availableAfter;
@Schema(description = "冻结后")
@QueryField(type = QueryType.EQ)
private Integer frozenAfter;
@Schema(description = "使用后")
@QueryField(type = QueryType.EQ)
private Integer usedAfter;
@Schema(description = "订单ID")
@QueryField(type = QueryType.EQ)
private Integer orderId;
@Schema(description = "订单编号")
private String orderNo;
@Schema(description = "用户ID")
@QueryField(type = QueryType.EQ)
private Integer userId;
@Schema(description = "排序(数字越小越靠前)")
@QueryField(type = QueryType.EQ)
private Integer sortNumber;
@Schema(description = "备注")
private String comments;
@Schema(description = "状态, 0正常, 1冻结")
@QueryField(type = QueryType.EQ)
private Integer status;
@Schema(description = "是否删除, 0否, 1是")
@QueryField(type = QueryType.EQ)
private Integer deleted;
}

View File

@@ -0,0 +1,86 @@
package com.gxwebsoft.glt.param;
import java.math.BigDecimal;
import com.gxwebsoft.common.core.annotation.QueryField;
import com.gxwebsoft.common.core.annotation.QueryType;
import com.gxwebsoft.common.core.web.BaseParam;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 我的水票查询参数
*
* @author 科技小王子
* @since 2026-02-03 18:55:55
*/
@Data
@EqualsAndHashCode(callSuper = false)
@JsonInclude(JsonInclude.Include.NON_NULL)
@Schema(name = "GltUserTicketParam对象", description = "我的水票查询参数")
public class GltUserTicketParam extends BaseParam {
private static final long serialVersionUID = 1L;
@QueryField(type = QueryType.EQ)
private Integer id;
@Schema(description = "模板ID")
@QueryField(type = QueryType.EQ)
private Integer templateId;
@Schema(description = "商品ID")
@QueryField(type = QueryType.EQ)
private Integer goodsId;
@Schema(description = "订单ID")
@QueryField(type = QueryType.EQ)
private Integer orderId;
@Schema(description = "订单编号")
private String orderNo;
@Schema(description = "订单商品ID")
@QueryField(type = QueryType.EQ)
private Integer orderGoodsId;
@Schema(description = "总数量")
@QueryField(type = QueryType.EQ)
private Integer totalQty;
@Schema(description = "可用数量")
@QueryField(type = QueryType.EQ)
private Integer availableQty;
@Schema(description = "冻结数量")
@QueryField(type = QueryType.EQ)
private Integer frozenQty;
@Schema(description = "已使用数量")
@QueryField(type = QueryType.EQ)
private Integer usedQty;
@Schema(description = "已释放数量")
@QueryField(type = QueryType.EQ)
private Integer releasedQty;
@Schema(description = "用户ID")
@QueryField(type = QueryType.EQ)
private Integer userId;
@Schema(description = "排序(数字越小越靠前)")
@QueryField(type = QueryType.EQ)
private Integer sortNumber;
@Schema(description = "备注")
private String comments;
@Schema(description = "状态, 0正常, 1冻结")
@QueryField(type = QueryType.EQ)
private Integer status;
@Schema(description = "是否删除, 0否, 1是")
@QueryField(type = QueryType.EQ)
private Integer deleted;
}

View File

@@ -0,0 +1,54 @@
package com.gxwebsoft.glt.param;
import java.math.BigDecimal;
import com.gxwebsoft.common.core.annotation.QueryField;
import com.gxwebsoft.common.core.annotation.QueryType;
import com.gxwebsoft.common.core.web.BaseParam;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 水票释放查询参数
*
* @author 科技小王子
* @since 2026-02-03 18:55:55
*/
@Data
@EqualsAndHashCode(callSuper = false)
@JsonInclude(JsonInclude.Include.NON_NULL)
@Schema(name = "GltUserTicketReleaseParam对象", description = "水票释放查询参数")
public class GltUserTicketReleaseParam extends BaseParam {
private static final long serialVersionUID = 1L;
@QueryField(type = QueryType.EQ)
private Integer id;
@Schema(description = "水票ID")
private Integer userTicketId;
@Schema(description = "用户ID")
@QueryField(type = QueryType.EQ)
private Integer userId;
@Schema(description = "周期编号")
@QueryField(type = QueryType.EQ)
private Integer periodNo;
@Schema(description = "释放数量")
@QueryField(type = QueryType.EQ)
private Integer releaseQty;
@Schema(description = "释放时间")
private String releaseTime;
@Schema(description = "状态")
@QueryField(type = QueryType.EQ)
private Integer status;
@Schema(description = "是否删除, 0否, 1是")
@QueryField(type = QueryType.EQ)
private Integer deleted;
}

View File

@@ -0,0 +1,362 @@
package com.gxwebsoft.glt.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.gxwebsoft.glt.entity.GltTicketTemplate;
import com.gxwebsoft.glt.entity.GltUserTicket;
import com.gxwebsoft.glt.entity.GltUserTicketLog;
import com.gxwebsoft.glt.entity.GltUserTicketRelease;
import com.gxwebsoft.shop.entity.ShopOrder;
import com.gxwebsoft.shop.entity.ShopOrderGoods;
import com.gxwebsoft.shop.service.ShopOrderGoodsService;
import com.gxwebsoft.shop.service.ShopOrderService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* 套票发放(从订单生成用户套票 + 释放计划)的业务逻辑。
*
* 说明:
* - 定时任务无登录态时MyBatis-Plus 多租户插件可能拿不到 tenantId
* 外层任务方法会通过 @IgnoreTenant 禁用租户拦截,本服务内部强制用 tenantId 过滤。
* - 幂等:以 (tenantId, templateId, orderNo, orderGoodsId) 判断是否已发放。
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class GltTicketIssueService {
public static final int CHANGE_TYPE_ISSUE = 10;
private enum IssueOutcome {
ISSUED,
ALREADY_ISSUED,
SKIPPED,
NO_TEMPLATE
}
private final ShopOrderService shopOrderService;
private final ShopOrderGoodsService shopOrderGoodsService;
private final GltTicketTemplateService gltTicketTemplateService;
private final GltUserTicketService gltUserTicketService;
private final GltUserTicketReleaseService gltUserTicketReleaseService;
private final GltUserTicketLogService gltUserTicketLogService;
private final TransactionTemplate transactionTemplate;
/**
* 扫描“今日订单”,执行套票发放。
*/
public void issueTodayOrders(Integer tenantId, Integer formId) {
LocalDateTime todayStart = LocalDate.now().atStartOfDay();
LocalDateTime tomorrowStart = todayStart.plusDays(1);
List<ShopOrder> orders = shopOrderService.list(
new LambdaQueryWrapper<ShopOrder>()
.eq(ShopOrder::getTenantId, tenantId)
.eq(ShopOrder::getFormId, formId)
.eq(ShopOrder::getPayStatus, true)
.eq(ShopOrder::getOrderStatus, 0)
// 今日订单(兼容:以 create_time 或 pay_time 任一落在今日即可)
.and(w -> w
.ge(ShopOrder::getCreateTime, todayStart).lt(ShopOrder::getCreateTime, tomorrowStart)
.or()
.ge(ShopOrder::getPayTime, todayStart).lt(ShopOrder::getPayTime, tomorrowStart)
)
.orderByAsc(ShopOrder::getPayTime)
.orderByAsc(ShopOrder::getOrderId)
);
if (orders.isEmpty()) {
log.debug("套票发放扫描:今日无符合条件的订单 tenantId={}, formId={}", tenantId, formId);
return;
}
int success = 0;
int skipped = 0;
int failed = 0;
for (ShopOrder order : orders) {
try {
int issuedCount = issueForOrder(tenantId, formId, order);
if (issuedCount > 0) {
success += issuedCount;
} else {
skipped++;
}
} catch (Exception e) {
failed++;
log.error("套票发放失败 - tenantId={}, orderNo={}, orderId={}",
tenantId, order.getOrderNo(), order.getOrderId(), e);
}
}
log.info("套票发放扫描完成 - tenantId={}, formId={}, 订单数={}, 发放成功={}, 跳过={}, 失败={}",
tenantId, formId, orders.size(), success, skipped, failed);
}
private int issueForOrder(Integer tenantId, Integer formId, ShopOrder order) {
List<ShopOrderGoods> goodsList = shopOrderGoodsService.getListByOrderIdIgnoreTenant(order.getOrderId());
if (goodsList == null || goodsList.isEmpty()) {
return 0;
}
int issuedCount = 0; // 本轮新增发放数量(用于统计)
boolean shouldCompleteOrder = false;
for (ShopOrderGoods og : goodsList) {
if (!Objects.equals(og.getGoodsId(), formId)) {
continue;
}
IssueOutcome outcome = transactionTemplate.execute(status -> doIssueOne(tenantId, order, og));
if (outcome == IssueOutcome.ISSUED) {
issuedCount++;
shouldCompleteOrder = true;
} else if (outcome == IssueOutcome.ALREADY_ISSUED) {
// 幂等:已处理过也应视为完成,避免重复扫描
shouldCompleteOrder = true;
}
}
if (shouldCompleteOrder) {
LocalDateTime now = LocalDateTime.now();
// 任务执行完后将订单置为“已完成”order_status=1
shopOrderService.update(
new LambdaUpdateWrapper<ShopOrder>()
.eq(ShopOrder::getOrderId, order.getOrderId())
.eq(ShopOrder::getTenantId, tenantId)
.set(ShopOrder::getOrderStatus, 1)
// 同步更新发货状态为“已发货”
.set(ShopOrder::getDeliveryStatus, 20)
.set(ShopOrder::getHasTakeGift, true)
.set(ShopOrder::getUpdateTime, now)
);
}
return issuedCount;
}
private IssueOutcome doIssueOne(Integer tenantId, ShopOrder order, ShopOrderGoods og) {
if (order.getUserId() == null) {
log.warn("套票发放跳过:订单缺少 userId - tenantId={}, orderNo={}", tenantId, order.getOrderNo());
return IssueOutcome.SKIPPED;
}
if (og.getId() == null) {
log.warn("套票发放跳过:订单商品缺少 id - tenantId={}, orderNo={}", tenantId, order.getOrderNo());
return IssueOutcome.SKIPPED;
}
GltTicketTemplate template = gltTicketTemplateService.getOne(
new LambdaQueryWrapper<GltTicketTemplate>()
.eq(GltTicketTemplate::getTenantId, tenantId)
.eq(GltTicketTemplate::getGoodsId, og.getGoodsId())
.eq(GltTicketTemplate::getDeleted, 0)
.last("limit 1")
);
if (template == null) {
log.warn("套票发放跳过:未配置套票模板 - tenantId={}, goodsId={}, orderNo={}",
tenantId, og.getGoodsId(), order.getOrderNo());
return IssueOutcome.NO_TEMPLATE;
}
if (!Boolean.TRUE.equals(template.getEnabled())) {
log.info("套票发放跳过:套票模板未启用 - tenantId={}, templateId={}, goodsId={}",
tenantId, template.getId(), template.getGoodsId());
return IssueOutcome.SKIPPED;
}
// 幂等:同一 orderNo + orderGoodsId 只发放一次
GltUserTicket existing = gltUserTicketService.getOne(
new LambdaQueryWrapper<GltUserTicket>()
.eq(GltUserTicket::getTenantId, tenantId)
.eq(GltUserTicket::getTemplateId, template.getId())
.eq(GltUserTicket::getOrderNo, order.getOrderNo())
.eq(GltUserTicket::getOrderGoodsId, og.getId())
.eq(GltUserTicket::getDeleted, 0)
.last("limit 1")
);
if (existing != null) {
log.debug("套票已发放,跳过 - tenantId={}, orderNo={}, orderGoodsId={}, userTicketId={}",
tenantId, order.getOrderNo(), og.getId(), existing.getId());
return IssueOutcome.ALREADY_ISSUED;
}
int buyQty = og.getTotalNum() != null ? og.getTotalNum() : 0;
if (buyQty <= 0) {
log.warn("套票发放跳过:购买数量无效 - tenantId={}, orderNo={}, orderGoodsId={}, totalNum={}",
tenantId, order.getOrderNo(), og.getId(), og.getTotalNum());
return IssueOutcome.SKIPPED;
}
Integer minBuyQty = template.getMinBuyQty();
if (minBuyQty != null && minBuyQty > 0 && buyQty < minBuyQty) {
log.info("套票发放跳过:未达到最小购买数量 - tenantId={}, orderNo={}, buyQty={}, minBuyQty={}",
tenantId, order.getOrderNo(), buyQty, minBuyQty);
return IssueOutcome.SKIPPED;
}
int giftMultiplier = template.getGiftMultiplier() != null ? template.getGiftMultiplier() : 0;
int giftQty = buyQty * Math.max(giftMultiplier, 0);
// 购买量buyQty应立即可用赠送量giftQty进入冻结并按计划释放。
int totalQty = buyQty + giftQty;
if (totalQty <= 0) {
log.info("套票发放跳过计算结果为0 - tenantId={}, orderNo={}, buyQty={}, giftMultiplier={}, includeBuyQty={}",
tenantId, order.getOrderNo(), buyQty, giftMultiplier, template.getIncludeBuyQty());
return IssueOutcome.SKIPPED;
}
LocalDateTime now = LocalDateTime.now();
GltUserTicket userTicket = new GltUserTicket();
userTicket.setTemplateId(template.getId());
userTicket.setGoodsId(og.getGoodsId());
userTicket.setOrderId(order.getOrderId());
userTicket.setOrderNo(order.getOrderNo());
userTicket.setOrderGoodsId(og.getId());
userTicket.setTotalQty(totalQty);
userTicket.setAvailableQty(buyQty);
userTicket.setFrozenQty(giftQty);
userTicket.setUsedQty(0);
// 初始可用量来自“购买量”,视为已释放
userTicket.setReleasedQty(buyQty);
userTicket.setUserId(order.getUserId());
userTicket.setSortNumber(0);
userTicket.setComments("订单发放套票");
userTicket.setStatus(0);
userTicket.setDeleted(0);
userTicket.setTenantId(tenantId);
userTicket.setCreateTime(now);
userTicket.setUpdateTime(now);
gltUserTicketService.save(userTicket);
// 生成释放计划(按月)
LocalDateTime baseTime = order.getPayTime() != null ? order.getPayTime() : order.getCreateTime();
if (baseTime == null) {
baseTime = now;
}
List<GltUserTicketRelease> releases = buildReleasePlan(template, userTicket, baseTime, giftQty, now);
if (!releases.isEmpty()) {
gltUserTicketReleaseService.saveBatch(releases);
}
// 发放流水
GltUserTicketLog issueLog = new GltUserTicketLog();
issueLog.setUserTicketId(userTicket.getId());
issueLog.setChangeType(CHANGE_TYPE_ISSUE);
issueLog.setChangeAvailable(buyQty);
issueLog.setChangeFrozen(giftQty);
issueLog.setChangeUsed(0);
issueLog.setAvailableAfter(buyQty);
issueLog.setFrozenAfter(giftQty);
issueLog.setUsedAfter(0);
issueLog.setOrderId(order.getOrderId());
issueLog.setOrderNo(order.getOrderNo());
issueLog.setUserId(order.getUserId());
issueLog.setSortNumber(0);
issueLog.setComments("套票发放");
issueLog.setStatus(0);
issueLog.setDeleted(0);
issueLog.setTenantId(tenantId);
issueLog.setCreateTime(now);
issueLog.setUpdateTime(now);
gltUserTicketLogService.save(issueLog);
log.info("套票发放成功 - tenantId={}, orderNo={}, orderGoodsId={}, templateId={}, userTicketId={}, totalQty={}",
tenantId, order.getOrderNo(), og.getId(), template.getId(), userTicket.getId(), totalQty);
return IssueOutcome.ISSUED;
}
private List<GltUserTicketRelease> buildReleasePlan(GltTicketTemplate template,
GltUserTicket userTicket,
LocalDateTime baseTime,
int totalQty,
LocalDateTime now) {
List<GltUserTicketRelease> list = new ArrayList<>();
if (totalQty <= 0) {
return list;
}
// 首期释放时间
LocalDateTime firstReleaseTime;
if (Objects.equals(template.getFirstReleaseMode(), 1)) {
firstReleaseTime = nextMonthSameDay(baseTime);
} else {
firstReleaseTime = baseTime;
}
// 每期释放数量计算
Integer releasePeriods = template.getReleasePeriods();
if (releasePeriods != null && releasePeriods > 0) {
int base = totalQty / releasePeriods;
int remainder = totalQty % releasePeriods;
for (int i = 1; i <= releasePeriods; i++) {
int qty = base + (i <= remainder ? 1 : 0);
if (qty <= 0) {
continue;
}
list.add(buildRelease(userTicket, i, qty, firstReleaseTime.plusMonths(i - 1), now));
}
return list;
}
int monthlyReleaseQty = template.getMonthlyReleaseQty() != null && template.getMonthlyReleaseQty() > 0
? template.getMonthlyReleaseQty()
: 10;
int periods = (totalQty + monthlyReleaseQty - 1) / monthlyReleaseQty;
int remaining = totalQty;
for (int i = 1; i <= periods; i++) {
int qty = Math.min(monthlyReleaseQty, remaining);
if (qty <= 0) {
break;
}
remaining -= qty;
list.add(buildRelease(userTicket, i, qty, firstReleaseTime.plusMonths(i - 1), now));
}
return list;
}
private GltUserTicketRelease buildRelease(GltUserTicket userTicket,
int periodNo,
int releaseQty,
LocalDateTime releaseTime,
LocalDateTime now) {
GltUserTicketRelease r = new GltUserTicketRelease();
r.setUserTicketId(userTicket.getId() != null ? userTicket.getId().longValue() : null);
r.setUserId(userTicket.getUserId());
r.setPeriodNo(periodNo);
r.setReleaseQty(releaseQty);
r.setReleaseTime(releaseTime);
r.setStatus(0);
r.setDeleted(0);
r.setTenantId(userTicket.getTenantId());
r.setCreateTime(now);
r.setUpdateTime(now);
return r;
}
private static LocalDateTime nextMonthSameDay(LocalDateTime baseTime) {
LocalDate baseDate = baseTime.toLocalDate();
LocalTime time = baseTime.toLocalTime();
LocalDate nextMonth = baseDate.plusMonths(1);
int day = Math.min(baseDate.getDayOfMonth(), nextMonth.lengthOfMonth());
LocalDate adjusted = nextMonth.withDayOfMonth(day);
return LocalDateTime.of(adjusted, time);
}
}

View File

@@ -0,0 +1,42 @@
package com.gxwebsoft.glt.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.gxwebsoft.common.core.web.PageResult;
import com.gxwebsoft.glt.entity.GltTicketTemplate;
import com.gxwebsoft.glt.param.GltTicketTemplateParam;
import java.util.List;
/**
* 水票Service
*
* @author 科技小王子
* @since 2026-02-03 18:55:54
*/
public interface GltTicketTemplateService extends IService<GltTicketTemplate> {
/**
* 分页关联查询
*
* @param param 查询参数
* @return PageResult<GltTicketTemplate>
*/
PageResult<GltTicketTemplate> pageRel(GltTicketTemplateParam param);
/**
* 关联查询全部
*
* @param param 查询参数
* @return List<GltTicketTemplate>
*/
List<GltTicketTemplate> listRel(GltTicketTemplateParam param);
/**
* 根据id查询
*
* @param id
* @return GltTicketTemplate
*/
GltTicketTemplate getByIdRel(Integer id);
}

View File

@@ -0,0 +1,42 @@
package com.gxwebsoft.glt.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.gxwebsoft.common.core.web.PageResult;
import com.gxwebsoft.glt.entity.GltUserTicketLog;
import com.gxwebsoft.glt.param.GltUserTicketLogParam;
import java.util.List;
/**
* 消费日志Service
*
* @author 科技小王子
* @since 2026-02-03 18:55:55
*/
public interface GltUserTicketLogService extends IService<GltUserTicketLog> {
/**
* 分页关联查询
*
* @param param 查询参数
* @return PageResult<GltUserTicketLog>
*/
PageResult<GltUserTicketLog> pageRel(GltUserTicketLogParam param);
/**
* 关联查询全部
*
* @param param 查询参数
* @return List<GltUserTicketLog>
*/
List<GltUserTicketLog> listRel(GltUserTicketLogParam param);
/**
* 根据id查询
*
* @param id
* @return GltUserTicketLog
*/
GltUserTicketLog getByIdRel(Integer id);
}

View File

@@ -0,0 +1,42 @@
package com.gxwebsoft.glt.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.gxwebsoft.common.core.web.PageResult;
import com.gxwebsoft.glt.entity.GltUserTicketRelease;
import com.gxwebsoft.glt.param.GltUserTicketReleaseParam;
import java.util.List;
/**
* 水票释放Service
*
* @author 科技小王子
* @since 2026-02-03 18:55:55
*/
public interface GltUserTicketReleaseService extends IService<GltUserTicketRelease> {
/**
* 分页关联查询
*
* @param param 查询参数
* @return PageResult<GltUserTicketRelease>
*/
PageResult<GltUserTicketRelease> pageRel(GltUserTicketReleaseParam param);
/**
* 关联查询全部
*
* @param param 查询参数
* @return List<GltUserTicketRelease>
*/
List<GltUserTicketRelease> listRel(GltUserTicketReleaseParam param);
/**
* 根据id查询
*
* @param id
* @return GltUserTicketRelease
*/
GltUserTicketRelease getByIdRel(Integer id);
}

View File

@@ -0,0 +1,50 @@
package com.gxwebsoft.glt.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.gxwebsoft.common.core.web.PageResult;
import com.gxwebsoft.glt.entity.GltUserTicket;
import com.gxwebsoft.glt.param.GltUserTicketParam;
import java.util.List;
/**
* 我的水票Service
*
* @author 科技小王子
* @since 2026-02-03 18:55:55
*/
public interface GltUserTicketService extends IService<GltUserTicket> {
/**
* 分页关联查询
*
* @param param 查询参数
* @return PageResult<GltUserTicket>
*/
PageResult<GltUserTicket> pageRel(GltUserTicketParam param);
/**
* 关联查询全部
*
* @param param 查询参数
* @return List<GltUserTicket>
*/
List<GltUserTicket> listRel(GltUserTicketParam param);
/**
* 根据id查询
*
* @param id
* @return GltUserTicket
*/
GltUserTicket getByIdRel(Integer id);
/**
* 统计指定用户水票总数量sum(total_qty)
*
* @param userId 用户ID
* @return 总数量无记录返回0
*/
Integer sumTotalQtyByUserId(Integer userId);
}

View File

@@ -0,0 +1,47 @@
package com.gxwebsoft.glt.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gxwebsoft.glt.mapper.GltTicketTemplateMapper;
import com.gxwebsoft.glt.service.GltTicketTemplateService;
import com.gxwebsoft.glt.entity.GltTicketTemplate;
import com.gxwebsoft.glt.param.GltTicketTemplateParam;
import com.gxwebsoft.common.core.web.PageParam;
import com.gxwebsoft.common.core.web.PageResult;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 水票Service实现
*
* @author 科技小王子
* @since 2026-02-03 18:55:54
*/
@Service
public class GltTicketTemplateServiceImpl extends ServiceImpl<GltTicketTemplateMapper, GltTicketTemplate> implements GltTicketTemplateService {
@Override
public PageResult<GltTicketTemplate> pageRel(GltTicketTemplateParam param) {
PageParam<GltTicketTemplate, GltTicketTemplateParam> page = new PageParam<>(param);
page.setDefaultOrder("sort_number asc, create_time desc");
List<GltTicketTemplate> list = baseMapper.selectPageRel(page, param);
return new PageResult<>(list, page.getTotal());
}
@Override
public List<GltTicketTemplate> listRel(GltTicketTemplateParam param) {
List<GltTicketTemplate> list = baseMapper.selectListRel(param);
// 排序
PageParam<GltTicketTemplate, GltTicketTemplateParam> page = new PageParam<>();
page.setDefaultOrder("sort_number asc, create_time desc");
return page.sortRecords(list);
}
@Override
public GltTicketTemplate getByIdRel(Integer id) {
GltTicketTemplateParam param = new GltTicketTemplateParam();
param.setId(id);
return param.getOne(baseMapper.selectListRel(param));
}
}

View File

@@ -0,0 +1,47 @@
package com.gxwebsoft.glt.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gxwebsoft.glt.mapper.GltUserTicketLogMapper;
import com.gxwebsoft.glt.service.GltUserTicketLogService;
import com.gxwebsoft.glt.entity.GltUserTicketLog;
import com.gxwebsoft.glt.param.GltUserTicketLogParam;
import com.gxwebsoft.common.core.web.PageParam;
import com.gxwebsoft.common.core.web.PageResult;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 消费日志Service实现
*
* @author 科技小王子
* @since 2026-02-03 18:55:55
*/
@Service
public class GltUserTicketLogServiceImpl extends ServiceImpl<GltUserTicketLogMapper, GltUserTicketLog> implements GltUserTicketLogService {
@Override
public PageResult<GltUserTicketLog> pageRel(GltUserTicketLogParam param) {
PageParam<GltUserTicketLog, GltUserTicketLogParam> page = new PageParam<>(param);
page.setDefaultOrder("sort_number asc, create_time desc");
List<GltUserTicketLog> list = baseMapper.selectPageRel(page, param);
return new PageResult<>(list, page.getTotal());
}
@Override
public List<GltUserTicketLog> listRel(GltUserTicketLogParam param) {
List<GltUserTicketLog> list = baseMapper.selectListRel(param);
// 排序
PageParam<GltUserTicketLog, GltUserTicketLogParam> page = new PageParam<>();
page.setDefaultOrder("sort_number asc, create_time desc");
return page.sortRecords(list);
}
@Override
public GltUserTicketLog getByIdRel(Integer id) {
GltUserTicketLogParam param = new GltUserTicketLogParam();
param.setId(id);
return param.getOne(baseMapper.selectListRel(param));
}
}

View File

@@ -0,0 +1,47 @@
package com.gxwebsoft.glt.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gxwebsoft.glt.mapper.GltUserTicketReleaseMapper;
import com.gxwebsoft.glt.service.GltUserTicketReleaseService;
import com.gxwebsoft.glt.entity.GltUserTicketRelease;
import com.gxwebsoft.glt.param.GltUserTicketReleaseParam;
import com.gxwebsoft.common.core.web.PageParam;
import com.gxwebsoft.common.core.web.PageResult;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 水票释放Service实现
*
* @author 科技小王子
* @since 2026-02-03 18:55:55
*/
@Service
public class GltUserTicketReleaseServiceImpl extends ServiceImpl<GltUserTicketReleaseMapper, GltUserTicketRelease> implements GltUserTicketReleaseService {
@Override
public PageResult<GltUserTicketRelease> pageRel(GltUserTicketReleaseParam param) {
PageParam<GltUserTicketRelease, GltUserTicketReleaseParam> page = new PageParam<>(param);
page.setDefaultOrder("sort_number asc, create_time desc");
List<GltUserTicketRelease> list = baseMapper.selectPageRel(page, param);
return new PageResult<>(list, page.getTotal());
}
@Override
public List<GltUserTicketRelease> listRel(GltUserTicketReleaseParam param) {
List<GltUserTicketRelease> list = baseMapper.selectListRel(param);
// 排序
PageParam<GltUserTicketRelease, GltUserTicketReleaseParam> page = new PageParam<>();
page.setDefaultOrder("sort_number asc, create_time desc");
return page.sortRecords(list);
}
@Override
public GltUserTicketRelease getByIdRel(Integer id) {
GltUserTicketReleaseParam param = new GltUserTicketReleaseParam();
param.setId(id);
return param.getOne(baseMapper.selectListRel(param));
}
}

View File

@@ -0,0 +1,53 @@
package com.gxwebsoft.glt.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gxwebsoft.glt.mapper.GltUserTicketMapper;
import com.gxwebsoft.glt.service.GltUserTicketService;
import com.gxwebsoft.glt.entity.GltUserTicket;
import com.gxwebsoft.glt.param.GltUserTicketParam;
import com.gxwebsoft.common.core.web.PageParam;
import com.gxwebsoft.common.core.web.PageResult;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 我的水票Service实现
*
* @author 科技小王子
* @since 2026-02-03 18:55:55
*/
@Service
public class GltUserTicketServiceImpl extends ServiceImpl<GltUserTicketMapper, GltUserTicket> implements GltUserTicketService {
@Override
public PageResult<GltUserTicket> pageRel(GltUserTicketParam param) {
PageParam<GltUserTicket, GltUserTicketParam> page = new PageParam<>(param);
page.setDefaultOrder("sort_number asc, create_time desc");
List<GltUserTicket> list = baseMapper.selectPageRel(page, param);
return new PageResult<>(list, page.getTotal());
}
@Override
public List<GltUserTicket> listRel(GltUserTicketParam param) {
List<GltUserTicket> list = baseMapper.selectListRel(param);
// 排序
PageParam<GltUserTicket, GltUserTicketParam> page = new PageParam<>();
page.setDefaultOrder("sort_number asc, create_time desc");
return page.sortRecords(list);
}
@Override
public GltUserTicket getByIdRel(Integer id) {
GltUserTicketParam param = new GltUserTicketParam();
param.setId(id);
return param.getOne(baseMapper.selectListRel(param));
}
@Override
public Integer sumTotalQtyByUserId(Integer userId) {
Integer totalQty = baseMapper.sumTotalQtyByUserId(userId);
return totalQty == null ? 0 : totalQty;
}
}

View File

@@ -0,0 +1,46 @@
package com.gxwebsoft.glt.task;
import com.gxwebsoft.common.core.annotation.IgnoreTenant;
import com.gxwebsoft.glt.service.GltTicketIssueService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* GLT 套票发放任务:
* - 每分钟扫描一次今日订单tenantId=10584, formId=10074, payStatus=1, orderStatus=0
* - 为订单生成用户套票账户 + 释放计划(幂等)
*/
@Slf4j
@Component
@RequiredArgsConstructor
@ConditionalOnProperty(prefix = "glt.ticket.issue10584", name = "enabled", havingValue = "true", matchIfMissing = true)
public class GltTicketIssue10584Task {
private static final int TENANT_ID = 10584;
private static final int FORM_ID = 10074;
private final GltTicketIssueService gltTicketIssueService;
private final AtomicBoolean running = new AtomicBoolean(false);
@Scheduled(cron = "${glt.ticket.issue10584.cron:0 */1 * * * ?}")
@IgnoreTenant("定时任务无登录态,需忽略租户隔离;内部使用 tenantId=10584 精确过滤")
public void run() {
if (!running.compareAndSet(false, true)) {
log.warn("套票发放任务仍在执行中,本轮跳过 - tenantId={}, formId={}", TENANT_ID, FORM_ID);
return;
}
try {
gltTicketIssueService.issueTodayOrders(TENANT_ID, FORM_ID);
} finally {
running.set(false);
}
}
}

View File

@@ -144,7 +144,8 @@ public class HjmCarController extends BaseController {
@Operation(summary = "删除黄家明_车辆管理")
@DeleteMapping("/{id}")
public ApiResult<?> remove(@PathVariable("id") Integer id) {
if (hjmCarService.removeById(id)) {
// 硬删除(物理删除),不走 @TableLogic 逻辑删除
if (hjmCarService.hardRemoveById(id)) {
return success("删除成功");
}
return fail("删除失败");
@@ -177,7 +178,8 @@ public class HjmCarController extends BaseController {
@Operation(summary = "批量删除黄家明_车辆管理")
@DeleteMapping("/batch")
public ApiResult<?> removeBatch(@RequestBody List<Integer> ids) {
if (hjmCarService.removeByIds(ids)) {
// 硬删除(物理删除),不走 @TableLogic 逻辑删除
if (hjmCarService.hardRemoveByIds(ids)) {
return success("删除成功");
}
return fail("删除失败");

View File

@@ -5,8 +5,10 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.gxwebsoft.hjm.entity.HjmCar;
import com.gxwebsoft.hjm.param.HjmCarParam;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Param;
import java.util.Collection;
import java.util.List;
/**
@@ -44,4 +46,23 @@ public interface HjmCarMapper extends BaseMapper<HjmCar> {
@InterceptorIgnore(tenantLine = "true")
HjmCar getByCode(String code);
/**
* 硬删除:绕过 MyBatis-Plus 逻辑删除(@TableLogic
*/
@Delete("DELETE FROM hjm_car WHERE id = #{id}")
int hardDeleteById(@Param("id") Integer id);
/**
* 硬删除:批量删除,绕过 MyBatis-Plus 逻辑删除(@TableLogic
*/
@Delete({
"<script>",
"DELETE FROM hjm_car WHERE id IN",
"<foreach collection='ids' item='id' open='(' separator=',' close=')'>",
"#{id}",
"</foreach>",
"</script>"
})
int hardDeleteBatchIds(@Param("ids") Collection<Integer> ids);
}

View File

@@ -44,4 +44,14 @@ public interface HjmCarService extends IService<HjmCar> {
boolean updateByGpsNo(HjmCar byGpsNo);
HjmCar getByCode(String code);
/**
* 硬删除(物理删除),绕过 @TableLogic 逻辑删除。
*/
boolean hardRemoveById(Integer id);
/**
* 硬删除(物理删除),绕过 @TableLogic 逻辑删除。
*/
boolean hardRemoveByIds(List<Integer> ids);
}

View File

@@ -127,6 +127,22 @@ public class HjmCarServiceImpl extends ServiceImpl<HjmCarMapper, HjmCar> impleme
}
}
@Override
public boolean hardRemoveById(Integer id) {
if (id == null) {
return false;
}
return baseMapper.hardDeleteById(id) > 0;
}
@Override
public boolean hardRemoveByIds(List<Integer> ids) {
if (ids == null || ids.isEmpty()) {
return false;
}
return baseMapper.hardDeleteBatchIds(ids) > 0;
}
/**

View File

@@ -68,6 +68,8 @@ public class WxTransferService {
/**
* 发起单笔“商家转账到零钱”(升级版接口 /v3/fund-app/mch-transfer/transfer-bills
*
* 默认:不需要用户在小程序确认页二次确认(直接受理转账)。
*
* @param tenantId 租户ID用于获取微信支付配置
* @param openid 收款用户openid必须是该appid下的openid
* @param amountYuan 转账金额(单位:元)
@@ -81,6 +83,31 @@ public class WxTransferService {
String outBillNo,
String remark,
String userName) throws PaymentException {
return initiateSingleTransferInternal(tenantId, openid, amountYuan, outBillNo, remark, userName);
}
/**
* 发起单笔“商家转账到零钱(小程序前端拉起收款确认页)”。
*
* 返回的 response.packageInfoJSON 字段 package_info用于小程序端调用 wx.requestMerchantTransfer。
*/
public TransferBillsResponse initiateSingleTransferWithUserConfirm(Integer tenantId,
String openid,
BigDecimal amountYuan,
String outBillNo,
String remark,
String userName) throws PaymentException {
// 注意:微信侧会严格校验请求体字段,传入未定义字段(如 user_confirm会直接 400(PARAM_ERROR)。
// 此处不再向请求体写入 user_confirm仅保留该方法用于兼容调用方若微信侧返回 package_info可供小程序拉起确认页
return initiateSingleTransferInternal(tenantId, openid, amountYuan, outBillNo, remark, userName);
}
private TransferBillsResponse initiateSingleTransferInternal(Integer tenantId,
String openid,
BigDecimal amountYuan,
String outBillNo,
String remark,
String userName) throws PaymentException {
if (tenantId == null) {
throw PaymentException.paramError("租户ID不能为空");
@@ -271,5 +298,7 @@ public class WxTransferService {
private String transferBillNo;
private String createTime;
private String state;
@SerializedName(value = "package_info", alternate = {"packageInfo"})
private String packageInfo;
}
}

View File

@@ -0,0 +1,120 @@
package com.gxwebsoft.shop.controller;
import com.gxwebsoft.common.core.web.BaseController;
import com.gxwebsoft.shop.service.ShopCommunityService;
import com.gxwebsoft.shop.entity.ShopCommunity;
import com.gxwebsoft.shop.param.ShopCommunityParam;
import com.gxwebsoft.common.core.web.ApiResult;
import com.gxwebsoft.common.core.web.PageResult;
import com.gxwebsoft.common.core.web.PageParam;
import com.gxwebsoft.common.core.web.BatchParam;
import com.gxwebsoft.common.core.annotation.OperationLog;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
/**
* 小区控制器
*
* @author 科技小王子
* @since 2026-01-29 20:48:32
*/
@Tag(name = "小区管理")
@RestController
@RequestMapping("/api/shop/shop-community")
public class ShopCommunityController extends BaseController {
@Resource
private ShopCommunityService shopCommunityService;
@Operation(summary = "分页查询小区")
@GetMapping("/page")
public ApiResult<PageResult<ShopCommunity>> page(ShopCommunityParam param) {
// 使用关联查询
return success(shopCommunityService.pageRel(param));
}
@Operation(summary = "查询全部小区")
@GetMapping()
public ApiResult<List<ShopCommunity>> list(ShopCommunityParam param) {
// 使用关联查询
return success(shopCommunityService.listRel(param));
}
@Operation(summary = "根据id查询小区")
@GetMapping("/{id}")
public ApiResult<ShopCommunity> get(@PathVariable("id") Integer id) {
// 使用关联查询
return success(shopCommunityService.getByIdRel(id));
}
@PreAuthorize("hasAuthority('shop:shopCommunity:save')")
@OperationLog
@Operation(summary = "添加小区")
@PostMapping()
public ApiResult<?> save(@RequestBody ShopCommunity shopCommunity) {
if (shopCommunityService.save(shopCommunity)) {
return success("添加成功");
}
return fail("添加失败");
}
@PreAuthorize("hasAuthority('shop:shopCommunity:update')")
@OperationLog
@Operation(summary = "修改小区")
@PutMapping()
public ApiResult<?> update(@RequestBody ShopCommunity shopCommunity) {
if (shopCommunityService.updateById(shopCommunity)) {
return success("修改成功");
}
return fail("修改失败");
}
@PreAuthorize("hasAuthority('shop:shopCommunity:remove')")
@OperationLog
@Operation(summary = "删除小区")
@DeleteMapping("/{id}")
public ApiResult<?> remove(@PathVariable("id") Integer id) {
if (shopCommunityService.removeById(id)) {
return success("删除成功");
}
return fail("删除失败");
}
@PreAuthorize("hasAuthority('shop:shopCommunity:save')")
@OperationLog
@Operation(summary = "批量添加小区")
@PostMapping("/batch")
public ApiResult<?> saveBatch(@RequestBody List<ShopCommunity> list) {
if (shopCommunityService.saveBatch(list)) {
return success("添加成功");
}
return fail("添加失败");
}
@PreAuthorize("hasAuthority('shop:shopCommunity:update')")
@OperationLog
@Operation(summary = "批量修改小区")
@PutMapping("/batch")
public ApiResult<?> removeBatch(@RequestBody BatchParam<ShopCommunity> batchParam) {
if (batchParam.update(shopCommunityService, "id")) {
return success("修改成功");
}
return fail("修改失败");
}
@PreAuthorize("hasAuthority('shop:shopCommunity:remove')")
@OperationLog
@Operation(summary = "批量删除小区")
@DeleteMapping("/batch")
public ApiResult<?> removeBatch(@RequestBody List<Integer> ids) {
if (shopCommunityService.removeByIds(ids)) {
return success("删除成功");
}
return fail("删除失败");
}
}

View File

@@ -18,11 +18,14 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 分销商提现明细表控制器
@@ -70,19 +73,60 @@ public class ShopDealerWithdrawController extends BaseController {
@Operation(summary = "添加分销商提现明细表")
@PostMapping()
public ApiResult<?> save(@RequestBody ShopDealerWithdraw shopDealerWithdraw) {
// 记录当前登录用户id
User loginUser = getLoginUser();
if (loginUser != null) {
shopDealerWithdraw.setUserId(loginUser.getUserId());
final ShopDealerUser dealerUser = shopDealerUserService.getByUserIdRel(loginUser.getUserId());
// 扣除提现金额
dealerUser.setMoney(dealerUser.getMoney().subtract(shopDealerWithdraw.getMoney()));
shopDealerUserService.updateById(dealerUser);
if (shopDealerWithdrawService.save(shopDealerWithdraw)) {
try {
// 记录当前登录用户id
User loginUser = getLoginUser();
if (loginUser == null) {
return fail("未登录或登录已失效");
}
if (shopDealerWithdraw.getMoney() == null || shopDealerWithdraw.getMoney().compareTo(java.math.BigDecimal.ZERO) <= 0) {
return fail("提现金额必须大于0");
}
Integer tenantId = shopDealerWithdraw.getTenantId() != null ? shopDealerWithdraw.getTenantId() : getTenantId();
if (tenantId == null) {
return fail("tenantId为空无法发起提现");
}
shopDealerWithdraw.setTenantId(tenantId);
shopDealerWithdraw.setUserId(loginUser.getUserId());
Integer payType = shopDealerWithdraw.getPayType();
if (payType == null) {
return fail("提现方式不能为空");
}
// 资金安全:用户申请后统一进入“待审核(10)”,审核通过后再由用户主动领取
shopDealerWithdraw.setApplyStatus(10);
shopDealerWithdraw.setAuditTime(null);
final ShopDealerUser dealerUser = shopDealerUserService.getByUserIdRel(loginUser.getUserId());
if (dealerUser == null) {
return fail("分销商信息不存在");
}
if (dealerUser.getMoney() == null || dealerUser.getMoney().compareTo(shopDealerWithdraw.getMoney()) < 0) {
return fail("可提现佣金不足");
}
if (!shopDealerWithdrawService.save(shopDealerWithdraw)) {
return fail("添加失败");
}
// 扣除提现金额
dealerUser.setMoney(dealerUser.getMoney().subtract(shopDealerWithdraw.getMoney()));
if (!shopDealerUserService.updateById(dealerUser)) {
throw PaymentException.systemError("扣减可提现佣金失败", null);
}
return success("添加成功");
}
} catch (PaymentException e) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return fail(e.getMessage());
} catch (Exception e) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return fail("添加失败");
}
return fail("添加失败");
}
@PreAuthorize("hasAuthority('shop:shopDealerWithdraw:update')")
@@ -100,9 +144,18 @@ public class ShopDealerWithdrawController extends BaseController {
return fail("提现记录不存在");
}
// 防御:前端未传字段时,避免被 updateById 更新为 NULL
if (shopDealerWithdraw.getApplyStatus() == null) {
shopDealerWithdraw.setApplyStatus(db.getApplyStatus());
}
if (shopDealerWithdraw.getPayType() == null) {
shopDealerWithdraw.setPayType(db.getPayType());
}
Integer newStatus = shopDealerWithdraw.getApplyStatus() != null ? shopDealerWithdraw.getApplyStatus() : db.getApplyStatus();
Integer oldStatus = db.getApplyStatus();
Integer payType = shopDealerWithdraw.getPayType() != null ? shopDealerWithdraw.getPayType() : db.getPayType();
Integer incomingStatus = shopDealerWithdraw.getApplyStatus();
// 驳回操作状态迁移到30时退回金额避免重复退回
if (Integer.valueOf(30).equals(newStatus) && !Integer.valueOf(30).equals(oldStatus)) {
@@ -113,11 +166,57 @@ public class ShopDealerWithdrawController extends BaseController {
}
}
// 微信收款且审核通过迁移到20时自动发起商家转账到零钱并将状态置为已打款(40)
// 审核通过:仅标记为 20等待用户在提现记录中主动领取微信等在线转账在领取环节执行
if (Integer.valueOf(20).equals(incomingStatus) && !Integer.valueOf(20).equals(oldStatus)) {
shopDealerWithdraw.setAuditTime(LocalDateTime.now());
}
// 微信提现:已打款(40)由用户“领取成功”后置为 40后台不允许直接改为 40
if (Integer.valueOf(10).equals(payType)
&& Integer.valueOf(20).equals(newStatus)
&& !Integer.valueOf(20).equals(oldStatus)
&& Integer.valueOf(40).equals(incomingStatus)
&& !Integer.valueOf(40).equals(oldStatus)) {
return fail("微信提现请用户在提现记录中领取后自动完成,后台不可直接置为已打款");
}
// 已打款:非微信自动转账的场景,要求上传凭证
if (Integer.valueOf(40).equals(incomingStatus) && !Integer.valueOf(40).equals(oldStatus)) {
shopDealerWithdraw.setAuditTime(LocalDateTime.now());
if (!Integer.valueOf(10).equals(payType) && StrUtil.isBlankIfStr(shopDealerWithdraw.getImage())) {
return fail("请上传打款凭证");
}
}
if (shopDealerWithdrawService.updateById(shopDealerWithdraw)) {
return success("修改成功");
}
return fail("修改失败");
}
@PreAuthorize("hasAuthority('shop:shopDealerWithdraw:update')")
@Operation(summary = "用户领取提现(审核通过后,返回微信收款确认页 package_info")
@PostMapping("/receive/{id}")
public ApiResult<?> receive(@PathVariable("id") Integer id) {
try {
User loginUser = getLoginUser();
if (loginUser == null) {
return fail("未登录或登录已失效");
}
if (id == null) {
return fail("id不能为空");
}
ShopDealerWithdraw db = shopDealerWithdrawService.getByIdRel(id);
if (db == null) {
return fail("提现记录不存在");
}
if (!loginUser.getUserId().equals(db.getUserId())) {
return fail("无权领取该提现记录");
}
if (!Integer.valueOf(20).equals(db.getApplyStatus())) {
return fail("当前状态不可领取");
}
if (!Integer.valueOf(10).equals(db.getPayType())) {
return fail("仅微信提现支持在线领取");
}
Integer tenantId = db.getTenantId() != null ? db.getTenantId() : getTenantId();
if (tenantId == null) {
@@ -133,43 +232,72 @@ public class ShopDealerWithdrawController extends BaseController {
}
}
if (StrUtil.isBlank(openid)) {
return fail("用户openid为空无法起微信转账");
return fail("用户openid为空无法起微信收款确认页");
}
// 微信支付商家转账接口对商户单号通常有最小长度限制(当前实测为 >= 5
// 使用 0 填充,避免如 "WD59" 这种过短导致 PARAM_ERROR。
// 使用提现记录ID构造单号保持幂等微信要求 5-32 且仅字母/数字
String outBillNo = String.format("WD%03d", db.getId());
String remark = "分销商提现";
String userName = db.getRealName();
try {
wxTransferService.initiateSingleTransfer(
tenantId,
openid,
db.getMoney(),
outBillNo,
remark,
userName
);
} catch (PaymentException e) {
return fail(e.getMessage());
WxTransferService.TransferBillsResponse resp = wxTransferService.initiateSingleTransferWithUserConfirm(
tenantId,
openid,
db.getMoney(),
outBillNo,
remark,
userName
);
if (resp == null || StrUtil.isBlank(resp.getPackageInfo())) {
return fail("后台未返回 package_info无法调起微信收款确认页");
}
shopDealerWithdraw.setApplyStatus(40);
shopDealerWithdraw.setAuditTime(LocalDateTime.now());
Map<String, Object> data = new HashMap<>();
data.put("package_info", resp.getPackageInfo());
return success("领取发起成功", data);
} catch (PaymentException e) {
return fail(e.getMessage());
} catch (Exception e) {
return fail("领取失败");
}
}
@Operation(summary = "用户领取成功回调(将状态置为已打款/已领取40")
@PostMapping("/receive-success/{id}")
public ApiResult<?> receiveSuccess(@PathVariable("id") Integer id) {
User loginUser = getLoginUser();
if (loginUser == null) {
return fail("未登录或登录已失效");
}
if (id == null) {
return fail("id不能为空");
}
// 已打款:非微信自动转账的场景,要求上传凭证
if (Integer.valueOf(40).equals(newStatus)) {
shopDealerWithdraw.setAuditTime(LocalDateTime.now());
if (!Integer.valueOf(10).equals(payType) && StrUtil.isBlankIfStr(shopDealerWithdraw.getImage())) {
return fail("请上传打款凭证");
}
ShopDealerWithdraw db = shopDealerWithdrawService.getByIdRel(id);
if (db == null) {
return fail("提现记录不存在");
}
if (shopDealerWithdrawService.updateById(shopDealerWithdraw)) {
return success("修改成功");
if (!loginUser.getUserId().equals(db.getUserId())) {
return fail("无权操作该提现记录");
}
return fail("修改失败");
if (!Integer.valueOf(10).equals(db.getPayType())) {
return fail("仅微信提现支持在线领取");
}
if (Integer.valueOf(40).equals(db.getApplyStatus())) {
return success("已领取");
}
if (!Integer.valueOf(20).equals(db.getApplyStatus())) {
return fail("当前状态不可确认领取");
}
ShopDealerWithdraw upd = new ShopDealerWithdraw();
upd.setId(db.getId());
upd.setApplyStatus(40);
upd.setAuditTime(LocalDateTime.now());
if (shopDealerWithdrawService.updateById(upd)) {
return success("领取成功");
}
return fail("领取失败");
}
@PreAuthorize("hasAuthority('shop:shopDealerWithdraw:remove')")

View File

@@ -19,6 +19,7 @@ import com.gxwebsoft.shop.task.OrderAutoCancelTask;
import com.gxwebsoft.shop.entity.ShopOrder;
import com.gxwebsoft.shop.param.ShopOrderParam;
import com.gxwebsoft.shop.dto.OrderCreateRequest;
import com.gxwebsoft.shop.dto.OrderPrepayRequest;
import com.gxwebsoft.shop.dto.UpdatePaymentStatusRequest;
import com.gxwebsoft.payment.service.PaymentService;
import com.gxwebsoft.payment.dto.PaymentResponse;
@@ -48,6 +49,7 @@ import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* 订单控制器
@@ -169,20 +171,109 @@ public class ShopOrderController extends BaseController {
return fail("添加失败");
}
@Operation(summary = "发起支付/重新支付(兼容 pay/prepay/repay")
@PostMapping({"/pay", "/prepay", "/repay"})
public ApiResult<?> prepay(@RequestBody OrderPrepayRequest request) {
User loginUser = getLoginUser();
if (loginUser == null) {
return fail("用户未登录");
}
// 允许从请求显式传 tenantId兼容一些历史调用否则优先从请求头/登录态推断
Integer tenantId = request != null ? request.getTenantId() : null;
if (tenantId == null) {
tenantId = ObjectUtil.defaultIfNull(getTenantId(), loginUser.getTenantId());
}
if (request == null || (request.getOrderId() == null && StrUtil.isBlank(request.getOrderNo()))) {
return fail("orderId 或 orderNo 不能为空");
}
ShopOrder order;
if (request.getOrderId() != null) {
order = shopOrderService.getById(request.getOrderId());
} else {
if (tenantId == null) {
return fail("tenantId 不能为空");
}
order = shopOrderService.getByOrderNo(request.getOrderNo(), tenantId);
}
if (order == null) {
return fail("订单不存在");
}
// 校验租户(避免用别的租户订单号撞库)
if (tenantId != null && order.getTenantId() != null && !tenantId.equals(order.getTenantId())) {
return fail("订单不存在");
}
// 普通用户只能操作自己的订单;管理员可越权(复用取消权限判断即可)
if (!loginUser.getUserId().equals(order.getUserId()) && !hasOrderCancelAuthority()) {
return fail("无权限操作此订单");
}
// 业务状态校验这些错误需要明确返回code!=0避免前端误判为接口不支持而降级走创建订单
if (Boolean.TRUE.equals(order.getPayStatus())) {
return fail("订单已支付");
}
if (order.getDeleted() != null && order.getDeleted() == 1) {
return fail("订单已删除");
}
if (order.getOrderStatus() != null) {
// 2=已取消6=退款成功7=客户端申请退款其他非0状态也视为不可再次发起支付
if (!Objects.equals(order.getOrderStatus(), 0)) {
return fail("订单状态不允许发起支付");
}
}
if (order.getExpirationTime() != null && order.getExpirationTime().isBefore(LocalDateTime.now())) {
return fail("订单已过期");
}
// 补齐 createWxOrder 所需字段注意openid 在 ShopOrder 上是非持久化字段,查询出来为空)
Integer payType = request.getPayType() != null ? request.getPayType() : order.getPayType();
if (payType == null) {
payType = 1;
}
if (!Objects.equals(payType, 1) && !Objects.equals(payType, 102)) {
return fail("该订单不支持发起微信支付");
}
order.setPayType(payType);
order.setPayUserId(loginUser.getUserId());
order.setOpenid(loginUser.getOpenid());
if (order.getPayPrice() == null) {
order.setPayPrice(ObjectUtil.defaultIfNull(order.getTotalPrice(), BigDecimal.ZERO));
}
if (StrUtil.isBlank(order.getComments())) {
order.setComments("订单支付");
}
try {
Map<String, String> wxOrderInfo = shopOrderService.createWxOrder(order);
return success(wxOrderInfo);
} catch (Exception e) {
logger.error("发起支付失败 - userId={}, orderId={}, orderNo={}",
loginUser.getUserId(), order.getOrderId(), order.getOrderNo(), e);
return fail("发起支付失败:" + e.getMessage());
}
}
@PreAuthorize("hasAuthority('shop:shopOrder:update')")
@Operation(summary = "修改订单")
@PutMapping()
public ApiResult<?> update(@RequestBody ShopOrder shopOrder) throws Exception {
// 1. 验证订单是否可以退款
if (shopOrder == null) {
return fail("订单不存在");
return fail("订单不存在");
}
// 退款相关操作单独走退款接口,便于做财务权限隔离
if (Objects.equals(shopOrder.getOrderStatus(), 4) || Objects.equals(shopOrder.getOrderStatus(), 6)) {
return fail("退款相关操作请使用退款接口: PUT /api/shop/shop-order/refund");
}
ShopOrder shopOrderNow = shopOrderService.getById(shopOrder.getOrderId());
// 申请退款
if (shopOrder.getOrderStatus().equals(4)) {
shopOrder.setRefundApplyTime(LocalDateTime.now());
if (shopOrderNow == null) {
return fail("订单不存在");
}
if (shopOrderNow.getDeliveryStatus().equals(10) && shopOrder.getDeliveryStatus().equals(20)) {
// 发货状态从“未发货(10)”变更为“已发货(20)”时,记录发货信息
if (Objects.equals(shopOrderNow.getDeliveryStatus(), 10) && Objects.equals(shopOrder.getDeliveryStatus(), 20)) {
ShopOrderDelivery shopOrderDelivery = new ShopOrderDelivery();
shopOrderDelivery.setOrderId(shopOrder.getOrderId());
shopOrderDelivery.setDeliveryMethod(30);
@@ -195,113 +286,118 @@ public class ShopOrderController extends BaseController {
shopOrderDeliveryService.setExpress(getLoginUser(), shopOrderDelivery, shopOrder);
}
// 退款操作
if(shopOrder.getOrderStatus().equals(6)){
// 当订单状态更改为6已退款执行退款操作
try {
// 检查订单是否已支付
if (!shopOrder.getPayStatus()) {
return fail("订单未支付,无法退款");
}
// 检查是否已经退款过了
if (StrUtil.isNotBlank(shopOrderNow.getRefundOrder())) {
logger.warn("订单已经退款过,订单号: {}, 退款单号: {}", shopOrderNow.getOrderNo(), shopOrderNow.getRefundOrder());
return fail("订单已退款,请勿重复操作");
}
// 2. 生成退款单号
String refundNo = "RF" + IdUtil.getSnowflakeNextId();
// 3. 确定退款金额(默认全额退款)
BigDecimal refundAmount = shopOrder.getRefundMoney();
if (refundAmount == null || refundAmount.compareTo(BigDecimal.ZERO) <= 0) {
// 如果没有指定退款金额,使用订单实付金额
refundAmount = shopOrderNow.getTotalPrice();
}
// 验证退款金额不能大于订单金额
if (refundAmount.compareTo(shopOrderNow.getTotalPrice()) > 0) {
return fail("退款金额不能大于订单金额");
}
// 4. 确定支付类型默认为微信Native支付
PaymentType paymentType = PaymentType.WECHAT_NATIVE;
if (shopOrderNow.getPayType() != null) {
// 根据订单的支付类型确定
// 支付方式0余额支付1微信支付2支付宝支付3银联支付4现金支付5POS机支付6免费7积分支付
paymentType = PaymentType.getByCode(shopOrderNow.getPayType());
// 如果是微信支付,需要根据微信支付子类型确定具体的支付方式
if (paymentType == PaymentType.WECHAT) {
// 目前统一使用WECHAT_NATIVE进行退款
paymentType = PaymentType.WECHAT_NATIVE;
}
}
// 5. 调用统一支付服务的退款接口
logger.info("开始处理订单退款 - 订单号: {}, 退款单号: {}, 退款金额: {}, 支付方式: {}",
shopOrderNow.getOrderNo(), refundNo, refundAmount, paymentType);
PaymentResponse refundResponse = paymentService.refund(
shopOrderNow.getOrderNo(), // 原订单号
refundNo, // 退款单号
paymentType, // 支付方式
shopOrderNow.getTotalPrice(), // 订单总金额
refundAmount, // 退款金额
shopOrder.getRefundReason() != null ? shopOrder.getRefundReason() : "用户申请退款", // 退款原因
shopOrderNow.getTenantId() // 租户ID
);
// 6. 处理退款结果
if (refundResponse.getSuccess()) {
// 退款成功,更新订单信息
shopOrder.setRefundOrder(refundNo);
shopOrder.setRefundMoney(refundAmount);
shopOrder.setRefundTime(LocalDateTime.now());
shopOrder.setOrderStatus(6); // 退款成功
// 根据退款状态决定订单状态
// 如果微信返回退款处理中则设置订单状态为5退款处理中
// 如果微信返回退款成功则保持状态为6退款成功
// if (refundResponse.getPaymentStatus() != null) {
// switch (refundResponse.getPaymentStatus()) {
// case REFUNDING:
// shopOrder.setOrderStatus(5); // 退款处理中
// logger.info("订单退款处理中,订单号: {}, 退款单号: {}", shopOrderNow.getOrderNo(), refundNo);
// break;
// case REFUNDED:
// shopOrder.setOrderStatus(6); // 退款成功
// logger.info("订单退款成功,订单号: {}, 退款单号: {}", shopOrderNow.getOrderNo(), refundNo);
// break;
// case REFUND_FAILED:
// logger.error("订单退款失败,订单号: {}, 退款单号: {}", shopOrderNow.getOrderNo(), refundNo);
// return fail("退款失败,请联系管理员");
// default:
// shopOrder.setOrderStatus(5); // 默认为退款处理中
// }
// }
logger.info("订单退款请求成功 - 订单号: {}, 退款单号: {}, 微信退款单号: {}",
shopOrderNow.getOrderNo(), refundNo, refundResponse.getTransactionId());
} else {
// 退款失败
logger.error("订单退款失败 - 订单号: {}, 错误: {}", shopOrderNow.getOrderNo(), refundResponse.getErrorMessage());
return fail("退款失败: " + refundResponse.getErrorMessage());
}
} catch (Exception e) {
logger.error("处理订单退款异常 - 订单号: {}, 错误: {}", shopOrderNow.getOrderNo(), e.getMessage(), e);
return fail("退款处理异常: " + e.getMessage());
}
}
if (shopOrderService.updateById(shopOrder)) {
return success("修改成功");
}
return fail("修改失败");
}
@PreAuthorize("hasAuthority('shop:shopOrder:refund')")
@Operation(summary = "订单退款操作(申请退款/同意退款)", description = "orderStatus=4 申请退款orderStatus=6 同意退款并发起原路退款")
@PutMapping("/refund")
public ApiResult<?> refund(@RequestBody ShopOrder req) {
if (req == null || req.getOrderId() == null || req.getOrderStatus() == null) {
return fail("orderId 和 orderStatus 不能为空");
}
if (!Objects.equals(req.getOrderStatus(), 4) && !Objects.equals(req.getOrderStatus(), 6)) {
return fail("orderStatus 仅支持 4(申请退款) 或 6(同意退款)");
}
ShopOrder current = shopOrderService.getById(req.getOrderId());
if (current == null) {
return fail("订单不存在");
}
// 申请退款:只记录申请时间/原因/金额(如有)
if (Objects.equals(req.getOrderStatus(), 4)) {
ShopOrder patch = new ShopOrder();
patch.setOrderId(req.getOrderId());
patch.setOrderStatus(4);
patch.setRefundApplyTime(LocalDateTime.now());
if (StrUtil.isNotBlank(req.getRefundReason())) {
patch.setRefundReason(req.getRefundReason());
}
if (req.getRefundMoney() != null) {
patch.setRefundMoney(req.getRefundMoney());
}
if (shopOrderService.updateById(patch)) {
return success("申请退款成功");
}
return fail("申请退款失败");
}
// 同意退款:发起原路退款并更新退款信息
try {
if (!Boolean.TRUE.equals(current.getPayStatus())) {
return fail("订单未支付,无法退款");
}
if (StrUtil.isNotBlank(current.getRefundOrder())) {
logger.warn("订单已经退款过,订单号: {}, 退款单号: {}", current.getOrderNo(), current.getRefundOrder());
return fail("订单已退款,请勿重复操作");
}
String refundNo = "RF" + IdUtil.getSnowflakeNextId();
BigDecimal refundAmount = req.getRefundMoney();
if (refundAmount == null || refundAmount.compareTo(BigDecimal.ZERO) <= 0) {
refundAmount = current.getTotalPrice();
}
if (refundAmount.compareTo(current.getTotalPrice()) > 0) {
return fail("退款金额不能大于订单金额");
}
PaymentType paymentType = PaymentType.WECHAT_NATIVE;
if (current.getPayType() != null) {
// 支付方式0余额支付1微信支付2支付宝支付3银联支付4现金支付5POS机支付6免费7积分支付
paymentType = PaymentType.getByCode(current.getPayType());
if (paymentType == PaymentType.WECHAT) {
paymentType = PaymentType.WECHAT_NATIVE;
}
}
logger.info("开始处理订单退款 - 订单号: {}, 退款单号: {}, 退款金额: {}, 支付方式: {}",
current.getOrderNo(), refundNo, refundAmount, paymentType);
PaymentResponse refundResponse = paymentService.refund(
current.getOrderNo(),
refundNo,
paymentType,
current.getTotalPrice(),
refundAmount,
StrUtil.isNotBlank(req.getRefundReason()) ? req.getRefundReason() : "用户申请退款",
current.getTenantId()
);
if (!Boolean.TRUE.equals(refundResponse.getSuccess())) {
logger.error("订单退款失败 - 订单号: {}, 错误: {}", current.getOrderNo(), refundResponse.getErrorMessage());
return fail("退款失败: " + refundResponse.getErrorMessage());
}
ShopOrder patch = new ShopOrder();
patch.setOrderId(req.getOrderId());
patch.setRefundOrder(refundNo);
patch.setRefundMoney(refundAmount);
patch.setRefundTime(LocalDateTime.now());
patch.setOrderStatus(6);
if (StrUtil.isNotBlank(req.getRefundReason())) {
patch.setRefundReason(req.getRefundReason());
}
if (!shopOrderService.updateById(patch)) {
logger.error("退款已成功但订单更新失败 - orderId={}, orderNo={}, refundNo={}", current.getOrderId(), current.getOrderNo(), refundNo);
return fail("退款成功,但订单状态更新失败,请联系管理员");
}
logger.info("订单退款请求成功 - 订单号: {}, 退款单号: {}, 微信退款单号: {}",
current.getOrderNo(), refundNo, refundResponse.getTransactionId());
return success("退款成功");
} catch (Exception e) {
logger.error("处理订单退款异常 - 订单号: {}, 错误: {}", current.getOrderNo(), e.getMessage(), e);
return fail("退款处理异常: " + e.getMessage());
}
}
@Operation(summary = "删除订单")
@DeleteMapping("/{id}")
public ApiResult<?> remove(@PathVariable("id") Integer id) {
@@ -323,6 +419,13 @@ public class ShopOrderController extends BaseController {
@Operation(summary = "批量修改订单")
@PutMapping("/batch")
public ApiResult<?> removeBatch(@RequestBody BatchParam<ShopOrder> batchParam) {
if (batchParam != null && batchParam.getData() != null) {
Integer status = batchParam.getData().getOrderStatus();
// 退款相关操作单独走退款接口,避免绕过财务权限
if (Objects.equals(status, 4) || Objects.equals(status, 6)) {
return fail("退款相关操作请使用退款接口: PUT /api/shop/shop-order/refund");
}
}
if (batchParam.update(shopOrderService, "order_id")) {
return success("修改成功");
}

View File

@@ -0,0 +1,125 @@
package com.gxwebsoft.shop.controller;
import com.gxwebsoft.common.core.web.BaseController;
import com.gxwebsoft.shop.service.ShopStoreService;
import com.gxwebsoft.shop.entity.ShopStore;
import com.gxwebsoft.shop.param.ShopStoreParam;
import com.gxwebsoft.common.core.web.ApiResult;
import com.gxwebsoft.common.core.web.PageResult;
import com.gxwebsoft.common.core.web.PageParam;
import com.gxwebsoft.common.core.web.BatchParam;
import com.gxwebsoft.common.core.annotation.OperationLog;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
/**
* 门店控制器
*
* @author 科技小王子
* @since 2026-01-30 15:00:25
*/
@Tag(name = "门店管理")
@RestController
@RequestMapping("/api/shop/shop-store")
public class ShopStoreController extends BaseController {
@Resource
private ShopStoreService shopStoreService;
@Operation(summary = "分页查询门店")
@GetMapping("/page")
public ApiResult<PageResult<ShopStore>> page(ShopStoreParam param) {
// 使用关联查询
return success(shopStoreService.pageRel(param));
}
@Operation(summary = "查询全部门店")
@GetMapping()
public ApiResult<List<ShopStore>> list(ShopStoreParam param) {
// 使用关联查询
return success(shopStoreService.listRel(param));
}
@Operation(summary = "根据id查询门店")
@GetMapping("/{id}")
public ApiResult<ShopStore> get(@PathVariable("id") Integer id) {
// 使用关联查询
return success(shopStoreService.getByIdRel(id));
}
@PreAuthorize("hasAuthority('shop:shopStore:save')")
@OperationLog
@Operation(summary = "添加门店")
@PostMapping()
public ApiResult<?> save(@RequestBody ShopStore shopStore) {
// 记录当前登录用户id
// User loginUser = getLoginUser();
// if (loginUser != null) {
// shopStore.setUserId(loginUser.getUserId());
// }
if (shopStoreService.save(shopStore)) {
return success("添加成功");
}
return fail("添加失败");
}
@PreAuthorize("hasAuthority('shop:shopStore:update')")
@OperationLog
@Operation(summary = "修改门店")
@PutMapping()
public ApiResult<?> update(@RequestBody ShopStore shopStore) {
if (shopStoreService.updateById(shopStore)) {
return success("修改成功");
}
return fail("修改失败");
}
@PreAuthorize("hasAuthority('shop:shopStore:remove')")
@OperationLog
@Operation(summary = "删除门店")
@DeleteMapping("/{id}")
public ApiResult<?> remove(@PathVariable("id") Integer id) {
if (shopStoreService.removeById(id)) {
return success("删除成功");
}
return fail("删除失败");
}
@PreAuthorize("hasAuthority('shop:shopStore:save')")
@OperationLog
@Operation(summary = "批量添加门店")
@PostMapping("/batch")
public ApiResult<?> saveBatch(@RequestBody List<ShopStore> list) {
if (shopStoreService.saveBatch(list)) {
return success("添加成功");
}
return fail("添加失败");
}
@PreAuthorize("hasAuthority('shop:shopStore:update')")
@OperationLog
@Operation(summary = "批量修改门店")
@PutMapping("/batch")
public ApiResult<?> removeBatch(@RequestBody BatchParam<ShopStore> batchParam) {
if (batchParam.update(shopStoreService, "id")) {
return success("修改成功");
}
return fail("修改失败");
}
@PreAuthorize("hasAuthority('shop:shopStore:remove')")
@OperationLog
@Operation(summary = "批量删除门店")
@DeleteMapping("/batch")
public ApiResult<?> removeBatch(@RequestBody List<Integer> ids) {
if (shopStoreService.removeByIds(ids)) {
return success("删除成功");
}
return fail("删除失败");
}
}

View File

@@ -0,0 +1,125 @@
package com.gxwebsoft.shop.controller;
import com.gxwebsoft.common.core.web.BaseController;
import com.gxwebsoft.shop.service.ShopStoreRiderService;
import com.gxwebsoft.shop.entity.ShopStoreRider;
import com.gxwebsoft.shop.param.ShopStoreRiderParam;
import com.gxwebsoft.common.core.web.ApiResult;
import com.gxwebsoft.common.core.web.PageResult;
import com.gxwebsoft.common.core.web.PageParam;
import com.gxwebsoft.common.core.web.BatchParam;
import com.gxwebsoft.common.core.annotation.OperationLog;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
/**
* 配送员控制器
*
* @author 科技小王子
* @since 2026-01-30 15:14:16
*/
@Tag(name = "配送员管理")
@RestController
@RequestMapping("/api/shop/shop-store-rider")
public class ShopStoreRiderController extends BaseController {
@Resource
private ShopStoreRiderService shopStoreRiderService;
@Operation(summary = "分页查询配送员")
@GetMapping("/page")
public ApiResult<PageResult<ShopStoreRider>> page(ShopStoreRiderParam param) {
// 使用关联查询
return success(shopStoreRiderService.pageRel(param));
}
@Operation(summary = "查询全部配送员")
@GetMapping()
public ApiResult<List<ShopStoreRider>> list(ShopStoreRiderParam param) {
// 使用关联查询
return success(shopStoreRiderService.listRel(param));
}
@Operation(summary = "根据id查询配送员")
@GetMapping("/{id}")
public ApiResult<ShopStoreRider> get(@PathVariable("id") Long id) {
// 使用关联查询
return success(shopStoreRiderService.getByIdRel(id));
}
@PreAuthorize("hasAuthority('shop:shopStoreRider:save')")
@OperationLog
@Operation(summary = "添加配送员")
@PostMapping()
public ApiResult<?> save(@RequestBody ShopStoreRider shopStoreRider) {
// 记录当前登录用户id
// User loginUser = getLoginUser();
// if (loginUser != null) {
// shopStoreRider.setUserId(loginUser.getUserId());
// }
if (shopStoreRiderService.save(shopStoreRider)) {
return success("添加成功");
}
return fail("添加失败");
}
@PreAuthorize("hasAuthority('shop:shopStoreRider:update')")
@OperationLog
@Operation(summary = "修改配送员")
@PutMapping()
public ApiResult<?> update(@RequestBody ShopStoreRider shopStoreRider) {
if (shopStoreRiderService.updateById(shopStoreRider)) {
return success("修改成功");
}
return fail("修改失败");
}
@PreAuthorize("hasAuthority('shop:shopStoreRider:remove')")
@OperationLog
@Operation(summary = "删除配送员")
@DeleteMapping("/{id}")
public ApiResult<?> remove(@PathVariable("id") Integer id) {
if (shopStoreRiderService.removeById(id)) {
return success("删除成功");
}
return fail("删除失败");
}
@PreAuthorize("hasAuthority('shop:shopStoreRider:save')")
@OperationLog
@Operation(summary = "批量添加配送员")
@PostMapping("/batch")
public ApiResult<?> saveBatch(@RequestBody List<ShopStoreRider> list) {
if (shopStoreRiderService.saveBatch(list)) {
return success("添加成功");
}
return fail("添加失败");
}
@PreAuthorize("hasAuthority('shop:shopStoreRider:update')")
@OperationLog
@Operation(summary = "批量修改配送员")
@PutMapping("/batch")
public ApiResult<?> removeBatch(@RequestBody BatchParam<ShopStoreRider> batchParam) {
if (batchParam.update(shopStoreRiderService, "id")) {
return success("修改成功");
}
return fail("修改失败");
}
@PreAuthorize("hasAuthority('shop:shopStoreRider:remove')")
@OperationLog
@Operation(summary = "批量删除配送员")
@DeleteMapping("/batch")
public ApiResult<?> removeBatch(@RequestBody List<Integer> ids) {
if (shopStoreRiderService.removeByIds(ids)) {
return success("删除成功");
}
return fail("删除失败");
}
}

View File

@@ -0,0 +1,126 @@
package com.gxwebsoft.shop.controller;
import com.gxwebsoft.common.core.web.BaseController;
import com.gxwebsoft.shop.service.ShopStoreUserService;
import com.gxwebsoft.shop.entity.ShopStoreUser;
import com.gxwebsoft.shop.param.ShopStoreUserParam;
import com.gxwebsoft.common.core.web.ApiResult;
import com.gxwebsoft.common.core.web.PageResult;
import com.gxwebsoft.common.core.web.PageParam;
import com.gxwebsoft.common.core.web.BatchParam;
import com.gxwebsoft.common.core.annotation.OperationLog;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
/**
* 店员控制器
*
* @author 科技小王子
* @since 2026-01-30 15:00:25
*/
@Tag(name = "店员管理")
@RestController
@RequestMapping("/api/shop/shop-store-user")
public class ShopStoreUserController extends BaseController {
@Resource
private ShopStoreUserService shopStoreUserService;
@Operation(summary = "分页查询店员")
@GetMapping("/page")
public ApiResult<PageResult<ShopStoreUser>> page(ShopStoreUserParam param) {
// 使用关联查询
return success(shopStoreUserService.pageRel(param));
}
@PreAuthorize("hasAuthority('shop:shopStoreUser:list')")
@Operation(summary = "查询全部店员")
@GetMapping()
public ApiResult<List<ShopStoreUser>> list(ShopStoreUserParam param) {
// 使用关联查询
return success(shopStoreUserService.listRel(param));
}
@Operation(summary = "根据id查询店员")
@GetMapping("/{id}")
public ApiResult<ShopStoreUser> get(@PathVariable("id") Integer id) {
// 使用关联查询
return success(shopStoreUserService.getByIdRel(id));
}
@PreAuthorize("hasAuthority('shop:shopStoreUser:save')")
@OperationLog
@Operation(summary = "添加店员")
@PostMapping()
public ApiResult<?> save(@RequestBody ShopStoreUser shopStoreUser) {
// 记录当前登录用户id
// User loginUser = getLoginUser();
// if (loginUser != null) {
// shopStoreUser.setUserId(loginUser.getUserId());
// }
if (shopStoreUserService.save(shopStoreUser)) {
return success("添加成功");
}
return fail("添加失败");
}
@PreAuthorize("hasAuthority('shop:shopStoreUser:update')")
@OperationLog
@Operation(summary = "修改店员")
@PutMapping()
public ApiResult<?> update(@RequestBody ShopStoreUser shopStoreUser) {
if (shopStoreUserService.updateById(shopStoreUser)) {
return success("修改成功");
}
return fail("修改失败");
}
@PreAuthorize("hasAuthority('shop:shopStoreUser:remove')")
@OperationLog
@Operation(summary = "删除店员")
@DeleteMapping("/{id}")
public ApiResult<?> remove(@PathVariable("id") Integer id) {
if (shopStoreUserService.removeById(id)) {
return success("删除成功");
}
return fail("删除失败");
}
@PreAuthorize("hasAuthority('shop:shopStoreUser:save')")
@OperationLog
@Operation(summary = "批量添加店员")
@PostMapping("/batch")
public ApiResult<?> saveBatch(@RequestBody List<ShopStoreUser> list) {
if (shopStoreUserService.saveBatch(list)) {
return success("添加成功");
}
return fail("添加失败");
}
@PreAuthorize("hasAuthority('shop:shopStoreUser:update')")
@OperationLog
@Operation(summary = "批量修改店员")
@PutMapping("/batch")
public ApiResult<?> removeBatch(@RequestBody BatchParam<ShopStoreUser> batchParam) {
if (batchParam.update(shopStoreUserService, "id")) {
return success("修改成功");
}
return fail("修改失败");
}
@PreAuthorize("hasAuthority('shop:shopStoreUser:remove')")
@OperationLog
@Operation(summary = "批量删除店员")
@DeleteMapping("/batch")
public ApiResult<?> removeBatch(@RequestBody List<Integer> ids) {
if (shopStoreUserService.removeByIds(ids)) {
return success("删除成功");
}
return fail("删除失败");
}
}

View File

@@ -0,0 +1,125 @@
package com.gxwebsoft.shop.controller;
import com.gxwebsoft.common.core.web.BaseController;
import com.gxwebsoft.shop.service.ShopWarehouseService;
import com.gxwebsoft.shop.entity.ShopWarehouse;
import com.gxwebsoft.shop.param.ShopWarehouseParam;
import com.gxwebsoft.common.core.web.ApiResult;
import com.gxwebsoft.common.core.web.PageResult;
import com.gxwebsoft.common.core.web.PageParam;
import com.gxwebsoft.common.core.web.BatchParam;
import com.gxwebsoft.common.core.annotation.OperationLog;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
/**
* 仓库控制器
*
* @author 科技小王子
* @since 2026-01-30 17:46:48
*/
@Tag(name = "仓库管理")
@RestController
@RequestMapping("/api/shop/shop-warehouse")
public class ShopWarehouseController extends BaseController {
@Resource
private ShopWarehouseService shopWarehouseService;
@Operation(summary = "分页查询仓库")
@GetMapping("/page")
public ApiResult<PageResult<ShopWarehouse>> page(ShopWarehouseParam param) {
// 使用关联查询
return success(shopWarehouseService.pageRel(param));
}
@Operation(summary = "查询全部仓库")
@GetMapping()
public ApiResult<List<ShopWarehouse>> list(ShopWarehouseParam param) {
// 使用关联查询
return success(shopWarehouseService.listRel(param));
}
@Operation(summary = "根据id查询仓库")
@GetMapping("/{id}")
public ApiResult<ShopWarehouse> get(@PathVariable("id") Integer id) {
// 使用关联查询
return success(shopWarehouseService.getByIdRel(id));
}
@PreAuthorize("hasAuthority('shop:shopWarehouse:save')")
@OperationLog
@Operation(summary = "添加仓库")
@PostMapping()
public ApiResult<?> save(@RequestBody ShopWarehouse shopWarehouse) {
// 记录当前登录用户id
// User loginUser = getLoginUser();
// if (loginUser != null) {
// shopWarehouse.setUserId(loginUser.getUserId());
// }
if (shopWarehouseService.save(shopWarehouse)) {
return success("添加成功");
}
return fail("添加失败");
}
@PreAuthorize("hasAuthority('shop:shopWarehouse:update')")
@OperationLog
@Operation(summary = "修改仓库")
@PutMapping()
public ApiResult<?> update(@RequestBody ShopWarehouse shopWarehouse) {
if (shopWarehouseService.updateById(shopWarehouse)) {
return success("修改成功");
}
return fail("修改失败");
}
@PreAuthorize("hasAuthority('shop:shopWarehouse:remove')")
@OperationLog
@Operation(summary = "删除仓库")
@DeleteMapping("/{id}")
public ApiResult<?> remove(@PathVariable("id") Integer id) {
if (shopWarehouseService.removeById(id)) {
return success("删除成功");
}
return fail("删除失败");
}
@PreAuthorize("hasAuthority('shop:shopWarehouse:save')")
@OperationLog
@Operation(summary = "批量添加仓库")
@PostMapping("/batch")
public ApiResult<?> saveBatch(@RequestBody List<ShopWarehouse> list) {
if (shopWarehouseService.saveBatch(list)) {
return success("添加成功");
}
return fail("添加失败");
}
@PreAuthorize("hasAuthority('shop:shopWarehouse:update')")
@OperationLog
@Operation(summary = "批量修改仓库")
@PutMapping("/batch")
public ApiResult<?> removeBatch(@RequestBody BatchParam<ShopWarehouse> batchParam) {
if (batchParam.update(shopWarehouseService, "id")) {
return success("修改成功");
}
return fail("修改失败");
}
@PreAuthorize("hasAuthority('shop:shopWarehouse:remove')")
@OperationLog
@Operation(summary = "批量删除仓库")
@DeleteMapping("/batch")
public ApiResult<?> removeBatch(@RequestBody List<Integer> ids) {
if (shopWarehouseService.removeByIds(ids)) {
return success("删除成功");
}
return fail("删除失败");
}
}

View File

@@ -46,6 +46,9 @@ public class OrderCreateRequest {
@Schema(description = "商户编号")
private String merchantCode;
@Schema(description = "店铺ID")
private Integer storeId;
@Schema(description = "使用的优惠券id")
private Integer couponId;

View File

@@ -0,0 +1,32 @@
package com.gxwebsoft.shop.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.Positive;
/**
* 订单重新发起支付请求DTO
*
* 前端会按 /shop/shop-order/pay -> /prepay -> /repay 依次尝试。
* 后端可统一实现为同一套逻辑多个URL别名避免出现 404 导致前端误判为不支持接口。
*/
@Data
@Schema(name = "OrderPrepayRequest", description = "订单重新发起支付请求")
public class OrderPrepayRequest {
@Schema(description = "订单ID二选一orderId 或 orderNo")
@Positive(message = "订单ID必须为正数")
private Integer orderId;
@Schema(description = "订单号二选一orderId 或 orderNo")
private String orderNo;
@Schema(description = "支付方式1=微信支付102=微信Native兼容旧类型。不传则使用订单原支付方式/默认微信支付")
private Integer payType;
@Schema(description = "租户ID可选不传则从当前登录用户/请求头推断)")
@Positive(message = "租户ID必须为正数")
private Integer tenantId;
}

View File

@@ -0,0 +1,53 @@
package com.gxwebsoft.shop.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import java.time.LocalDateTime;
import java.io.Serializable;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import com.fasterxml.jackson.annotation.JsonFormat;
/**
* 小区
*
* @author 科技小王子
* @since 2026-01-29 20:48:32
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Schema(name = "ShopCommunity对象", description = "小区")
public class ShopCommunity implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "ID")
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
@Schema(description = "小区名称")
private String name;
@Schema(description = "小区编号")
private String code;
@Schema(description = "详细地址")
private String address;
@Schema(description = "排序(数字越小越靠前)")
private Integer sortNumber;
@Schema(description = "备注")
private String comments;
@Schema(description = "状态, 0正常, 1冻结")
private Integer status;
@Schema(description = "租户id")
private Integer tenantId;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
}

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