Compare commits

...

235 Commits
HEAD ... master

Author SHA1 Message Date
cb d957a0c2a3 no message 2025-12-22 18:05:22 +08:00
cb dd51d48a45 no message 2025-12-22 17:42:16 +08:00
cb 5a172360bc no message 2025-12-04 17:15:28 +08:00
cb 28c5d34acf no message 2025-12-04 14:46:35 +08:00
cb e66d6b56e9 no message 2025-12-03 17:21:39 +08:00
cb 232509dc14 no message 2025-12-02 16:52:34 +08:00
cb 477f252fd1 no message 2025-11-26 08:58:22 +08:00
cb 4c801f007a no message 2025-11-24 17:13:17 +08:00
cb 5d5f67ef07 no message 2025-11-21 17:41:04 +08:00
cb 027e23e538 no message 2025-11-14 16:34:00 +08:00
cb f88d3a1673 no message 2025-11-13 10:46:50 +08:00
cb 5c17e811a9 处理分页问题 以及完单报错问题处理 2025-11-11 15:22:01 +08:00
cb ae843ab767 取消分页限制以及修改24小时定时后期处理 2025-11-10 15:33:50 +08:00
cb d2db2a5547 删除售后单增加对纠纷单的处理,增加通知设置 2025-11-08 16:47:47 +08:00
cb f1df93ec25 no message 2025-11-07 09:28:08 +08:00
cb 34d71c1fd0 增加新字段代表退款时的支付状态 2025-11-06 12:15:56 +08:00
cb bc9d50bf0c no message 2025-11-06 11:01:46 +08:00
cb e1e41d98bc no message 2025-11-04 08:57:27 +08:00
cb a36d9374dd no message 2025-11-01 11:15:02 +08:00
cb 174f6b4a82 增加商品售后的不同显示 以及后台重复处理的超时设置 2025-10-30 11:35:05 +08:00
cb aa7b8c7a5d no message 2025-10-30 09:35:05 +08:00
cb 77008d11da no message 2025-10-29 11:17:34 +08:00
cb f66ee72314 no message 2025-10-28 15:03:59 +08:00
cb 40ba4652be 修改售后类型,以及平台操作类型,平台退款金额处理 2025-10-25 16:35:48 +08:00
cb 1485037c82 no message 2025-10-24 17:49:51 +08:00
cb cb78526d0e 完善后台售后纠纷处理吗,完善数量以及数据获取, 2025-10-24 15:15:32 +08:00
cb bc96277f86 修改售后处理 以及主单售后纠纷查询 2025-10-23 17:33:12 +08:00
cb c733349a9e 售后超时,恢复财务金额 2025-10-23 10:38:59 +08:00
cb e7bb47f023 no message 2025-10-21 18:07:34 +08:00
cb a805c562c9 no message 2025-10-17 16:53:54 +08:00
cb 8bbacc653f 增加售后记录是否显示, 2025-10-17 14:54:02 +08:00
cb e0060c0432 超市排序处理,售后更新处理,售后记录显示 2025-10-17 11:49:29 +08:00
cb 72748b7113 no message 2025-10-17 08:52:16 +08:00
cb fe13c91ab2 no message 2025-10-16 14:28:11 +08:00
cb 16891e331d no message 2025-10-16 08:42:48 +08:00
cb 9ebbc056f3 no message 2025-10-14 16:43:01 +08:00
cb 43e51e7557 no message 2025-10-14 14:50:09 +08:00
cb 642038eef5 no message 2025-10-11 16:18:34 +08:00
cb 5489aaa620 no message 2025-10-11 10:12:59 +08:00
cb 9bb52b5ffb no message 2025-10-10 16:42:53 +08:00
cb bba67251af 支付后更新生成服务主单财务状态,修改监控单的查询条件,增加商品主单 全单退款 2025-10-10 16:13:24 +08:00
cb 4388cbb3f6 no message 2025-10-10 09:58:55 +08:00
cb e0226437ad no message 2025-09-30 17:52:47 +08:00
cb c57c585b37 no message 2025-09-29 15:35:25 +08:00
cb eced56a427 no message 2025-09-26 18:06:05 +08:00
cb 8148a59e83 no message 2025-09-26 11:02:57 +08:00
cb c84c39a28f 增加售后倒计时 以及 师傅状态查询 2025-09-25 15:53:14 +08:00
cb 47f717fcb2 no message 2025-09-25 11:38:11 +08:00
cb 5c2049c3ea no message 2025-09-15 18:08:27 +08:00
cb 82ecd1a144 no message 2025-09-15 16:51:52 +08:00
cb 9acd98d9b5 no message 2025-09-15 15:43:51 +08:00
cb 9db20efd88 no message 2025-09-13 17:30:50 +08:00
cb 61dcb7b25c no message 2025-09-12 17:14:20 +08:00
cb c8825f1c16 增加子单立即发货 2025-09-12 11:30:28 +08:00
cb c3f89a3a4f 修改距离计算 2025-09-12 10:18:55 +08:00
cb e9771921db 增加交货图片备注以及店铺和店铺距离 2025-09-11 17:57:16 +08:00
cb 9ebbcfacf7 修改不同的 类型调入大厅时间 增加详情返回对应的店铺信息 快递信息 2025-09-11 16:49:10 +08:00
cb 4783b4b077 增加类目设置倒计时 以及实付备注字段显示 2025-09-11 15:37:03 +08:00
cb 5cc6d5736d 增加类目倒计时小时数 增加新字段来表示分账结束时间 2025-09-11 11:47:57 +08:00
cb 87bcdda6f8 增加字段 2025-09-11 11:01:42 +08:00
cb 624921d776 增加返回的地址详情 2025-09-10 17:22:01 +08:00
cb 75ffa0f6ee no message 2025-09-10 16:49:28 +08:00
cb 1a8283e441 修改图片返回地址为https 增加商品店铺以及对应的距离显示 2025-09-10 12:13:28 +08:00
cb cb3e5d98a8 增加商品售后一些未修改的字段 2025-09-08 17:23:02 +08:00
cb 731d340abf 增加字段 2025-09-08 14:30:10 +08:00
cb e9ab52cb7e 增加子单的售后纠纷处理 2025-09-06 17:44:40 +08:00
cb 7878cc61c1 增加子单售后纠纷处理 2025-09-05 15:15:49 +08:00
cb c55ac047c0 增加pc端显示售后纠纷处理 2025-09-05 14:45:02 +08:00
cb a64f1728ac 优化商品售后的退款流程 2025-09-05 10:28:28 +08:00
cb a75d05bc9a no message 2025-09-05 09:29:24 +08:00
cb 6532c9d460 师傅立即重发补发方案 以及定时处理 2025-09-04 17:42:21 +08:00
cb 3eb0bd846f no message 2025-09-04 17:21:18 +08:00
cb 7d2b4f7fdb 增加商品售后大类 2025-09-04 17:12:02 +08:00
cb 7a2f71624b no message 2025-09-04 16:47:11 +08:00
cb 190d2a2bed 增加确认中倒计时的暂停以及开始 2025-09-04 16:25:46 +08:00
cb f12121c457 增加单独的售后纠纷查询 2025-09-04 15:27:43 +08:00
cb c7953da901 增加客户不同意的图片以及原因上传 2025-09-04 15:13:42 +08:00
cb 464fab9fa1 增加 重发补发 以及退货的接口 2025-09-03 16:30:42 +08:00
cb ef8fc2c492 no message 2025-09-03 15:10:18 +08:00
cb 9090b61095 增加定时处理 2025-09-03 15:04:37 +08:00
cb 08e5353b4e 处理商品类目没有的问题,增加客户是否同意上门重做方案,以及师傅重做/补做完成接口 2025-09-03 10:38:31 +08:00
cb a2a009a1e2 no message 2025-09-01 17:23:26 +08:00
cb 49394a5172 增加省市显示 2025-09-01 16:22:58 +08:00
cb 57d5161b2c 增加类目 类型区分 2025-08-30 16:08:35 +08:00
cb 270ad85a10 增加区域 类型划分 2025-08-30 12:28:04 +08:00
cb 65ca7bf9aa 增加主单和子单的师傅备注 2025-08-30 08:54:52 +08:00
cb 356d2beaa5 增加退单 后台主单显示 2025-08-28 11:45:31 +08:00
cb d2260ade7b 增加主单退单原因 2025-08-28 11:33:01 +08:00
cb 9c519ce980 no message 2025-08-26 15:40:58 +08:00
cb 620af65906 no message 2025-08-25 08:44:15 +08:00
cb 353e302df9 增加详细地址计算与店铺的距离 2025-08-23 16:45:00 +08:00
cb 0a37c352fb 增加通过店铺id 以及 经纬计算与店铺的距离 返回店铺详情 2025-08-23 16:04:57 +08:00
cb 1a5b094388 no message 2025-08-22 16:31:54 +08:00
cb c8811b0e60 增加地址经纬度以及逆解析地址到经纬度 2025-08-22 16:31:17 +08:00
cb 510e696030 no message 2025-08-22 11:35:33 +08:00
cb 8c859e1866 增加通过商品id 去获取对应的服务店铺列表 2025-08-21 17:17:30 +08:00
cb 91cb050b47 no message 2025-08-01 15:40:43 +08:00
cb 1fe53780b9 no message 2025-07-31 16:34:53 +08:00
cb 671a9f225e no message 2025-07-31 10:39:56 +08:00
cb ae2f59d183 no message 2025-07-29 17:39:22 +08:00
cb d59b15c569 no message 2025-07-29 08:34:57 +08:00
cb fa7ff1971f no message 2025-07-18 17:43:55 +08:00
cb 08c7cb32a9 no message 2025-07-14 17:33:50 +08:00
cb f1d55564c3 修改交货图片 备注字段名 2025-07-13 09:09:27 +08:00
cb 3cf9951bb3 no message 2025-07-11 20:53:39 +08:00
cb ce8015b836 no message 2025-07-11 18:55:44 +08:00
cb 9a3245380b 增加定时器来处理 发货中的状态转为确认中 2025-07-10 17:12:48 +08:00
cb 02528c930f no message 2025-07-10 15:13:57 +08:00
cb 3dc68ceaf8 no message 2025-07-09 17:41:24 +08:00
cb ad6473f446 no message 2025-07-08 10:36:12 +08:00
cb 61e9859181 no message 2025-07-08 09:47:25 +08:00
cb 3fbd432b9c 按照商品规格进行模糊查询 2025-07-07 15:12:25 +08:00
cb 7643b5d6d8 no message 2025-07-03 15:06:41 +08:00
cb 4ef6b6de2c no message 2025-07-03 14:55:42 +08:00
cb 7a73c9b099 no message 2025-07-03 11:00:34 +08:00
cb 40f5c1472e no message 2025-07-02 18:05:58 +08:00
cb 8870875647 no message 2025-07-02 16:17:24 +08:00
cb 5fe9d79a94 no message 2025-07-02 10:41:39 +08:00
cb ee36d363a7 no message 2025-06-30 18:07:40 +08:00
cb f9f36c16d9 no message 2025-06-25 11:46:55 +08:00
cb 7f959107da no message 2025-06-24 17:52:14 +08:00
cb 47f5c7b039 no message 2025-06-24 16:19:24 +08:00
cb 704454a759 no message 2025-06-20 17:08:54 +08:00
cb a82b513755 no message 2025-06-20 16:14:10 +08:00
cb c726bf91d4 给配件主单增加是否派发服务主单字段 2025-06-20 11:33:53 +08:00
cb ea0233425e no message 2025-06-20 11:20:31 +08:00
cb 708d2f12f5 增加配件主单下的服务主单退单逻辑 增加商品列表按距离排序 2025-06-20 11:20:03 +08:00
cb 037b1b5b5e no message 2025-06-20 10:09:03 +08:00
cb b2ddfb1502 no message 2025-06-19 18:10:43 +08:00
cb bd78980529 no message 2025-06-19 14:49:23 +08:00
cb fa616c8de6 no message 2025-06-18 15:46:13 +08:00
cb 7afe847942 后台增加商品类目可以配置服务类目 2025-06-18 15:33:49 +08:00
cb d3dcb97911 no message 2025-06-18 14:36:35 +08:00
cb 10835cd1a7 no message 2025-06-17 17:38:41 +08:00
cb e2ec85c7ae 增加根据用户id获取对应的商铺id 的方法 2025-06-16 17:29:13 +08:00
cb 7a2a155938 no message 2025-06-16 16:43:07 +08:00
cb dfcf26d976 no message 2025-06-16 15:37:36 +08:00
cb cedc78e76e no message 2025-06-14 21:37:33 +08:00
cb e38126d341 待完善 2025-06-14 12:50:26 +08:00
cb 547fcfc37b 增加新订单状态 来表示提现未到账 2025-06-14 12:46:35 +08:00
cb e8af400536 no message 2025-06-14 12:39:03 +08:00
cb 3524b20075 no message 2025-06-13 17:54:49 +08:00
cb affce5e1b4 no message 2025-06-12 19:00:48 +08:00
cb f1328d4db8 no message 2025-06-11 19:02:44 +08:00
cb 75c06e6457 增加加价的截留扣点设置 2025-06-10 18:01:10 +08:00
cb 448b134f11 主单状态修改 不在分账的时候就进入已完成的状态 2025-06-10 16:59:19 +08:00
cb 5b8fe3cd58 修改类目中的设置的时候显示全部的类目 2025-06-10 16:55:31 +08:00
cb f68eb53363 no message 2025-06-09 17:53:46 +08:00
cb 9a722c8da2 增加子单金额的分账 2025-06-08 18:13:17 +08:00
cb 18b6d5325d no message 2025-06-05 17:29:37 +08:00
cb 8766726189 no message 2025-06-05 14:43:49 +08:00
cb dfc1aaca91 no message 2025-06-04 18:01:21 +08:00
cb ca4530ce84 no message 2025-06-04 15:44:49 +08:00
cb 6c500a0349 no message 2025-05-30 17:35:08 +08:00
cb d7f8ffa20d no message 2025-05-30 16:15:04 +08:00
cb 8e09f2aaf6 no message 2025-05-29 18:13:16 +08:00
cb 36767a00bf no message 2025-05-28 16:43:40 +08:00
cb ecd9a76eeb no message 2025-05-27 17:32:29 +08:00
cb 4f984ab2e7 no message 2025-05-26 18:10:41 +08:00
cb 1d91eb9976 暂时只显示正选类目 2025-05-26 17:16:13 +08:00
cb c747158393 修改类目设置 2025-05-26 16:17:34 +08:00
cb cb16333e61 修改子单的服务金额 2025-05-26 14:35:26 +08:00
cb 964d1cb519 no message 2025-05-26 11:55:12 +08:00
cb cc032f44c0 11 2025-05-23 18:06:54 +08:00
cb 71bb414f23 1 2025-05-22 20:53:55 +08:00
cb ef23b61265 no message 2025-05-22 18:06:12 +08:00
cb 6dd60354a6 删除配件费用,增加加价凭证 2025-05-21 17:50:04 +08:00
cb 582d478186 no message 2025-05-20 09:25:52 +08:00
cb a14f69f77c 修改一小时上门的判断条件 2025-05-15 11:47:05 +08:00
cb 5a4945955d no message 2025-05-14 10:34:16 +08:00
cb 99be99391f no message 2025-05-13 15:19:41 +08:00
cb 60952f6b47 no message 2025-05-13 09:58:36 +08:00
cb 60c02a28ed no message 2025-05-13 09:51:25 +08:00
cb 05ac0b5acb no message 2025-05-13 09:18:38 +08:00
cb 087ea91ecf no message 2025-05-12 16:12:51 +08:00
cb 54a7b65f8f no message 2025-05-10 16:59:45 +08:00
cb 687f691ff9 no message 2025-05-10 09:33:36 +08:00
cb 78f43c0c16 no message 2025-05-09 15:30:39 +08:00
cb 880866b1b6 no message 2025-05-09 15:21:52 +08:00
cb c07f80e18f no message 2025-05-09 15:02:21 +08:00
cb 1abb02f464 1 2025-05-09 14:45:47 +08:00
cb fc38c49162 no message 2025-05-09 11:36:51 +08:00
cb c857e551fb no message 2025-05-09 09:49:20 +08:00
cb a22d60f23a no message 2025-05-09 09:03:24 +08:00
cb 806e5e700e no message 2025-05-08 18:05:13 +08:00
cb f8a987a0f6 no message 2025-05-08 18:03:29 +08:00
cb be3311daee 修改子单撤单后的 判断是否有子单还在派单 如果没有再修改为null 2025-05-08 17:39:37 +08:00
cb 0ff2a6b703 no message 2025-05-08 16:51:09 +08:00
cb dbeace3191 no message 2025-05-08 16:33:18 +08:00
cb 96cbd47cb0 no message 2025-05-08 16:08:19 +08:00
cb bcffad126c no message 2025-05-08 15:52:48 +08:00
cb 6dbdb33a2b no message 2025-05-08 15:24:49 +08:00
cb fecb22b5ad no message 2025-05-08 15:14:43 +08:00
cb e661871090 no message 2025-05-08 15:03:17 +08:00
cb 38c26e399f no message 2025-05-08 14:53:22 +08:00
cb e1696d937b no message 2025-05-08 14:31:14 +08:00
cb 2099610e59 no message 2025-05-08 14:24:53 +08:00
cb 2e2494c9bb no message 2025-05-08 12:22:55 +08:00
cb ed034cb2c7 no message 2025-05-08 12:05:23 +08:00
cb d40822487f no message 2025-05-07 16:40:55 +08:00
cb 3cd9560732 no message 2025-05-07 15:34:10 +08:00
cb 479e619d39 增加子单超时未接单 2025-05-07 14:35:05 +08:00
cb bd9883c0d0 no message 2025-05-07 11:42:09 +08:00
cb 6aec17ed20 no message 2025-05-07 11:41:17 +08:00
cb 5aea1c4731 no message 2025-05-07 11:31:53 +08:00
cb f385aaabf3 no message 2025-05-07 11:24:39 +08:00
cb 0a86d01406 no message 2025-05-07 11:13:32 +08:00
cb ccb7cc34b5 no message 2025-05-07 11:06:06 +08:00
cb 37722ef2fa no message 2025-05-07 10:57:38 +08:00
cb d1ce4b76a2 no message 2025-05-07 10:04:24 +08:00
cb 95952ea425 no message 2025-05-07 08:51:01 +08:00
cb 1a2f919f79 no message 2025-05-06 17:50:01 +08:00
cb 3c958a2923 no message 2025-05-06 15:06:10 +08:00
cb ec4fdda392 no message 2025-05-06 09:56:16 +08:00
cb 73710b0a4c no message 2025-05-06 09:37:45 +08:00
cb e6a3d6fc2d no message 2025-05-06 09:24:25 +08:00
cb e7e8583806 Merge branch 'master' of https://git.opsoul.com/clunt/ghy-all 2025-05-06 09:21:08 +08:00
cb 259b99c3f0 no message 2025-05-06 09:20:27 +08:00
cb 9df61f6a85 修改列表查询 2025-04-30 21:57:34 +08:00
cb 21c4d8e7dc Merge remote-tracking branch 'origin/master' 2025-04-30 19:47:44 +08:00
cb 174b6031e9 no message 2025-04-30 18:11:14 +08:00
cb 37471713b7 Merge remote-tracking branch 'origin/master' 2025-04-30 18:01:22 +08:00
cb 4c8a00d081 no message 2025-04-30 18:01:06 +08:00
cb f9080bb812 Merge remote-tracking branch 'origin/master' 2025-04-30 17:38:30 +08:00
cb cf10288761 no message 2025-04-30 17:37:56 +08:00
cb 1be8d59c6f Merge remote-tracking branch 'origin/master' 2025-04-30 17:36:37 +08:00
cb 930a9e3de8 no message 2025-04-30 17:36:19 +08:00
cb 2e7bcaba70 Merge remote-tracking branch 'origin/master' 2025-04-30 17:14:35 +08:00
cb d6028c3e1e no message 2025-04-30 17:14:23 +08:00
cb ff67462b73 Merge remote-tracking branch 'origin/master' 2025-04-30 16:53:49 +08:00
cb fab703b7af no message 2025-04-30 16:53:31 +08:00
cb 2655d8ffdb Merge remote-tracking branch 'origin/master' 2025-04-30 14:50:13 +08:00
cb ab7e7eaca7 no message 2025-04-30 14:48:40 +08:00
cb c92ea38c60 Merge remote-tracking branch 'origin/master' 2025-04-30 09:55:26 +08:00
cb 977994aeee no message 2025-04-30 09:31:13 +08:00
145 changed files with 14192 additions and 1039 deletions

1
.gitignore vendored
View File

@ -34,3 +34,4 @@ build/
.vscode/ .vscode/
/logs/ /logs/
/ghy-admin/src/test/ /ghy-admin/src/test/
ghy-shop/ADDRESS_QUERY_EXAMPLE.md

View File

@ -0,0 +1,214 @@
<!-- @author Yifei.Kuang -->
# 房间 / 群 / 群员业务需求说明文档
## 一、项目背景与目标
- **背景**
在现有「师傅 / 商城 / 分销」体系上,引入「房间(圈子)+ 群 + 分销」的新结构,实现多房间、多群、多身份的精细化运营与监管。
- **目标**
- 建立清晰的层级结构:**平台 → 房间 → 群 → 群员/分销**。
- 统一分销身份规则,保证订单与佣金归属准确。
- 支持群主、房间主、下级分销、运营商等多角色协同。
- 为后续房间收费、群收费、群员收费等模式预留空间。
---
## 二、核心概念定义
### 2.1 平台
- 系统最上层运营主体,负责全局参数(平台扣点、结算规则等)。
### 2.2 房间(圈子)
- **定义**:平台下的独立运营空间,一个平台可创建多个房间。
- **特点**
- 房间名称:限制在 46 个字。
- 房间标识图片、品牌注解由房间主配置。
- 顶部功能栏:默认为「师傅库 / 商城 / 会话厅」,文字可改,总长度有限制但功能不变。
- 房间可配置自身的功能权限(是否开放商城、是否允许群选品等)以及扣点、收费策略。
- **关系**
- 「微社圈」是整体产品名,“切换圈子”即切换不同房间。
### 2.3 群(分销群)
- **定义**:隶属于某房间的运营群,每个房间下可以创建多个群。
- **建群要求**
- 填写城市、区域、主营品类、预期群规模等信息。
- 建群申请需由房间主及后台审核(形式类似现有分销申请)。
- **职责**
- 每个群经营自己的商品与客户。
- 群商城中仅群主有选品权限,下级分销不能选品。
### 2.4 群主
- 该群的最高级分销者。
- 新建群后自动成为本群顶级分销(无上级分销)。
- 负责本群的选品、群成员管理、群内规则等。
### 2.5 群员 / 分销人员
- 所有群员可根据既有三级分销规则绑定上级关系。
- **分销身份规则**
- 每个群员在「平台 + 房间 + 群」这一组合下有一个独立分销身份。
- 同一微信号在不同房间、不同群中,对系统而言是多个独立身份。
- **影响**
- 每个身份对应一条独立的分销线。
- 订单结算与分账都按所在群对应的分销线进行。
### 2.6 系统机器人 / 监管号
- 平台可在任意群中驻入一个“系统机器人”账号:
- 拥有类似群主的管理能力及后台监管能力。
- 用于监管群运营是否健康、是否违规。
### 2.7 运营商(按微信号)
- 以微信号为主体的运营视角。
- 同一微信号在多个房间、多个群中有多条分销线,但在运营统计中可统一聚合。
---
## 三、分销身份与订单归属规则
### 3.1 唯一身份线
- 每条分销身份线由四段组合定义:
**平台ID + 房间ID + 群ID + 用户ID**
- 每一条组合都被视为“系统中的一个人、一条分销线”。
### 3.2 多群多房间身份
- 同一微信号在不同房间/群中创建或加入时:
- 系统会为其生成新的分销身份线。
- 不同房间、群的身份互不影响。
### 3.3 分销绑定
- 在某群中被拉入的新成员:
- 按现有三级分销规则建立上下级关系。
- 上下级关系限定在本群对应的分销线中。
### 3.4 订单与分销归属
- 用户从某个群入口进入商城并下单:
- 所有分销与分账都按该群的分销线计算。
- 即使服务商在其他群也有身份,本订单仍只归属于当前群对应的身份线。
---
## 四、房间与群的前台结构
### 4.1 房间首页结构
- 房间头部展示:
- 房间头像/标识图;
- 品牌名称46 字);
- 简短品牌注解。
- 顶部功能栏:
- 默认入口为:「师傅库 / 商城 / 会话厅」。
- 房间主可修改这三个名称(总长度有限制),但功能含义不变。
- 顶部入口与底部导航对应,为功能快捷入口。
### 4.2 师傅库
- 展示本房间下已入驻的师傅与服务店铺。
- 支持按街道、服务品类筛选。
- 师傅资料在现有认证信息基础上增加头像和定位信息。
- 用户可在师傅详情中:
- 直接发起“发布订单”给该师傅;
- 或将已有未接订单指派给该师傅。
### 4.3 商城
- 房间/群共用一个商城体系:
- 房间级定义基础类目与商品池。
- 群主在此基础上为本群做选品配置。
- 首页默认展示热门服务类目,可左划展示其他商品类目。
- 列表支持按价格、评分、销量筛选。
- 商品销量按整条上架商品做统一统计。
### 4.4 会话厅
- 作为房间的首页使用:
- 展示全部群、自建群、已加入群、单聊会话等。
- 顶部菜单包含:申请房间、申请建群、搜索群、房间名称设置等入口。
- 与群聊页面不同,主要承载群列表与会话入口。
---
## 五、订单与发布相关需求(概要)
### 5.1 师傅端状态汇总
- 师傅端首页顶部使用黄条展示:
- 新订单数量;
- 未约订单数量;
- 待上门/待发货数量。
- 点击不同统计可跳转到对应订单列表。
### 5.2 发单入口
- 页面底部设置蓝色「+」按钮作为发布主单入口。
- 支持:
- 服务类/商品类发单;
- 选择到第 4 级类目,可多选;
- 将常用配置保存为模板。
- 支持代填发布,由专业师傅代填问题内容,客户补充地址、电话后提交。
### 5.3 保险规则(概念级)
- 发单时默认勾选“启用保险核查服务费”。
- 发单方需选择保险费用由谁承担:
- 服务费由发单方支付;
- 服务费在订单中扣除。
- 到付场景下,已产生的保险费用仍需保留,并可在后续订单中抵扣。
### 5.4 报价/加价模式(概念级)
- 在满足一定条件(报价模式或允许商家报价)时,显示“加价/报价”入口。
- 支持大厅主单、未约/未排/监控单、已接单主单进行报价。
- 客户最多可选择 3 家(特例 4 家)报价,确认后进入支付流程。
---
## 六、「我的」与运营统计(概念级)
- “我的”页面延用现有结构:
- 展示分销中心、订单、选品等信息。
- 运营统计以微信号为主体:
- 汇总展示多房间、多群的运营数据;
- 可按房间、群维度下钻查看。
---
## 七、商品与服务上架(概念级)
- 服务上架沿用当前规则。
- 商品上架需区分于服务:
- 针对库存、规格、展示方式做单独设计;
- 与服务上架在前台展示上明显区分。
---
## 八、后台与安全监管(概念级)
- 房间级管理:
- 房间创建与审核;
- 房间启用/禁用;
- 房间收费与扣点配置。
- 群级管理:
- 建群审核;
- 群启用/禁用;
- 群主调整;
- 系统机器人驻入/移除。
- 操作审计:
- 记录房间、群创建和重要参数修改,便于运营追溯。
---
**文档定位**:业务需求说明(偏产品视角)
**用途**:为后续详细设计与开发提供业务背景和规则依据

View File

@ -0,0 +1,285 @@
<!-- @author Yifei.Kuang -->
# 房间 / 群 / 分销功能说明文档(开发视角)
## 一、整体架构
- 层级结构:
- 平台 → 房间Room→ 群Group→ 群员 / 分销身份Distributor
- 身份核心:
- 每条分销身份由「平台 + 房间 + 群 + 用户」组合唯一确定,用于分销归属和订单结算。
---
## 二、房间模块Room
### 2.1 房间功能点
- 创建房间申请:
- 用户提交房间名称、头像、品牌注解等信息。
- 后台审核通过后,生成房间,并指定房间主。
- 房间信息维护:
- 房间主可编辑名称、头像、品牌注解。
- 房间顶部功能栏配置:
- 默认显示「师傅库 / 商城 / 会话厅」三项。
- 房间主可以修改显示文案(总字数受限),功能类型不变。
- 房间权限与扣点设置(后台):
- 是否开启本房间商城。
- 是否允许本房间下的群进行选品。
- 房间级扣点与收费策略配置。
- 房间切换:
- 同一微信可加入多个房间,通过「切换圈子」在不同房间间切换。
---
## 三、群模块Group
### 3.1 建群与审核
- 在某房间内,用户可发起建群申请。
- 填写群基础信息(城市、区域、主营类目、群规模等)。
- 审核流程:
- 房间主初审;
- 后台终审;
- 通过后创建群,申请人自动成为群主。
### 3.2 群基本操作
- 群信息维护:
- 群主可编辑群名、介绍、主营描述等。
- 群状态控制(后台):
- 支持群的启用/禁用;
- 禁用后,群内发单、接单、分销等操作关闭或受限。
- 群主变更:
- 后台可在必要时调整群主(例如违规处理)。
### 3.3 群监管
- 支持在任意群中驻入“系统机器人/系统监管号”:
- 具备基本群管理与后台数据采集、监控能力。
- 用于运营与风险控制。
---
## 四、群员 / 分销身份模块
### 4.1 分销身份规则
- 每个用户在某「平台 + 房间 + 群」组合下对应一个分销身份。
- 同一微信在不同房间/群中拥有多个独立身份:
- 每个身份有独立的分销链;
- 后台视为多个“人”。
### 4.2 加入群与分销绑定
- 成员加入群时:
- 若在该群下不存在分销身份,则系统为其创建一条新身份线。
- 按既有三级分销规则绑定上级分销。
- 上下级分销关系:
- 限定在当前群对应的分销线中,不跨群共享。
### 4.3 角色与权限(概念层)
- 群主:
- 本群最高级分销;
- 拥有选品、群配置、成员管理等高权限。
- 管理员:
- 由群主任命,可拥有部分管理能力(禁言、审核、公告等)。
- 普通分销/群员:
- 不能在群内进行选品;
- 可以拉人建立下级分销关系。
---
## 五、会话厅与群聊相关功能
### 5.1 订单页面导航(左侧)
- 导航栏包含 4 项:
- 资源库;
- 商品城;
- 会话厅;
- 附近圈动态。
- 群主可以:
- 修改导航名称(总字数有限制);
- 单独开放/禁用任一入口;
- 与外层房间首页的导航机制保持一致。
- 右下角「会话大厅」按钮:
- 进入群聊页面(中间页);
- 文案可改,群主/平台可控制是否可用。
### 5.2 资源库A
- 展示服务师傅资料与店铺:
- 格式类似现有“师傅圈”,但样式可定制。
- 群主可设置仅群内成员可见,或继承房间级展示。
- 提供发布入口,支持基于群选品的类目发单。
### 5.3 商品城B
- 群级商城展示逻辑:
- 与整体商城逻辑一致;
- 差异在于:群商城的选品由群主独立配置。
- 所有人看到的商品、类目等均来源于群主选品结果。
- 首页热门类目默认服务类,可左滑出商品类目列表。
- 列表筛选按价格、评分、销量进行。
- 销量统计按整条商品聚合,不再按规格拆分。
### 5.4 会话厅C
- 会话厅为“会话/订单流”页面,不是群聊页面。
- 默认进入订单页时落在该列:
- 支持上拉刷新;
- 承载订单、系统相关信息的流式展示。
### 5.5 附近圈动态D
- 展示商家图文动态的“公共圈子”区域:
- 默认名称为“附近圈动态”;
- 群主可以改名,但含义不变(商家圈/动态圈)。
### 5.6 会话大厅图与编辑订单E
- 页面中部展示“会话大厅相关图”:
- 使用会话大厅图的一部分放大,作为视觉入口。
- 支持进入“编辑订单/发单页”:
- 编辑后的订单可发至全群;
- 管理员可选择仅发送至单个客户。
---
## 六、群聊在线控制与刷新机制
### 6.1 在线人数限制
- 当群聊在线人数超过上限(群主/后台可配置):
- 群主与管理员不受上线限制,始终在线。
- 系统自动将部分成员标记为离线:
- 优先踢出近期未发言的成员;
- 该过程对成员前端为“默默离线”,不会强制踢出页面。
### 6.2 重新上线与刷新
- 被标记为离线的成员:
- 在输入框输入内容时,自动重新上线。
- 新进群但未发言的成员:
- 一段时间后,输入框变为“消息刷新”按钮;
- 点击后重新加载消息,并作为新上线行为。
- 在已超上线限制的状态下:
- 新进入成员默认离线;
- 需主动刷新或发言才开始拉取实时消息。
### 6.3 页面跳转与上线状态
- 在群聊中点击任何产品、品类跳转其他页面:
- 无论是否超限,都视为离线(停止实时连接)。
- 从商品/其他页面返回群聊页:
- 自动恢复为在线状态。
- 在商品页面点击“聊天”:
- 跳回群聊,并恢复在线。
---
## 七、会话厅顶部类目与选品关系
### 7.1 顶部类目区
- 顶部显示:
- 「全部」入口;
- 若干热门类目。
- 热门类目:
- 群主从本群的主营/重点品类中选定;
- 可对特别重要的类目打红点标记。
- 展示方式:
- 左右滑动;
- 每行最多 8 个类目,最多展示两行。
### 7.2 选品关系
- 群主 = 分销顶层:
- 群主在本群选品的结果,体现在“全部”和热门类目展示中。
- 群员:
- 无法直接在群内进行选品;
- 仅能浏览、转发商品并参与分销。
---
## 八、群内订单与全域订单
### 8.1 群内订单发起
- 在群聊页面可以通过以下方式生成群内订单:
- 使用专门的“群内订单发单入口”;
- 将当前输入框的文字通过快捷键转换为订单(跳转到发单确认页)。
- 生成的群内订单:
- 在群聊中以订单卡片形式展示;
- 提供“接单”按钮。
### 8.2 与专业版(师傅端)的联动
- 若群内发单时勾选“保险”等高级字段:
- 该订单将按专业版(师傅端)逻辑处理;
- 接单、保险、结算等均走专业端流程。
- 群主可配置:
- 群内订单是否仅允许本群服务人员接单;
- 或是否允许进入大厅,被所有专业师傅接单。
### 8.3 全域订单
- 群主可将部分群内订单开放到“全域订单池”:
- 其他群的服务商也能在“全域订单”中看到这些订单并接单。
- 展示限制:
- 在群页面或全域订单列表中:
- 只有服务商能看到具体金额;
- 普通消费者看不到金额。
### 8.4 订单生命周期(概念)
- 典型状态流:
- 已发单 → 待服务(需要填写上门时间)→ 已提交 → 已付款 → 已完成。
- 客户侧操作:
- 在“已提交”中查看支付方式并确认;
- 确认支付后进入“已付款”。
- 服务侧操作:
- 服务方确认“已收款,结单”,进入“已完成”。
### 8.5 纯发单群模式
- 对于“只发单、不聊天”的群:
- 群聊可以不保持实时连接;
- 用户仅在手动刷新时看到新订单;
- 群形态接近“单纯接单页面”,以降低系统实时成本。
---
## 九、「我的」与运营视角
- “我的”页面沿用现有商城结构:
- 包含分销中心、订单、选品等模块。
- 统计以微信号为主视角:
- 汇总展示该用户在不同房间、群中的运营情况;
- 可按房间、群进行明细下钻。
---
## 十、后台与审计(功能级)
- 房间管理:
- 房间申请审批;
- 房间启用/禁用;
- 房间级扣点/收费配置。
- 群管理:
- 建群申请审核;
- 群启用/禁用;
- 群主调整;
- 系统机器人驻入/移除。
- 审计日志:
- 创建/修改房间、群;
- 修改扣点与重要配置;
- 关键权限调整(如群主、管理员变更)等操作均需记录。
---
**文档定位**:开发可读的功能说明(不含字段/接口细节)
**用途**:指导后续接口设计、表结构设计与前后端实现拆分

View File

@ -0,0 +1,299 @@
# 群成员身份标签与权限管理功能文档
## 文档说明
本文档描述群聊中**群成员身份标签、分类管理、权限控制、消息通知**相关的功能逻辑,不涉及字段/表结构设计,仅面向功能实现。
---
## 一、群成员身份标签系统
### 1.1 身份标签类型
系统支持以下身份标签:
- **创建者**:群创建者(通常也是群主)
- **管理员**:被群主任命的管理员
- **服务商家**:提供服务的商家/师傅
- **商品商家**:提供商品的商家
- **消费者**:普通消费者
- **发单人**:经常发单的用户
- **接单人**:经常接单的用户
- **群成员**:普通群成员
- **VIP客户**VIP客户
### 1.2 标签分配流程
- **入群时自选标签**
- 用户加入群时,系统提供两个初始选项:
- **非商家**
- **商家**
- 用户选择一个作为初始身份标识。
- **群主/管理员最终确认**
- 群主或管理员可以在群成员资料中:
- 查看用户自选的标签。
- 从系统标签库中选择一个标签,覆盖或确认用户的标签。
- 后续可以随时修改成员的标签。
- **标签对外展示**
- 标签会显示在群成员资料、群成员列表等位置,作为该成员在群内的“对外身份”。
### 1.3 群成员资料栏位
- **群成员资料页包含7个栏位**
- 每个栏位可以放置一个名称/备注。
- 这些栏位对所有身份/标签的群员都开放使用。
- 用途:方便所有群员查看和识别成员信息。
---
## 二、群成员分类/分组系统
### 2.1 分类概念
- **分类定义**
- 群内成员可以按照“分类”进行组织,每个分类可以理解为“分组”或“分仓”。
- 系统支持创建 **1~7个分类**
- 每个分类可以自定义命名例如“1类”、“2类”或自定义别名如“核心商家组”、“普通客户组”等
- **分类与标签的关系**
- 分类与身份标签是**独立的两套体系**
- 一个成员可以拥有一个身份标签(如“服务商家”)。
- 同时该成员可以被放入任意一个分类中如“1类”
- 标签不影响分类归属,分类也不影响标签展示。
### 2.2 分类操作
- **成员分类归属**
- 任何成员可以被放入任意一个分类中。
- 例如可以将某个“服务商家”放入“1类”也可以将某个“消费者”放入“1类”。
- 成员可以只属于一个分类,也可以不属于任何分类(显示在“未分类”或“全部”中)。
- **按分类查看成员**
- 群主与管理员可以:
- 按分类查看成员列表。
- 按标签查看成员列表。
- 按“分类+标签”组合筛选查看。
- **普通群成员查看**
- 普通群成员在群名单中:
- 可以按“全部”及“标签身份”查看成员信息。
- 不能按分类查看(分类信息对普通成员不展示)。
### 2.3 分类消息发送
- **管理员按分类发消息**
- 管理员可以:
- 选择向某个分类的所有成员发送消息(其他分类看不到)。
- 选择向某个分类中的单个成员发送消息。
- 用途:主要用于群管理员进行精细化运营和定向通知。
---
## 三、管理员任命与权限体系
### 3.1 管理员任命
- **任命流程**
- 群主可以任命任意群成员为管理员。
- 任命时可以:
- 标注该管理员的“岗位名称”(例如“客服主管”、“运营专员”等)。
- 有岗位名称的管理员会在“服务团队消息专列”中显示。
- **订单交付权限(独立于管理员)**
- 订单交付栏的权限与管理员身份独立:
- 群主可以勾选某些成员(不一定是管理员)拥有“订单交付权限”。
- 拥有交付权限的人可以在订单交付栏中发布交付信息。
### 3.2 管理员权限列表
群主在任命管理员时,可以勾选该管理员拥有的权限项。管理员在哪个分类中不影响其权限范围。
#### 权限1按类发群信息
- 管理员可以向指定分类的所有成员发送群消息(其他分类看不到)。
#### 权限2禁言与消息可见性控制
- **禁言功能(实际为“审核可见”)**
- 管理员可以按分类或单个成员设置“禁言”。
- 被禁言的成员:
- 仍然可以发送消息,但消息默认**仅管理员可见 + 自己可见**。
- 管理员可以在后台审核这些消息,勾选后才会展示到群聊页面。
- 严格来说这不是传统“禁言”,而是“审核可见”机制。
- **消息可见性设置**
- 管理员可以:
- 按分类设置:该类群员发的消息仅该类成员可见,或仅管理员可见。
- 按单个成员设置:该成员发的消息仅管理员可见(在群内形成独立通道,对其他人无感)。
- **特殊规则**
- 被禁言的成员仍然可以发送商品链接到群(商品链接不受禁言限制)。
- 管理员查看消息的规则:
- 如果管理员属于某个分类,只能看到该类群员发的信息。
- 如果管理员在“全部”分类中,可以看到所有分类群员发的信息。
- **管理员@解禁**
- 管理员可以@被禁言的成员临时解禁3小时。
#### 权限3禁止接单
- 管理员可以按分类或单个成员设置“禁止点击接单键”。
- 被禁止的成员无法接取群消息中的订单。
#### 权限4禁止发单
- 管理员可以按分类或单个成员设置“禁止点击发单键”。
- 被禁止的成员无法使用发单功能(包括类目发单)。
#### 权限5禁止互加好友
- 管理员可以按分类或单个成员设置“禁止互加好友”。
- 被禁止的成员:
- 别人无法添加他为好友。
- 他也无法添加别人为好友。
- **特殊规则**
- 即使被禁止加好友,双方仍然可以:
- 在服务团队聊天中直接对话(通过服务团队入口)。
#### 权限6屏蔽群内信息
- 管理员可以按分类或单个成员设置“屏蔽群内信息”。
- 被屏蔽的成员:
- 看不到群内在发的消息。
- **但订单仍然可见可接**(屏蔽消息,不屏蔽订单展示)。
#### 权限7禁止发圈信息
- 管理员可以按分类或单个成员设置“禁止发圈信息”。
- 被禁止的成员无法在商家圈/动态圈中发布内容。
#### 权限8审核成员入群
- 管理员可以审核新成员的入群申请。
#### 权限9删除成员与撤回消息
- 管理员可以:
- 删除群成员(将成员移出群)。
- 撤回群成员的消息:
- 可以撤回任意成员的一条消息。
- 可以彻底删除消息(删除后不可见,不可恢复)。
#### 权限10分类分标签发公告
- 管理员可以:
- 按分类发送公告。
- 按标签发送公告。
- 按“分类+标签”组合发送公告。
---
## 四、群主专属权限
### 4.1 群名设置
- 群主可以修改群名称。
### 4.2 群转让
- 群主可以将群主身份转让给其他成员。
### 4.3 开通入群申请
- 群主可以开启/关闭“入群申请”功能。
- 开启后,新成员需要申请才能加入群。
### 4.4 待接单显示规则(待定)
- 群主可以设置“待接单是否仅显示本群的订单”(此功能待定,可能不实现)。
---
## 五、群消息通知机制
### 5.1 消息通知基础规则
#### 未打开登录页面的通知策略
- 用户未打开登录页面APP未打开
- 系统每 **5分钟** 聚合一次新消息,发送一条通知。
- 通知内容显示所有群的新消息汇总例如“您有X条新消息”
- 用户打开APP后不再发送聚合通知。
#### 群外消息计数
- 在群列表页面(群外):
- 每个群名旁边显示该群的消息数量角标。
- APP端统一使用角标显示。
### 5.2 群消息免打扰设置
#### 免打扰选项
每个群成员可以为自己设置该群的“免打扰”策略,选项包括:
- **仅1小时1条**1小时内只通知一次。
- **仅1天1条**1天内只通知一次。
- **仅3天1条**3天内只通知一次。
- **仅10天1条**10天内只通知一次。
#### 免打扰与通知队列的关系
- 系统维护多个通知队列:
- **5分钟通知队列**默认队列每5分钟通知一次。
- **1小时通知队列**设置了“1小时1条”的用户进入此队列。
- **1天通知队列**设置了“1天1条”的用户进入此队列。
- **3天通知队列**设置了“3天1条”的用户进入此队列。
- **10天通知队列**设置了“10天1条”的用户进入此队列。
- 用户设置免打扰后:
- 从“5分钟队列”中剔除进入对应的免打扰队列。
- 例如设置“1小时1条”后每1小时聚合通知一次而不是每5分钟
#### 通知时间窗口
- **5分钟队列与1小时队列**
- 晚上7点后不通知。
- 早上8点后才开始通知。
- **1天队列、3天队列、10天队列**
- 通知时间点可以设定在12点或错开时间点例如1天队列12点3天队列14点10天队列16点以减少大量通知同时推送。
#### 用户类型与免打扰选择
- **经营者(商家/师傅)**
- 更多选择“5分钟通知”或“1小时通知”需要及时响应
- **客户(消费者)**
- 更多选择“3天通知”或“10天通知”降低打扰频率
### 5.3 @通知机制
#### @通知规则
- **无论用户是否设置免打扰**,被@时:
- 使用**手机上方消息栏**直接通知(不进入队列等待)。
- 通知带有声音提醒(用户可以自定义声音)。
- 不等待5分钟/1小时等时间窗口立即推送。
#### @通知展示位置
- @通知显示在手机顶部通知栏(系统级通知)。
---
## 六、消息通知总结
### 6.1 通知队列体系
系统维护以下通知队列:
1. **5分钟通知队列**(默认)
- 时间窗口早上8点 ~ 晚上7点。
- 每5分钟聚合通知一次。
2. **1小时通知队列**
- 时间窗口早上8点 ~ 晚上7点。
- 每1小时聚合通知一次。
3. **1天通知队列**
- 在指定时间点例如12点通知一次。
4. **3天通知队列**
- 在指定时间点例如14点通知一次。
5. **10天通知队列**
- 在指定时间点例如16点通知一次。
### 6.2 特殊通知(不受队列限制)
- **@通知**
- 立即推送,使用手机顶部通知栏。
- 带声音提醒。
- 不受免打扰设置影响。
### 6.3 通知聚合规则
- **未打开APP时**
- 所有群的新消息统一聚合为一条通知例如“您有X条新消息”
- 按用户所属队列的时间窗口进行通知。
- **打开APP后**
- 不再发送聚合通知。
- 用户可以在APP内查看各群的具体消息。
---
## 七、功能总结
### 7.1 身份标签与分类
- 9种身份标签入群时自选群主/管理员可最终确认和修改。
- 支持1~7个分类分类与标签独立成员可被放入任意分类。
### 7.2 管理员权限
- 10个权限点群主可灵活配置每个管理员的权限。
- 订单交付权限独立于管理员身份。
### 7.3 消息通知
- 5个通知队列5分钟/1小时/1天/3天/10天用户可自由选择。
- @通知立即推送,不受免打扰影响。
- 时间窗口控制,避免夜间打扰。
---
## 八、待定事项
- 待接单是否仅显示本群的订单(功能待定)。
---
**文档版本**v1.0
**最后更新**2024年

View File

@ -143,6 +143,10 @@
<artifactId>spring-test</artifactId> <artifactId>spring-test</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>com.ghy</groupId>
<artifactId>ghy-shop</artifactId>
</dependency>
</dependencies> </dependencies>

View File

@ -8,6 +8,8 @@ import com.ghy.common.adapay.model.PayCallback;
import com.ghy.common.adapay.model.PaymentDTO; import com.ghy.common.adapay.model.PaymentDTO;
import com.ghy.common.enums.PayStatus; import com.ghy.common.enums.PayStatus;
import com.ghy.order.domain.OrderAddSubtract; import com.ghy.order.domain.OrderAddSubtract;
import com.ghy.order.domain.OrderMaster;
import com.ghy.order.mapper.OrderMasterMapper;
import com.ghy.order.service.IOrderAddSubtractService; import com.ghy.order.service.IOrderAddSubtractService;
import com.ghy.order.service.OrderMasterService; import com.ghy.order.service.OrderMasterService;
import com.ghy.payment.domain.FinancialChangeRecord; import com.ghy.payment.domain.FinancialChangeRecord;
@ -39,6 +41,7 @@ public class PayCallbackService implements CallBackService {
@Resource @Resource
OrderMasterService orderMasterService; OrderMasterService orderMasterService;
@Resource @Resource
FinancialMasterService financialMasterService; FinancialMasterService financialMasterService;
@Resource @Resource
@ -97,6 +100,31 @@ public class PayCallbackService implements CallBackService {
financialMasterService.updatePay(relationId, paymentId, PayStatus.PAID.getCode()); financialMasterService.updatePay(relationId, paymentId, PayStatus.PAID.getCode());
log.info("主财务单[{}]支付成功", relationId); log.info("主财务单[{}]支付成功", relationId);
// 检查是否为商品订单且有关联的服务订单需要同步更新服务订单财务主单支付状态
try {
OrderMaster orderMaster = orderMasterService.selectById(financialMaster.getOrderMasterId());
if (orderMaster != null && orderMaster.getOrderType() == 1 && orderMaster.getHasServiceOrder() == 1) {
// 查询关联的服务订单
OrderMaster serviceOrder = orderMasterService.selectByGoodsOrderMasterId(orderMaster.getId());
if (serviceOrder != null) {
// 查询服务订单对应的财务主单
FinancialMaster serviceFinancialMaster = financialMasterService.selectByOrderMasterId(serviceOrder.getId());
if (serviceFinancialMaster != null && !PayStatus.PAID.getCode().equals(serviceFinancialMaster.getPayStatus())) {
// 更新服务订单财务主单支付状态
financialMasterService.updatePay(serviceFinancialMaster.getId(), paymentId, PayStatus.PAID.getCode());
// 更新服务订单支付状态
orderMasterService.updatePayStatus(serviceOrder.getId(), PayStatus.PAID.getCode());
log.info("商品订单[{}]关联的服务订单[{}]财务主单[{}]支付状态已同步更新为已支付",
orderMaster.getId(), serviceOrder.getId(), serviceFinancialMaster.getId());
}
}
}
} catch (Exception e) {
log.error("同步更新商品订单关联服务订单支付状态时发生异常商品订单ID: {}, 异常信息: {}",
financialMaster.getOrderMasterId(), e.getMessage(), e);
}
} else if (PaymentRelation.FINANCIAL_CHANGE.equals(relation.getRelationIdType())) { } else if (PaymentRelation.FINANCIAL_CHANGE.equals(relation.getRelationIdType())) {
// 更新加价单的支付信息 // 更新加价单的支付信息
@ -106,14 +134,14 @@ public class PayCallbackService implements CallBackService {
FinancialDetail fd = financialDetailService.selectByOrderDetailId(fc.getOrderDetailId()); FinancialDetail fd = financialDetailService.selectByOrderDetailId(fc.getOrderDetailId());
FinancialDetail fd2Update = new FinancialDetail(); FinancialDetail fd2Update = new FinancialDetail();
fd2Update.setId(fd.getId()); fd2Update.setId(fd.getId());
fd2Update.setPayMoney(fd.getPayMoney().add(fc.getChangeMoney())); fd2Update.setPayMoney(fd.getPayMoney());
fd2Update.setPayStatus(PayStatus.PAID.getCode()); fd2Update.setPayStatus(PayStatus.PAID.getCode());
financialDetailService.updateFinancialDetail(fd2Update); financialDetailService.updateFinancialDetail(fd2Update);
// 修改主单的payMoney // 修改主单的payMoney
FinancialMaster financialMaster = financialMasterService.selectById(fd.getFinancialMasterId()); FinancialMaster financialMaster = financialMasterService.selectById(fd.getFinancialMasterId());
FinancialMaster financialMaster2Update = new FinancialMaster(); FinancialMaster financialMaster2Update = new FinancialMaster();
financialMaster2Update.setId(financialMaster.getId()); financialMaster2Update.setId(financialMaster.getId());
financialMaster2Update.setPayMoney(financialMaster.getPayMoney().add(fc.getChangeMoney())); financialMaster2Update.setPayMoney(financialMaster.getPayMoney());
financialMaster2Update.setPayStatus(PayStatus.PAID.getCode()); financialMaster2Update.setPayStatus(PayStatus.PAID.getCode());
financialMasterService.updateFinancialMaster(financialMaster2Update); financialMasterService.updateFinancialMaster(financialMaster2Update);
// 更新主订单的支付信息 // 更新主订单的支付信息
@ -128,14 +156,14 @@ public class PayCallbackService implements CallBackService {
FinancialDetail fd = financialDetailService.selectByOrderDetailId(orderAdd.getOrderDetailId()); FinancialDetail fd = financialDetailService.selectByOrderDetailId(orderAdd.getOrderDetailId());
FinancialDetail fd2Update = new FinancialDetail(); FinancialDetail fd2Update = new FinancialDetail();
fd2Update.setId(fd.getId()); fd2Update.setId(fd.getId());
fd2Update.setPayMoney(fd.getPayMoney().add(orderAdd.getMoney())); fd2Update.setPayMoney(fd.getPayMoney());
fd2Update.setPayStatus(PayStatus.PAID.getCode()); fd2Update.setPayStatus(PayStatus.PAID.getCode());
financialDetailService.updateFinancialDetail(fd2Update); financialDetailService.updateFinancialDetail(fd2Update);
// 修改主单的payMoney // 修改主单的payMoney
FinancialMaster financialMaster = financialMasterService.selectById(fd.getFinancialMasterId()); FinancialMaster financialMaster = financialMasterService.selectById(fd.getFinancialMasterId());
FinancialMaster financialMaster2Update = new FinancialMaster(); FinancialMaster financialMaster2Update = new FinancialMaster();
financialMaster2Update.setId(financialMaster.getId()); financialMaster2Update.setId(financialMaster.getId());
financialMaster2Update.setPayMoney(financialMaster.getPayMoney().add(orderAdd.getMoney())); financialMaster2Update.setPayMoney(financialMaster.getPayMoney());
financialMaster2Update.setPayStatus(PayStatus.PAID.getCode()); financialMaster2Update.setPayStatus(PayStatus.PAID.getCode());
financialMasterService.updateFinancialMaster(financialMaster2Update); financialMasterService.updateFinancialMaster(financialMaster2Update);
log.info("订单追加[{}]支付成功", relationId); log.info("订单追加[{}]支付成功", relationId);

View File

@ -0,0 +1,576 @@
package com.ghy.web.controller;
import com.ghy.common.core.controller.BaseController;
import com.ghy.shop.domain.Shop;
import com.ghy.shop.domain.ShopDistanceQuery;
import com.ghy.shop.service.ShopService;
import com.ghy.common.core.domain.AjaxResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import org.springframework.web.client.RestTemplate;
import com.ghy.common.utils.LocationUtils;
import java.util.stream.Collectors;
import java.util.Comparator;
/**
* 店铺管理接口
*/
@RestController
@RequestMapping("/shop")
public class ShopController extends BaseController {
@Autowired
private ShopService shopService;
@Autowired
private RestTemplate restTemplate;
@PostMapping("/add")
public AjaxResult addShop(@RequestBody Shop shop) {
// 如果没有经纬度尝试通过地址解析获取
if ((shop.getLatitude() == null || shop.getLongitude() == null) &&
shop.getAddress() != null && !shop.getAddress().trim().isEmpty()) {
try {
Map<String, Double> coordinates = getCoordinatesByAddress(shop);
if (coordinates != null) {
shop.setLatitude(coordinates.get("latitude"));
shop.setLongitude(coordinates.get("longitude"));
logger.info("为店铺[{}]自动获取到坐标: 经度={}, 纬度={}",
shop.getShopName(), shop.getLongitude(), shop.getLatitude());
}
} catch (Exception e) {
logger.warn("获取地址经纬度失败: {}", e.getMessage());
}
}
int result = shopService.addShop(shop);
if (result > 0) {
Map<String, Object> response = new HashMap<>();
response.put("shopId", shop.getShopId());
response.put("shopName", shop.getShopName());
response.put("latitude", shop.getLatitude());
response.put("longitude", shop.getLongitude());
response.put("address", shop.getAddress());
return AjaxResult.success("店铺创建成功", response);
} else {
return AjaxResult.error("店铺创建失败");
}
}
@GetMapping("/list")
public AjaxResult listShops() {
List<Shop> list = shopService.listShops();
return AjaxResult.success(list);
}
@GetMapping("/{id}")
public AjaxResult getShop(@PathVariable Long id) {
Shop shop = shopService.getShop(id);
return shop != null ? AjaxResult.success(shop) : AjaxResult.error("未找到店铺");
}
@PostMapping("/update")
public AjaxResult updateShop(@RequestBody Shop shop) {
// 如果没有经纬度但有地址尝试通过地址解析获取经纬度
if ((shop.getLatitude() == null || shop.getLongitude() == null) &&
shop.getAddress() != null && !shop.getAddress().trim().isEmpty()) {
try {
Map<String, Double> coordinates = getCoordinatesByAddress(shop);
if (coordinates != null) {
shop.setLatitude(coordinates.get("latitude"));
shop.setLongitude(coordinates.get("longitude"));
}
} catch (Exception e) {
logger.warn("更新店铺时获取地址经纬度失败: {}", e.getMessage());
}
}
return toAjax(shopService.updateShop(shop));
}
@PostMapping("/delete/{id}")
public AjaxResult deleteShop(@PathVariable Long id) {
return toAjax(shopService.deleteShop(id));
}
@GetMapping("/{id}/location")
public AjaxResult getShopLocation(@PathVariable Long id) {
Shop shop = shopService.getShop(id);
if (shop == null) {
return AjaxResult.error("未找到店铺");
}
Map<String, Object> location = new HashMap<>();
location.put("address", shop.getAddress());
location.put("latitude", shop.getLatitude());
location.put("longitude", shop.getLongitude());
location.put("provinceName", shop.getProvinceName());
location.put("cityName", shop.getCityName());
location.put("countryName", shop.getCountryName());
location.put("streetName", shop.getStreetName());
return AjaxResult.success(location);
}
// @GetMapping("/getCurrentLocation")
// public AjaxResult getCurrentLocation() {
// try {
// // 直接调用百度地图API获取当前位置信息
// String url = "/tool/baidu/getLocation";
// return restTemplate.getForObject(url, AjaxResult.class);
// } catch (Exception e) {
// return AjaxResult.error("获取当前位置失败:" + e.getMessage());
// }
// }
@GetMapping("/worker/{workerId}")
public AjaxResult getShopsByWorkerId(@PathVariable Long workerId) {
List<Shop> shops = shopService.getShopsByWorkerId(workerId);
return AjaxResult.success(shops);
}
// /**
// * 通过地址获取经纬度坐标
// */
// @PostMapping("/getCoordinates")
// public AjaxResult getCoordinatesByAddress(@RequestBody Shop shop) {
// try {
// Map<String, Double> coordinates = getCoordinatesByAddress(shop);
// if (coordinates != null) {
// return AjaxResult.success("获取坐标成功", coordinates);
// } else {
// return AjaxResult.error("无法获取该地址的坐标信息");
// }
// } catch (Exception e) {
// logger.error("获取坐标失败: {}", e.getMessage(), e);
// return AjaxResult.error("获取坐标失败: " + e.getMessage());
// }
// }
/**
* 通过经纬度逆解析获取地址信息并保存到店铺
*/
@PostMapping("/saveShopByLocation")
public AjaxResult saveShopByLocation(@RequestBody Map<String, Object> params) {
try {
String location = params.get("location").toString(); // 格式: "经度,纬度"
// 调用百度地图逆解析API
String url = "/tool/baidu/getLocation";
Map<String, String> requestBody = new HashMap<>();
requestBody.put("location", location);
// 这里应该调用百度接口获取地址信息
// AjaxResult locationResult = restTemplate.postForObject(url, requestBody, AjaxResult.class);
// 解析经纬度
String[] coordinates = location.split(",");
if (coordinates.length != 2) {
return AjaxResult.error("location格式错误应为经度,纬度");
}
Double longitude = Double.parseDouble(coordinates[0]);
Double latitude = Double.parseDouble(coordinates[1]);
// 创建店铺对象
Shop shop = new Shop();
shop.setLongitude(longitude);
shop.setLatitude(latitude);
// 设置其他信息
if (params.containsKey("shopName")) {
shop.setShopName(params.get("shopName").toString());
}
if (params.containsKey("workerId")) {
shop.setWorkerId(Long.valueOf(params.get("workerId").toString()));
}
if (params.containsKey("phone")) {
shop.setPhone(params.get("phone").toString());
}
// 保存店铺
int result = shopService.addShop(shop);
if (result > 0) {
Map<String, Object> response = new HashMap<>();
response.put("shopId", shop.getShopId());
response.put("longitude", longitude);
response.put("latitude", latitude);
response.put("location", location);
return AjaxResult.success("店铺创建成功", response);
} else {
return AjaxResult.error("店铺创建失败");
}
} catch (Exception e) {
logger.error("通过位置创建店铺失败: {}", e.getMessage(), e);
return AjaxResult.error("创建失败: " + e.getMessage());
}
}
/**
* 内部方法通过地址解析获取经纬度
*/
private Map<String, Double> getCoordinatesByAddress(Shop shop) {
try {
// 构建完整地址
StringBuilder fullAddress = new StringBuilder();
if (shop.getProvinceName() != null) fullAddress.append(shop.getProvinceName());
if (shop.getCityName() != null) fullAddress.append(shop.getCityName());
if (shop.getCountryName() != null) fullAddress.append(shop.getCountryName());
if (shop.getStreetName() != null) fullAddress.append(shop.getStreetName());
if (shop.getAddress() != null) fullAddress.append(shop.getAddress());
String address = fullAddress.toString();
if (address.isEmpty()) {
return null;
}
logger.info("开始解析地址获取坐标: {}", address);
// TODO: 这里应该调用真实的地图API
// 示例百度地图正向地理编码API
// String url = "https://api.map.baidu.com/geocoding/v3/?address=" +
// java.net.URLEncoder.encode(address, "UTF-8") +
// "&output=json&ak=YOUR_BAIDU_API_KEY";
//
// String result = restTemplate.getForObject(url, String.class);
// JSONObject jsonResult = JSONObject.parseObject(result);
//
// if ("0".equals(jsonResult.getString("status"))) {
// JSONObject location = jsonResult.getJSONObject("result").getJSONObject("location");
// Double lng = location.getDouble("lng");
// Double lat = location.getDouble("lat");
//
// Map<String, Double> coordinates = new HashMap<>();
// coordinates.put("longitude", lng);
// coordinates.put("latitude", lat);
// return coordinates;
// }
// 临时返回示例坐标实际使用时请替换为真实的API调用
Map<String, Double> coordinates = new HashMap<>();
coordinates.put("latitude", 39.915 + Math.random() * 0.1); // 示例纬度
coordinates.put("longitude", 116.404 + Math.random() * 0.1); // 示例经度
logger.info("地址解析成功: {} -> 经度={}, 纬度={}",
address, coordinates.get("longitude"), coordinates.get("latitude"));
return coordinates;
} catch (Exception e) {
logger.error("地址解析失败: {}", e.getMessage(), e);
return null;
}
}
/**
* 手动设置店铺经纬度
*/
@PostMapping("/setCoordinates")
public AjaxResult setCoordinates(@RequestBody Map<String, Object> params) {
try {
Long shopId = Long.valueOf(params.get("shopId").toString());
Double latitude = Double.valueOf(params.get("latitude").toString());
Double longitude = Double.valueOf(params.get("longitude").toString());
Shop shop = new Shop();
shop.setShopId(shopId);
shop.setLatitude(latitude);
shop.setLongitude(longitude);
int result = shopService.updateShop(shop);
if (result > 0) {
Map<String, Object> response = new HashMap<>();
response.put("shopId", shopId);
response.put("latitude", latitude);
response.put("longitude", longitude);
return AjaxResult.success("坐标设置成功", response);
} else {
return AjaxResult.error("坐标设置失败");
}
} catch (Exception e) {
logger.error("设置坐标失败: {}", e.getMessage(), e);
return AjaxResult.error("设置坐标失败: " + e.getMessage());
}
}
/**
* 批量查询店铺包含经纬度信息
*/
@PostMapping("/listWithLocation")
public AjaxResult listShopsWithLocation(@RequestBody(required = false) Map<String, Object> params) {
try {
List<Shop> shops = shopService.listShops();
// 可以根据参数进行筛选
if (params != null) {
// 按城市筛选
if (params.containsKey("cityId")) {
Long cityId = Long.valueOf(params.get("cityId").toString());
shops = shops.stream()
.filter(shop -> shop.getCityId() != null && shop.getCityId().equals(cityId))
.collect(java.util.stream.Collectors.toList());
}
// 按师傅筛选
if (params.containsKey("workerId")) {
Long workerId = Long.valueOf(params.get("workerId").toString());
shops = shops.stream()
.filter(shop -> shop.getWorkerId() != null && shop.getWorkerId().equals(workerId))
.collect(java.util.stream.Collectors.toList());
}
// 只返回有坐标的店铺
if (params.containsKey("hasLocation") && Boolean.valueOf(params.get("hasLocation").toString())) {
shops = shops.stream()
.filter(shop -> shop.getLatitude() != null && shop.getLongitude() != null)
.collect(java.util.stream.Collectors.toList());
}
}
return AjaxResult.success(shops);
} catch (Exception e) {
logger.error("查询店铺列表失败: {}", e.getMessage(), e);
return AjaxResult.error("查询失败: " + e.getMessage());
}
}
/**
* 查询附近的店铺
*/
@PostMapping("/nearby")
public AjaxResult getNearbyShops(@RequestBody Map<String, Object> params) {
try {
Double latitude = Double.valueOf(params.get("latitude").toString());
Double longitude = Double.valueOf(params.get("longitude").toString());
Double radiusKm = params.containsKey("radiusKm") ?
Double.valueOf(params.get("radiusKm").toString()) : 5.0; // 默认5公里
// 验证坐标有效性
if (!LocationUtils.isValidCoordinate(latitude, longitude)) {
return AjaxResult.error("无效的坐标参数");
}
// 获取所有店铺
List<Shop> allShops = shopService.listShops();
// 筛选出有坐标的店铺并计算距离
List<Map<String, Object>> nearbyShops = allShops.stream()
.filter(shop -> LocationUtils.isValidCoordinate(shop.getLatitude(), shop.getLongitude()))
.map(shop -> {
double distance = LocationUtils.getDistanceInKilometers(
latitude, longitude, shop.getLatitude(), shop.getLongitude());
Map<String, Object> shopWithDistance = new HashMap<>();
shopWithDistance.put("shop", shop);
shopWithDistance.put("distance", distance);
shopWithDistance.put("distanceText", LocationUtils.formatDistance(distance * 1000));
// 计算方向
double bearing = LocationUtils.getBearing(latitude, longitude,
shop.getLatitude(), shop.getLongitude());
shopWithDistance.put("direction", LocationUtils.getDirectionDescription(bearing));
shopWithDistance.put("bearing", bearing);
return shopWithDistance;
})
.filter(shopMap -> (Double) shopMap.get("distance") <= radiusKm)
.sorted(Comparator.comparingDouble(shopMap -> (Double) shopMap.get("distance")))
.collect(Collectors.toList());
Map<String, Object> result = new HashMap<>();
result.put("centerLatitude", latitude);
result.put("centerLongitude", longitude);
result.put("radiusKm", radiusKm);
result.put("totalCount", nearbyShops.size());
result.put("shops", nearbyShops);
return AjaxResult.success("查询成功", result);
} catch (Exception e) {
logger.error("查询附近店铺失败: {}", e.getMessage(), e);
return AjaxResult.error("查询失败: " + e.getMessage());
}
}
/**
* 计算两个店铺之间的距离
*/
@PostMapping("/distance")
public AjaxResult getShopsDistance(@RequestBody Map<String, Object> params) {
try {
Long shopId1 = Long.valueOf(params.get("shopId1").toString());
Long shopId2 = Long.valueOf(params.get("shopId2").toString());
Shop shop1 = shopService.getShop(shopId1);
Shop shop2 = shopService.getShop(shopId2);
if (shop1 == null || shop2 == null) {
return AjaxResult.error("店铺不存在");
}
if (!LocationUtils.isValidCoordinate(shop1.getLatitude(), shop1.getLongitude()) ||
!LocationUtils.isValidCoordinate(shop2.getLatitude(), shop2.getLongitude())) {
return AjaxResult.error("店铺坐标信息不完整");
}
double distanceKm = LocationUtils.getDistanceInKilometers(
shop1.getLatitude(), shop1.getLongitude(),
shop2.getLatitude(), shop2.getLongitude());
double bearing = LocationUtils.getBearing(
shop1.getLatitude(), shop1.getLongitude(),
shop2.getLatitude(), shop2.getLongitude());
Map<String, Object> result = new HashMap<>();
result.put("shop1", shop1);
result.put("shop2", shop2);
result.put("distanceKm", distanceKm);
result.put("distanceText", LocationUtils.formatDistance(distanceKm * 1000));
result.put("direction", LocationUtils.getDirectionDescription(bearing));
result.put("bearing", bearing);
return AjaxResult.success("计算成功", result);
} catch (Exception e) {
logger.error("计算店铺距离失败: {}", e.getMessage(), e);
return AjaxResult.error("计算失败: " + e.getMessage());
}
}
/**
* 通过店铺ID查询详细信息并计算距离
* 最终效果
* 1. 传入的详细地址 转换为经纬度
* 2. 店铺有经纬度 直接使用
* 3. 店铺没有经纬度 通过详细地址生成经纬度
* 4. 最终通过经纬度计算距离 放在店铺的距离字段上
*
* @param query 距离查询请求实体
* @return 包含距离信息的完整店铺详情
*/
@PostMapping("/getShopDetailWithDistance")
public AjaxResult getShopDetailWithDistance(@RequestBody ShopDistanceQuery query) {
try {
// 验证参数
if (query.getShopId() == null) {
return AjaxResult.error("店铺ID不能为空");
}
// 验证是否有足够的信息来计算距离或获取位置
if (!query.hasEnoughInfo()) {
return AjaxResult.error("请提供经纬度或地址信息");
}
// 如果提供了经纬度验证其有效性
if (query.hasCoordinateInfo() && !LocationUtils.isValidCoordinate(query.getLatitude(), query.getLongitude())) {
return AjaxResult.error("无效的坐标参数");
}
// 验证地址参数如果提供了地址信息则验证其完整性
if (query.hasAddressInfo()) {
String fullAddress = query.getFullAddress();
if (fullAddress.trim().length() < 5) {
return AjaxResult.error("地址信息不完整,请提供更详细的地址");
}
logger.info("使用传入的地址信息: {}", fullAddress);
}
// 调用服务层方法获取店铺详情和距离
Shop shop = shopService.getShopWithDistance(query);
if (shop == null) {
return AjaxResult.error("未找到店铺");
}
// 检查距离是否有效
if (shop.getDistance() == null || shop.getDistance().equals("无法计算距离")) {
if (query.hasCoordinateInfo()) {
return AjaxResult.error("无法计算店铺距离,请检查店铺地址信息或提供更详细的地址参数");
} else {
// 如果没有经纬度只获取位置信息
logger.info("仅获取店铺位置信息,无法计算距离");
}
}
// 构建返回结果
Map<String, Object> result = new HashMap<>();
result.put("shopId", shop.getShopId());
result.put("shopName", shop.getShopName());
result.put("imageUrl", shop.getImageUrl());
result.put("workerId", shop.getWorkerId());
result.put("provinceId", shop.getProvinceId());
result.put("provinceName", shop.getProvinceName());
result.put("cityId", shop.getCityId());
result.put("cityName", shop.getCityName());
result.put("countryId", shop.getCountryId());
result.put("countryName", shop.getCountryName());
result.put("streetId", shop.getStreetId());
result.put("streetName", shop.getStreetName());
result.put("address", shop.getAddress());
result.put("phone", shop.getPhone());
result.put("latitude", shop.getLatitude());
result.put("longitude", shop.getLongitude());
result.put("distance", shop.getDistance());
return AjaxResult.success("查询成功", result);
} catch (Exception e) {
logger.error("查询店铺详情失败: {}", e.getMessage(), e);
return AjaxResult.error("查询失败: " + e.getMessage());
}
}
// /**
// * 获取店铺覆盖范围内的其他店铺
// */
// @PostMapping("/coverage")
// public AjaxResult getShopsCoverage(@RequestBody Map<String, Object> params) {
// try {
// Long shopId = Long.valueOf(params.get("shopId").toString());
// Double radiusKm = params.containsKey("radiusKm") ?
// Double.valueOf(params.get("radiusKm").toString()) : 10.0; // 默认10公里
//
// Shop centerShop = shopService.getShop(shopId);
// if (centerShop == null) {
// return AjaxResult.error("店铺不存在");
// }
//
// if (!LocationUtils.isValidCoordinate(centerShop.getLatitude(), centerShop.getLongitude())) {
// return AjaxResult.error("中心店铺坐标信息不完整");
// }
//
// // 获取附近的店铺
// Map<String, Object> nearbyParams = new HashMap<>();
// nearbyParams.put("latitude", centerShop.getLatitude());
// nearbyParams.put("longitude", centerShop.getLongitude());
// radiusKm);
//
// AjaxResult nearbyResult = getNearbyShops(nearbyParams);
// if (nearbyResult.isSuccess()) {
// Map<String, Object> data = (Map<String, Object>) nearbyResult.get("data");
// List<Map<String, Object>> shops = (List<Map<String, Object>>) data.get("shops");
//
// // 排除自己
// shops = shops.stream()
// .filter(shopMap -> {
// Shop shop = (Shop) shopMap.get("shop");
// return !shop.getShopId().equals(shopId);
// })
// .collect(Collectors.toList());
//
// data.put("centerShop", centerShop);
// data.put("shops", shops);
// data.put("totalCount", shops.size());
//
// return AjaxResult.success("查询成功", data);
// } else {
// return nearbyResult;
// }
//
// } catch (Exception e) {
// logger.error("查询店铺覆盖范围失败: {}", e.getMessage(), e);
// return AjaxResult.error("查询失败: " + e.getMessage());
// }
// }
}

View File

@ -131,6 +131,7 @@ public class CustomerAddressController extends BaseController {
@PostMapping("/update") @PostMapping("/update")
@ResponseBody @ResponseBody
AjaxResult updateCustomerAddress(@RequestBody CustomerAddress customerAddress){ AjaxResult updateCustomerAddress(@RequestBody CustomerAddress customerAddress){
logger.info("地址库的订单修改方法{}",customerAddress);
try { try {
// 判断下当前是否是默认地址 // 判断下当前是否是默认地址
if(ObjectUtil.equals(customerAddress.getIsDefault(), 1)){ if(ObjectUtil.equals(customerAddress.getIsDefault(), 1)){

View File

@ -1,6 +1,7 @@
package com.ghy.web.controller.customer; package com.ghy.web.controller.customer;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import com.ghy.common.exception.base.BaseException; import com.ghy.common.exception.base.BaseException;
@ -152,12 +153,13 @@ public class CustomerSelectionController extends BaseController
hisParam.setSelectionType(customerSelection.getSelectionType()); hisParam.setSelectionType(customerSelection.getSelectionType());
List<CustomerSelection> hisList = customerSelectionService.selectCustomerSelectionList(hisParam); List<CustomerSelection> hisList = customerSelectionService.selectCustomerSelectionList(hisParam);
if(!CollectionUtils.isEmpty(hisList)){ if(!CollectionUtils.isEmpty(hisList)){
List<String> hisIds = hisList.stream().map(CustomerSelection::getId).collect(Collectors.toList()); List<String> hisIds = hisList.stream()
.filter(customerSelection1 -> Objects.equals(customerSelection1.getType(), customerSelection.getType())).map(CustomerSelection::getId).collect(Collectors.toList());
StringBuilder ids = new StringBuilder(); StringBuilder ids = new StringBuilder();
hisIds.forEach(model->{ hisIds.forEach(model->{
ids.append(model.trim()).append(","); ids.append(model.trim()).append(",");
}); });
if(!StringUtil.isEmpty(ids)){ if(!StringUtil.isEmpty(ids)&&ids.length()>0){
String idString = ids.substring(0, ids.length()-1); String idString = ids.substring(0, ids.length()-1);
customerSelectionService.deleteCustomerSelectionByIds(idString); customerSelectionService.deleteCustomerSelectionByIds(idString);
} }

View File

@ -181,6 +181,25 @@ public class GoodsCategoryController extends BaseController {
return goodsCategoryService.selectCategoryTree(new GoodsCategory()); return goodsCategoryService.selectCategoryTree(new GoodsCategory());
} }
/**
* 选择服务类目树
*/
@GetMapping("/selectServiceCategoryTree")
public String selectServiceCategoryTree(ModelMap mmap) {
return PREFIX + "/serviceTree";
}
/**
* 获取服务类目树数据
*/
@GetMapping("/serviceTreeData")
@ResponseBody
public List<Ztree> serviceTreeData() {
GoodsCategory category = new GoodsCategory();
category.setType(1); // 1表示服务类
return goodsCategoryService.selectCategoryTree(category);
}
/** /**
* 商品类别表 * 商品类别表
*/ */

View File

@ -10,8 +10,13 @@ import com.ghy.common.utils.ShiroUtils;
import com.ghy.common.utils.StringUtils; import com.ghy.common.utils.StringUtils;
import com.ghy.common.utils.bean.BeanUtils; import com.ghy.common.utils.bean.BeanUtils;
import com.ghy.common.utils.poi.ExcelUtil; import com.ghy.common.utils.poi.ExcelUtil;
import com.ghy.common.utils.LocationUtils;
import com.ghy.common.utils.http.HttpUtils;
import com.alibaba.fastjson.JSONObject;
import com.ghy.goods.domain.*; import com.ghy.goods.domain.*;
import com.ghy.goods.service.*; import com.ghy.goods.service.*;
import com.ghy.shop.domain.Shop;
import com.ghy.shop.service.ShopService;
import com.ghy.system.domain.SysArea; import com.ghy.system.domain.SysArea;
import com.ghy.system.service.ISysAreaService; import com.ghy.system.service.ISysAreaService;
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel;
@ -26,6 +31,7 @@ import javax.annotation.Resource;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import com.ghy.common.utils.BaiduMapUtils;
@Controller @Controller
@RequestMapping("/goods/goods") @RequestMapping("/goods/goods")
@ -51,6 +57,10 @@ public class GoodsController extends BaseController {
private IInsuranceManagerService insuranceManagerService; private IInsuranceManagerService insuranceManagerService;
@Resource @Resource
private IDeptCategoryInsuranceRelationService deptCategoryInsuranceRelationService; private IDeptCategoryInsuranceRelationService deptCategoryInsuranceRelationService;
@Resource
private ShopService shopService;
@Resource
private BaiduMapUtils baiduMapUtils;
@RequiresPermissions("goods:goods:view") @RequiresPermissions("goods:goods:view")
@GetMapping() @GetMapping()
@ -164,6 +174,15 @@ public class GoodsController extends BaseController {
@PostMapping("/app/list") @PostMapping("/app/list")
@ResponseBody @ResponseBody
public TableDataInfo appList(@RequestBody Goods goods) { public TableDataInfo appList(@RequestBody Goods goods) {
Integer type=goods.getType();
if (type==null){
goods.setType(1);
}
// 获取用户当前位置
Double userLatitude = goods.getLatitude();
Double userLongitude = goods.getLongitude();
// 判断类目id是否为第三级不是的话需要找到所有符合条件的第三级类目id作为新的条件 // 判断类目id是否为第三级不是的话需要找到所有符合条件的第三级类目id作为新的条件
if (goods.getDeptGoodsCategoryId() != null) { if (goods.getDeptGoodsCategoryId() != null) {
logger.info("入参:" + goods.getDeptGoodsCategoryId()); logger.info("入参:" + goods.getDeptGoodsCategoryId());
@ -202,9 +221,40 @@ public class GoodsController extends BaseController {
} }
} }
// 通过商品规格名称模糊查询
if (StringUtils.isNotEmpty(goods.getGoodsStandard())) {
logger.info("通过商品规格查询:{}", goods.getGoodsStandard());
List<GoodsStandard> matchedStandards = goodsStandardService.selectByStandardNameLike(goods.getGoodsStandard());
if (CollectionUtils.isNotEmpty(matchedStandards)) {
// 获取所有符合规格条件的商品ID
List<Long> goodsIds = matchedStandards.stream()
.map(GoodsStandard::getGoodsId)
.distinct()
.collect(Collectors.toList());
logger.info("通过规格查询到的商品ID列表{}", goodsIds);
// 设置商品ID筛选条件与现有的类目筛选条件组合使用
if (goods.getGoodsIds() == null) {
goods.setGoodsIds(goodsIds);
} else {
// 如果已有商品ID筛选条件取交集
goods.getGoodsIds().retainAll(goodsIds);
}
} else {
logger.info("未找到符合规格条件的商品,设置空结果");
// 如果没找到符合条件的规格设置一个不存在的商品ID
goods.setGoodsIds(Arrays.asList(-1L));
}
}
startPage(); startPage();
List<Goods> list = goodsService.selectGoodsList(goods); List<Goods> list = goodsService.selectGoodsList(goods);
logger.info("传入的类目id汇总{},传入的goods信息为{},获取到的所有商品{}",goods.getDeptGoodsCategoryIds(),goods,list);
// 用于缓存店铺信息避免重复查询
Map<Long, Shop> shopCache = new HashMap<>();
list.forEach(one -> { list.forEach(one -> {
// 补全商品服务区域 // 补全商品服务区域
List<GoodsArea> goodsAreas = goodsAreaService.selectByGoodsId(one.getGoodsId()); List<GoodsArea> goodsAreas = goodsAreaService.selectByGoodsId(one.getGoodsId());
@ -222,10 +272,89 @@ public class GoodsController extends BaseController {
one.setParGoodsCategoryId(parGoodsCategory.getGoodsCategoryId()); one.setParGoodsCategoryId(parGoodsCategory.getGoodsCategoryId());
one.setParGoodsCategoryName(parGoodsCategory.getGoodsCategoryName()); one.setParGoodsCategoryName(parGoodsCategory.getGoodsCategoryName());
} }
logger.debug("验证坐标是否合理: {}", LocationUtils.isValidCoordinate(userLatitude, userLongitude));
// 计算距离逻辑
if (LocationUtils.isValidCoordinate(userLatitude, userLongitude) && one.getShopId() != null) {
try {
// 从缓存获取店铺信息避免重复查询
Shop shop = shopCache.computeIfAbsent(one.getShopId(),
shopId -> shopService.getShop(shopId));
if (shop != null && LocationUtils.isValidCoordinate(shop.getLatitude(), shop.getLongitude())) {
// 计算距离
double distanceInMeters = LocationUtils.getDistanceInMeters(
userLatitude, userLongitude,
shop.getLatitude(), shop.getLongitude()
);
// 格式化距离并设置到商品对象
one.setDistance(LocationUtils.formatDistance(distanceInMeters));
logger.debug("商品[{}]距离用户: {}", one.getGoodsName(), one.getDistance());
} else {
// 店铺坐标不完整
one.setDistance(null);
}
} catch (Exception e) {
logger.warn("计算商品[{}]距离失败: {}", one.getGoodsName(), e.getMessage());
// 计算异常时设为null
one.setDistance(null);
}
} else {
// 用户未提供位置或商品无店铺信息
one.setDistance(null);
}
}); });
// 如果用户提供了位置信息按距离排序距离为null的排在最后
if (LocationUtils.isValidCoordinate(userLatitude, userLongitude)) {
list.sort((goods1, goods2) -> {
String distance1 = goods1.getDistance();
String distance2 = goods2.getDistance();
// 距离为null的排在最后
if (distance1 == null && distance2 == null) return 0;
if (distance1 == null) return 1;
if (distance2 == null) return -1;
// 解析距离数值进行比较
try {
double dist1 = parseDistanceToMeters(distance1);
double dist2 = parseDistanceToMeters(distance2);
return Double.compare(dist1, dist2);
} catch (Exception e) {
// 解析失败时保持原顺序
return 0;
}
});
logger.info("已按距离排序,用户位置: 纬度={}, 经度={}", userLatitude, userLongitude);
}
return getDataTable(list); return getDataTable(list);
} }
/**
* 解析距离字符串为米数用于排序
*/
private double parseDistanceToMeters(String distanceText) {
if (distanceText == null || distanceText.trim().isEmpty()) {
return Double.MAX_VALUE;
}
try {
if (distanceText.endsWith("")) {
return Double.parseDouble(distanceText.replace("", ""));
} else if (distanceText.endsWith("公里")) {
return Double.parseDouble(distanceText.replace("公里", "")) * 1000;
}
} catch (NumberFormatException e) {
logger.warn("无法解析距离字符串: {}", distanceText);
}
return Double.MAX_VALUE;
}
@PostMapping("/hot/list") @PostMapping("/hot/list")
@ResponseBody @ResponseBody
public TableDataInfo hotList(@RequestBody Goods goods) { public TableDataInfo hotList(@RequestBody Goods goods) {
@ -275,11 +404,225 @@ public class GoodsController extends BaseController {
} }
} }
/**
* 获取商品详情
*
* @param requestBody 请求参数
* @return 商品详情信息
*/
@PostMapping("/getDetail") @PostMapping("/getDetail")
@ResponseBody @ResponseBody
public AjaxResult getDetail(@RequestBody Goods goods) { public AjaxResult getDetail(@RequestBody JSONObject requestBody) {
try { try {
Goods result = goodsService.selectById(goods.getGoodsId()); // 从请求体中提取参数
Long goodsId = requestBody.getLong("goodsId");
Double userLatitude = requestBody.getDouble("latitude");
Double userLongitude = requestBody.getDouble("longitude");
String provinceName = requestBody.getString("provinceName");
String cityName = requestBody.getString("cityName");
String countryName = requestBody.getString("countryName");
String streetName = requestBody.getString("streetName");
String address = requestBody.getString("address");
if (goodsId == null) {
return AjaxResult.error("商品ID不能为空");
}
Goods result = goodsService.selectById(goodsId);
if (result == null) {
return AjaxResult.error("商品不存在");
}
// 如果用户没有提供经纬度但有详细地址则通过地址获取经纬度
if ((userLatitude == null || userLongitude == null) &&
(provinceName != null || cityName != null ||
countryName != null || streetName != null ||
address != null)) {
try {
// 使用BaiduMapUtils工具类获取经纬度
Map<String, Double> coordinates = baiduMapUtils.getCoordinatesByAddress(
provinceName, cityName, countryName, streetName, address
);
if (coordinates != null) {
userLongitude = coordinates.get("longitude");
userLatitude = coordinates.get("latitude");
logger.info("通过地址获取到用户经纬度: 经度={}, 纬度={}", userLongitude, userLatitude);
} else {
logger.warn("通过地址获取用户经纬度失败");
}
} catch (Exception e) {
logger.error("调用百度地图API异常: {}", e.getMessage(), e);
}
}
// 获取商品店铺信息
Shop goodsShop = null;
if (result.getShopId() != null) {
try {
goodsShop = shopService.getShop(result.getShopId());
} catch (Exception e) {
logger.warn("获取商品店铺信息失败: {}", e.getMessage());
}
}
// 获取服务店铺信息
Shop serviceShop = null;
if (result.getDeptGoodsCategoryId() != null) {
try {
// 1. 通过商品的类目ID获取类目信息
DeptGoodsCategory deptGoodsCategory = deptGoodsCategoryService.selectOneByGoodsCategoryId(result.getDeptGoodsCategoryId());
Long serviceCategoryId = null;
if (deptGoodsCategory != null) {
// 先检查当前类目是否有服务类目ID
if (deptGoodsCategory.getServiceCategoryId() != null) {
serviceCategoryId = deptGoodsCategory.getServiceCategoryId();
logger.debug("当前类目[{}]的服务类目ID: {}", deptGoodsCategory.getGoodsCategoryName(), serviceCategoryId);
} else {
// 如果当前类目没有服务类目ID查找上一级类目
logger.debug("当前类目[{}]未配置服务类目ID查找上一级类目", deptGoodsCategory.getGoodsCategoryName());
if (deptGoodsCategory.getParentCategoryId() != null) {
DeptGoodsCategory parentCategory = deptGoodsCategoryService.selectOneByGoodsCategoryId(deptGoodsCategory.getParentCategoryId());
if (parentCategory != null && parentCategory.getServiceCategoryId() != null) {
serviceCategoryId = parentCategory.getServiceCategoryId();
logger.debug("上一级类目[{}]的服务类目ID: {}", parentCategory.getGoodsCategoryName(), serviceCategoryId);
} else {
logger.debug("上一级类目[{}]也未配置服务类目ID", parentCategory != null ? parentCategory.getGoodsCategoryName() : "null");
}
} else {
logger.debug("当前类目没有父级类目");
}
}
}
if (serviceCategoryId != null) {
// 2. 通过服务类目ID查询所有使用该服务类目的商品
Goods queryGoods = new Goods();
queryGoods.setDeptGoodsCategoryId(serviceCategoryId);
queryGoods.setStatus(0); // 只查询上架的商品
List<Goods> goodsList = goodsService.selectGoodsList(queryGoods);
if (goodsList.size()==0){
DeptGoodsCategory deptGoodsCategory1=deptGoodsCategoryService.selectOneByGoodsCategoryId(serviceCategoryId);
serviceCategoryId=deptGoodsCategory1.getDeptGoodsCategoryId();
// 直接使用新方法获取商品列表
goodsList = goodsStandardService.selectGoodsByDeptGoodsCategoryId(serviceCategoryId);
// 过滤只保留上架的商品
goodsList = goodsList.stream()
.filter(goods -> goods.getStatus() != null && goods.getStatus() == 0)
.collect(Collectors.toList());
}
logger.info("获取到的服务类目id{} 取到的商品列表:{}", serviceCategoryId,goodsList);
// 3. 提取所有店铺ID去重
Set<Long> shopIds = goodsList.stream()
.filter(g -> g.getShopId() != null)
.map(Goods::getShopId)
.collect(Collectors.toSet());
logger.info("取到的店铺列表:{}", shopIds);
if (!shopIds.isEmpty()) {
// 找到最近的店铺
Shop nearestShop = null;
double minDistance = Double.MAX_VALUE;
for (Long shopId : shopIds) {
Shop shop = shopService.getShop(shopId);
if (shop != null && LocationUtils.isValidCoordinate(shop.getLatitude(), shop.getLongitude())) {
if (LocationUtils.isValidCoordinate(userLatitude, userLongitude)) {
try {
double distanceInMeters = LocationUtils.getDistanceInMeters(
userLatitude, userLongitude,
shop.getLatitude(), shop.getLongitude()
);
if (distanceInMeters < minDistance) {
minDistance = distanceInMeters;
nearestShop = shop;
}
} catch (Exception e) {
logger.warn("计算店铺[{}]距离失败: {}", shop.getShopName(), e.getMessage());
}
} else {
// 如果用户没有提供位置选择第一个有效店铺
if (nearestShop == null) {
nearestShop = shop;
}
}
}
}
serviceShop = nearestShop;
} else {
logger.debug("服务类目[{}]下没有找到商品", serviceCategoryId);
}
} else {
logger.debug("未找到有效的服务类目ID");
}
} catch (Exception e) {
logger.warn("获取服务店铺信息失败: {}", e.getMessage());
}
}
// 设置商品店铺和服务店铺信息
if(result.getType()==1){
serviceShop=goodsShop;
result.setServiceShop(serviceShop);
}else {
result.setShop(goodsShop);
}
// 计算距离逻辑
if (LocationUtils.isValidCoordinate(userLatitude, userLongitude)) {
// 计算服务店铺距离
if (serviceShop != null) {
try {
if (LocationUtils.isValidCoordinate(serviceShop.getLatitude(), serviceShop.getLongitude())) {
// 计算距离
double distanceInMeters = LocationUtils.getDistanceInMeters(
userLatitude, userLongitude,
serviceShop.getLatitude(), serviceShop.getLongitude()
);
// 格式化距离并设置到服务店铺对象
serviceShop.setDistance(LocationUtils.formatDistance(distanceInMeters));
logger.debug("商品[{}]服务店铺距离用户: {}", result.getGoodsName(), serviceShop.getDistance());
} else {
// 店铺坐标不完整
serviceShop.setDistance(null);
}
} catch (Exception e) {
logger.warn("计算商品[{}]服务店铺距离失败: {}", result.getGoodsName(), e.getMessage());
// 计算异常时设为null
serviceShop.setDistance(null);
}
}
// 计算商品店铺距离
if (goodsShop != null) {
try {
if (LocationUtils.isValidCoordinate(goodsShop.getLatitude(), goodsShop.getLongitude())) {
// 计算距离
double distanceInMeters = LocationUtils.getDistanceInMeters(
userLatitude, userLongitude,
goodsShop.getLatitude(), goodsShop.getLongitude()
);
// 格式化距离并设置到商品店铺对象
goodsShop.setDistance(LocationUtils.formatDistance(distanceInMeters));
logger.debug("商品[{}]商品店铺距离用户: {}", result.getGoodsName(), goodsShop.getDistance());
} else {
// 店铺坐标不完整
goodsShop.setDistance(null);
}
} catch (Exception e) {
logger.warn("计算商品[{}]商品店铺距离失败: {}", result.getGoodsName(), e.getMessage());
// 计算异常时设为null
goodsShop.setDistance(null);
}
}
}
result.setServiceShop(serviceShop);
// 补全商品类目及父级类目信息 // 补全商品类目及父级类目信息
GoodsCategory goodsCategory = goodsCategoryService.selectById(result.getDeptGoodsCategoryId()); GoodsCategory goodsCategory = goodsCategoryService.selectById(result.getDeptGoodsCategoryId());
@ -352,6 +695,186 @@ public class GoodsController extends BaseController {
} }
} }
/**
* 根据商品ID和用户位置获取服务店铺列表
* @return 服务店铺列表按距离排序
*/
@PostMapping("/getShopsByGoodsId")
@ResponseBody
public AjaxResult getShopsByGoodsId(@RequestBody JSONObject requestBody) {
try {
Long goodsId = requestBody.getLong("goodsId");
Double latitude = requestBody.getDouble("latitude");
Double longitude = requestBody.getDouble("longitude");
String provinceName = requestBody.getString("provinceName");
String cityName = requestBody.getString("cityName");
String countryName = requestBody.getString("countryName");
String streetName = requestBody.getString("streetName");
String address = requestBody.getString("address");
if (goodsId == null) {
return AjaxResult.error("商品ID不能为空");
}
// 如果用户没有提供经纬度但有详细地址则通过地址获取经纬度
if ((latitude == null || longitude == null) &&
(provinceName != null || cityName != null || countryName != null ||
streetName != null || address != null)) {
try {
// 使用BaiduMapUtils工具类获取经纬度
Map<String, Double> coordinates = baiduMapUtils.getCoordinatesByAddress(
provinceName, cityName, countryName, streetName, address
);
if (coordinates != null) {
longitude = coordinates.get("longitude");
latitude = coordinates.get("latitude");
logger.info("通过地址获取到用户经纬度: 经度={}, 纬度={}", longitude, latitude);
} else {
logger.warn("通过地址获取用户经纬度失败");
}
} catch (Exception e) {
logger.error("调用百度地图API异常: {}", e.getMessage(), e);
}
}
// 获取商品信息
Goods goods = goodsService.selectById(goodsId);
if (goods == null) {
return AjaxResult.error("商品不存在");
}
List<Shop> serviceShops = new ArrayList<>();
// 通过商品的服务类目获取服务店铺
if (goods.getDeptGoodsCategoryId() != null) {
try {
// 1. 通过商品的类目ID获取类目信息
DeptGoodsCategory deptGoodsCategory = deptGoodsCategoryService.selectOneByGoodsCategoryId(goods.getDeptGoodsCategoryId());
Long serviceCategoryId = null;
if (deptGoodsCategory != null) {
// 先检查当前类目是否有服务类目ID
if (deptGoodsCategory.getServiceCategoryId() != null) {
serviceCategoryId = deptGoodsCategory.getServiceCategoryId();
logger.debug("当前类目[{}]的服务类目ID: {}", deptGoodsCategory.getGoodsCategoryName(), serviceCategoryId);
} else {
// 如果当前类目没有服务类目ID查找上一级类目
logger.debug("当前类目[{}]未配置服务类目ID查找上一级类目", deptGoodsCategory.getGoodsCategoryName());
if (deptGoodsCategory.getParentCategoryId() != null) {
DeptGoodsCategory parentCategory = deptGoodsCategoryService.selectOneByGoodsCategoryId(deptGoodsCategory.getParentCategoryId());
if (parentCategory != null && parentCategory.getServiceCategoryId() != null) {
serviceCategoryId = parentCategory.getServiceCategoryId();
logger.debug("上一级类目[{}]的服务类目ID: {}", parentCategory.getGoodsCategoryName(), serviceCategoryId);
} else {
logger.debug("上一级类目[{}]也未配置服务类目ID", parentCategory != null ? parentCategory.getGoodsCategoryName() : "null");
}
} else {
logger.debug("当前类目没有父级类目");
}
}
}
List<Goods> goodsList = new ArrayList<>();
if (serviceCategoryId != null) {
// 2. 通过服务类目ID查询所有使用该服务类目的商品
Goods queryGoods = new Goods();
queryGoods.setDeptGoodsCategoryId(serviceCategoryId);
queryGoods.setStatus(0); // 只查询上架的商品
goodsList = goodsService.selectGoodsList(queryGoods);
if (goodsList.size() == 0) {
DeptGoodsCategory deptGoodsCategory1 = deptGoodsCategoryService.selectOneByGoodsCategoryId(serviceCategoryId);
serviceCategoryId = deptGoodsCategory1.getDeptGoodsCategoryId();
// 直接使用新方法获取商品列表
goodsList = goodsStandardService.selectGoodsByDeptGoodsCategoryId(serviceCategoryId);
// 过滤只保留上架的商品
goodsList = goodsList.stream()
.filter(g -> g.getStatus() != null && g.getStatus() == 0)
.collect(Collectors.toList());
}
logger.info("获取到的服务类目id{} 取到的商品列表:{}", serviceCategoryId, goodsList);
}
if (!goodsList.isEmpty()) {
logger.debug("通过商品ID[{}]找到{}个相关服务商品", goodsId, goodsList.size());
// 提取所有店铺ID去重
Set<Long> shopIds = goodsList.stream()
.filter(g -> g.getShopId() != null)
.map(Goods::getShopId)
.collect(Collectors.toSet());
if (!shopIds.isEmpty()) {
// 获取所有店铺信息并计算距离
for (Long shopId : shopIds) {
Shop shop = shopService.getShop(shopId);
if (shop != null) {
// 计算距离
if (LocationUtils.isValidCoordinate(latitude, longitude) &&
LocationUtils.isValidCoordinate(shop.getLatitude(), shop.getLongitude())) {
try {
double distanceInMeters = LocationUtils.getDistanceInMeters(
latitude, longitude,
shop.getLatitude(), shop.getLongitude()
);
shop.setDistance(LocationUtils.formatDistance(distanceInMeters));
} catch (Exception e) {
logger.warn("计算店铺[{}]距离失败: {}", shop.getShopName(), e.getMessage());
shop.setDistance(null);
}
} else {
shop.setDistance(null);
}
serviceShops.add(shop);
}
}
// 按距离排序有距离的排在前面然后按距离升序
serviceShops.sort((s1, s2) -> {
if (s1.getDistance() == null && s2.getDistance() == null) {
return 0;
}
if (s1.getDistance() == null) {
return 1;
}
if (s2.getDistance() == null) {
return -1;
}
// 提取距离数值进行比较
try {
double d1 = Double.parseDouble(s1.getDistance().replaceAll("[^0-9.]", ""));
double d2 = Double.parseDouble(s2.getDistance().replaceAll("[^0-9.]", ""));
return Double.compare(d1, d2);
} catch (Exception e) {
return 0;
}
});
}
} else {
logger.debug("未找到商品ID[{}]的相关服务商品", goodsId);
}
} catch (Exception e) {
logger.warn("获取服务店铺信息失败: {}", e.getMessage());
}
}
JSONObject result = new JSONObject();
result.put("goodsId", goodsId);
result.put("goodsName", goods.getGoodsName());
result.put("userLatitude", latitude);
result.put("userLongitude", longitude);
result.put("serviceShops", serviceShops);
result.put("totalCount", serviceShops.size());
return AjaxResult.success(result);
} catch (Exception e) {
logger.error("获取服务店铺列表失败: {}", e.getMessage(), e);
return AjaxResult.error("获取服务店铺列表失败: " + ExceptionUtil.getExceptionMessage(e));
}
}
@Log(title = "商品管理", businessType = BusinessType.EXPORT) @Log(title = "商品管理", businessType = BusinessType.EXPORT)
@RequiresPermissions("goods:goods:export") @RequiresPermissions("goods:goods:export")
@PostMapping("/export") @PostMapping("/export")
@ -422,6 +945,13 @@ public class GoodsController extends BaseController {
@ResponseBody @ResponseBody
public AjaxResult editSave(@Validated Goods goods) { public AjaxResult editSave(@Validated Goods goods) {
goods.setUpdateBy(getLoginName()); goods.setUpdateBy(getLoginName());
// 处理Long类型字段的空值情况
// 当前端传入0或空字符串时将shopId设置为null以便清空
if (goods.getShopId() != null && goods.getShopId().equals(0L)) {
goods.setShopId(null);
}
return toAjax(goodsService.updateGoods(goods)); return toAjax(goodsService.updateGoods(goods));
} }
@ -429,6 +959,12 @@ public class GoodsController extends BaseController {
@ResponseBody @ResponseBody
public AjaxResult appEditSave(@RequestBody @Validated Goods goods) { public AjaxResult appEditSave(@RequestBody @Validated Goods goods) {
// 处理Long类型字段的空值情况
// 当前端传入0或空字符串时将shopId设置为null以便清空
if (goods.getShopId() != null && goods.getShopId().equals(0L)) {
goods.setShopId(null);
}
goodsService.edit(goods); goodsService.edit(goods);
return AjaxResult.success(); return AjaxResult.success();
} }
@ -488,4 +1024,6 @@ public class GoodsController extends BaseController {
return AjaxResult.error(ExceptionUtil.getExceptionMessage(e)); return AjaxResult.error(ExceptionUtil.getExceptionMessage(e));
} }
} }
} }

View File

@ -3,6 +3,7 @@ package com.ghy.web.controller.goods;
import com.ghy.common.core.controller.BaseController; import com.ghy.common.core.controller.BaseController;
import com.ghy.common.core.domain.AjaxResult; import com.ghy.common.core.domain.AjaxResult;
import com.ghy.common.core.page.TableDataInfo; import com.ghy.common.core.page.TableDataInfo;
import com.ghy.goods.domain.Goods;
import com.ghy.goods.domain.GoodsStandard; import com.ghy.goods.domain.GoodsStandard;
import com.ghy.goods.service.GoodsStandardService; import com.ghy.goods.service.GoodsStandardService;
import com.ghy.order.domain.OrderTemplate; import com.ghy.order.domain.OrderTemplate;
@ -64,4 +65,14 @@ public class GoodsStandardController extends BaseController {
return toAjax(goodsStandardService.save(goodsStandardList)); return toAjax(goodsStandardService.save(goodsStandardList));
} }
/**
* 根据部门商品分类ID获取商品列表
*/
@ResponseBody
@GetMapping("/goods/by-dept-category/{deptGoodsCategoryId}")
public AjaxResult getGoodsByDeptCategoryId(@PathVariable("deptGoodsCategoryId") Long deptGoodsCategoryId) {
List<Goods> goodsList = goodsStandardService.selectGoodsByDeptGoodsCategoryId(deptGoodsCategoryId);
return AjaxResult.success(goodsList);
}
} }

View File

@ -0,0 +1,88 @@
package com.ghy.web.controller.order;
import java.math.BigDecimal;
/**
* 售后纠纷处理请求
*
* @author system
* @date 2024-12-19
*/
public class AfterServiceDisputeRequest {
/**
* 售后记录ID
*/
private String recordId;
/**
* 主单ID
*/
private Long orderMasterId;
/**
* 子单ID
*/
private Long orderDetailId;
/**
* 退款金额
*/
private BigDecimal refundAmount;
/**
* 售后纠纷平台处理原因
*/
private String platformHandleReason;
public String getRecordId() {
return recordId;
}
public void setRecordId(String recordId) {
this.recordId = recordId;
}
public Long getOrderMasterId() {
return orderMasterId;
}
public void setOrderMasterId(Long orderMasterId) {
this.orderMasterId = orderMasterId;
}
public Long getOrderDetailId() {
return orderDetailId;
}
public void setOrderDetailId(Long orderDetailId) {
this.orderDetailId = orderDetailId;
}
public BigDecimal getRefundAmount() {
return refundAmount;
}
public void setRefundAmount(BigDecimal refundAmount) {
this.refundAmount = refundAmount;
}
public String getPlatformHandleReason() {
return platformHandleReason;
}
public void setPlatformHandleReason(String platformHandleReason) {
this.platformHandleReason = platformHandleReason;
}
@Override
public String toString() {
return "AfterServiceDisputeRequest{" +
"recordId='" + recordId + '\'' +
", orderMasterId=" + orderMasterId +
", orderDetailId=" + orderDetailId +
", refundAmount=" + refundAmount +
", platformHandleReason='" + platformHandleReason + '\'' +
'}';
}
}

View File

@ -141,6 +141,21 @@ public class AfterServiceRecordController extends BaseController {
} }
} }
/**
* 修改商品售后记录
*/
@PostMapping("/editGoods")
@ResponseBody
public AjaxResult editGoodsSave(@RequestBody AfterServiceRecord afterServiceRecord) {
logger.info("修改商品售后记录:{}", afterServiceRecord);
try {
return afterServiceRecordService.updateGoodsAfterServiceRecord(afterServiceRecord);
} catch (Exception exception) {
logger.error(ExceptionUtils.getStackTrace(exception));
return AjaxResult.error(exception.getMessage());
}
}
/** /**
* 删除售后记录 * 删除售后记录
*/ */
@ -151,4 +166,34 @@ public class AfterServiceRecordController extends BaseController {
public AjaxResult remove(String ids) { public AjaxResult remove(String ids) {
return toAjax(afterServiceRecordService.deleteAfterServiceRecordByIds(ids)); return toAjax(afterServiceRecordService.deleteAfterServiceRecordByIds(ids));
} }
/**
* 师傅重发/补发操作
* 师傅端点击重发补发按钮保存重发/补发方案
*/
@PostMapping("/workerResendPlan")
@ResponseBody
public AjaxResult workerResendPlan(@RequestBody AfterServiceRecord afterServiceRecord) {
return afterServiceRecordService.workerResendPlan(afterServiceRecord);
}
/**
* 退货操作
* 客户或师傅端进行退货操作保存退货信息
*/
@PostMapping("/returnGoods")
@ResponseBody
public AjaxResult returnGoods(@RequestBody AfterServiceRecord afterServiceRecord) {
return afterServiceRecordService.returnGoods(afterServiceRecord);
}
/**
* 师傅确认收货
* 师傅确认收到货物后根据同意处理方式决定是否执行退款
*/
@PostMapping("/workerConfirmReceive")
@ResponseBody
public AjaxResult workerConfirmReceive(@RequestBody AfterServiceRecord afterServiceRecord) {
return afterServiceRecordService.workerConfirmReceive(afterServiceRecord);
}
} }

View File

@ -1,6 +1,8 @@
package com.ghy.web.controller.order; package com.ghy.web.controller.order;
import java.util.List; import java.util.List;
import org.apache.ibatis.annotations.Param;
import org.apache.shiro.authz.annotation.RequiresPermissions; import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
@ -140,4 +142,16 @@ public class OrderAttachmentRecordController extends BaseController
{ {
return toAjax(orderAttachmentRecordService.deleteOrderAttachmentRecordByIds(ids)); return toAjax(orderAttachmentRecordService.deleteOrderAttachmentRecordByIds(ids));
} }
/**
* 删除附件费 根据子单id
*/
@PostMapping( "/deleteByDetailId")
@ResponseBody
public AjaxResult deleteByDetailId(@RequestParam Long orderDetailId)
{
return toAjax(orderAttachmentRecordService.deleteOrderAttachmentRecordByOrderDetailId(orderDetailId));
}
} }

View File

@ -78,8 +78,23 @@ public class AlipayController extends BaseController {
PayParam payParam = PayParam.delayPayParam(om.getCode() + "_" + System.currentTimeMillis(), payMoney, "商品标题", "商品描述信息"); PayParam payParam = PayParam.delayPayParam(om.getCode() + "_" + System.currentTimeMillis(), payMoney, "商品标题", "商品描述信息");
try { try {
JSONObject response = adapayService.alipayQrPay(om.getDeptId(), payParam, null, null, null); JSONObject response = adapayService.alipayQrPay(om.getDeptId(), payParam, null, null, null);
boolean status = AdapayStatusEnum.succeeded.code.equals(response.getString("status"));
if (!status) {
logger.error("创建支付失败: {}", response.toJSONString());
return AjaxResult.error("网络不佳 请稍后再试");
}
// 支付二维码创建成功 保存一下paymentId
String paymentId = response.getString("id");
// 更新财务主单的paymentId
FinancialMaster fm2update = new FinancialMaster();
fm2update.setId(fm.getId());
fm2update.setPaymentId(paymentId);
fm2update.setPayType(PayTypeEnum.ALIPAY_QR.getCode());
financialMasterService.updateFinancialMaster(fm2update);
// 保存支付ID与主财务单ID到关系表 // 保存支付ID与主财务单ID到关系表
paymentRelationService.insert(new PaymentRelation(null, fm.getId(), PaymentRelation.FINANCIAL_MASTER, fm.getPayMoney())); PaymentRelation relation = new PaymentRelation(null, fm.getId(), PaymentRelation.FINANCIAL_MASTER, fm.getPayMoney());
relation.setPaymentId(paymentId);
paymentRelationService.insert(relation);
return AjaxResult.success(response); return AjaxResult.success(response);
} catch (BaseAdaPayException e) { } catch (BaseAdaPayException e) {
logger.error("创建支付失败", e); logger.error("创建支付失败", e);

View File

@ -107,6 +107,25 @@ public class WxPayController extends BaseController {
String.valueOf(payMoney), "工圈子居家设备", "工圈子居家设备购买付费"); String.valueOf(payMoney), "工圈子居家设备", "工圈子居家设备购买付费");
JSONObject response = adapayService.wxLitePay(orderMaster.getDeptId(), payParam, expend, null, null); JSONObject response = adapayService.wxLitePay(orderMaster.getDeptId(), payParam, expend, null, null);
String paymentId = response.getString("id"); String paymentId = response.getString("id");
// 更新财务主单的paymentId
if (PayStatus.WAIT_PAY.getCode().equals(financialMaster.getPayStatus())) {
FinancialMaster fm2update = new FinancialMaster();
fm2update.setId(financialMaster.getId());
fm2update.setPaymentId(paymentId);
financialMasterService.updateFinancialMaster(fm2update);
}
// 更新财务变更记录的paymentId
for (FinancialChangeRecord fcr : financialChangeRecords) {
if (PayStatus.WAIT_PAY.getCode().equals(fcr.getPayStatus())) {
FinancialChangeRecord fcr2update = new FinancialChangeRecord();
fcr2update.setId(fcr.getId());
fcr2update.setPaymentId(paymentId);
financialChangeRecordService.update(fcr2update);
}
}
// 保存支付ID与订单ID到关系表 // 保存支付ID与订单ID到关系表
for (PaymentRelation relation : relations) { for (PaymentRelation relation : relations) {
relation.setPaymentId(paymentId); relation.setPaymentId(paymentId);

View File

@ -17,6 +17,9 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
/** /**
* 百度地图逆解析 * 百度地图逆解析
* @author clunt * @author clunt
@ -38,12 +41,34 @@ public class BaiduController extends BaseController {
JSONObject json = new JSONObject(); JSONObject json = new JSONObject();
String location = jsonObject.getString("location"); String location = jsonObject.getString("location");
// 解析经纬度
String[] coordinates = location.split(",");
if (coordinates.length != 2) {
return AjaxResult.error("location格式错误应为经度,纬度");
}
try {
double longitude = Double.parseDouble(coordinates[1]); // 经度
double latitude = Double.parseDouble(coordinates[0]); // 纬度
// 将经纬度添加到返回结果中
json.put("longitude", longitude);
json.put("latitude", latitude);
json.put("coordinates", coordinates);
logger.info("解析到的坐标 - 经度: {}, 纬度: {}", longitude, latitude);
} catch (NumberFormatException e) {
return AjaxResult.error("经纬度格式错误");
}
String url = baiduConfig.getUrl().replace("#AK#", baiduConfig.getAk()) + location; String url = baiduConfig.getUrl().replace("#AK#", baiduConfig.getAk()) + location;
String result = HttpUtils.sendGet(url); String result = HttpUtils.sendGet(url);
result = result.replaceAll("\n", "").replaceAll("\t", ""); result = result.replaceAll("\n", "").replaceAll("\t", "");
JSONObject resultJson = JSONObject.parseObject(result); JSONObject resultJson = JSONObject.parseObject(result);
if("0".equals(resultJson.getString("status"))){ if("0".equals(resultJson.getString("status"))){
JSONObject addressJson = resultJson.getJSONObject("result").getJSONObject("addressComponent"); JSONObject addressJson = resultJson.getJSONObject("result").getJSONObject("addressComponent");
logger.info("百度地图获取到的地址 :" + addressJson);
String provinceName = addressJson.getString("province"); String provinceName = addressJson.getString("province");
logger.info("provinceName :" + provinceName); logger.info("provinceName :" + provinceName);
SysArea provinceArea = iSysAreaService.selectByName(provinceName, null); SysArea provinceArea = iSysAreaService.selectByName(provinceName, null);
@ -53,9 +78,25 @@ public class BaiduController extends BaseController {
String countryName = addressJson.getString("district"); String countryName = addressJson.getString("district");
logger.info("countryName :" + countryName); logger.info("countryName :" + countryName);
SysArea countryArea = iSysAreaService.selectByName(countryName, cityArea.getAreaCode()); SysArea countryArea = iSysAreaService.selectByName(countryName, cityArea.getAreaCode());
String streetName = addressJson.getString("town");
logger.info("streetName :" + streetName);
SysArea streetArea = iSysAreaService.selectByName(streetName, countryArea.getAreaCode());
// 添加地址信息
json.put("provinceArea", provinceArea); json.put("provinceArea", provinceArea);
json.put("cityArea", cityArea); json.put("cityArea", cityArea);
json.put("countryArea", countryArea); json.put("countryArea", countryArea);
json.put("streetArea", streetArea);
// 添加完整地址
String fullAddress = (provinceName != null ? provinceName : "") +
(cityName != null ? cityName : "") +
(countryName != null ? countryName : "") +
(streetName != null ? streetName : "");
json.put("fullAddress", fullAddress);
// 保留原有的location字段用于兼容
json.put("location", location);
}else { }else {
return AjaxResult.error("Api服务异常!"); return AjaxResult.error("Api服务异常!");
} }
@ -67,6 +108,56 @@ public class BaiduController extends BaseController {
} }
} }
/**
* 百度地图正向地理编码地址 -> 经纬度
* 接收JSON{provinceName, cityName, countryName, streetName, address}
*/
@PostMapping("/geocode")
@ResponseBody
public AjaxResult geocode(@RequestBody JSONObject jsonObject) {
try {
String provinceName = jsonObject.getString("provinceName");
String cityName = jsonObject.getString("cityName");
String countryName = jsonObject.getString("countryName");
String streetName = jsonObject.getString("streetName");
String detailAddress = jsonObject.getString("address");
StringBuilder full = new StringBuilder();
if (provinceName != null) { full.append(provinceName); }
if (cityName != null) { full.append(cityName); }
if (countryName != null) { full.append(countryName); }
if (streetName != null) { full.append(streetName); }
if (detailAddress != null) { full.append(detailAddress); }
String address = full.toString();
if (address == null || address.trim().isEmpty()) {
return AjaxResult.error("地址不能为空");
}
String encoded = URLEncoder.encode(address, StandardCharsets.UTF_8.name());
// 使用百度正向地理编码API
String url = "https://api.map.baidu.com/geocoding/v3/?output=json&ak="
+ baiduConfig.getAk() + "&address=" + encoded;
String result = HttpUtils.sendGet(url);
result = result.replaceAll("\n", "").replaceAll("\t", "");
JSONObject resultJson = JSONObject.parseObject(result);
if ("0".equals(resultJson.getString("status"))) {
JSONObject location = resultJson.getJSONObject("result").getJSONObject("location");
JSONObject data = new JSONObject();
data.put("longitude", location.getBigDecimal("lng"));
data.put("latitude", location.getBigDecimal("lat"));
data.put("fullAddress", address);
return AjaxResult.success(data);
} else {
return AjaxResult.error("百度地理编码失败:" + resultJson.getString("msg"));
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
return AjaxResult.error(ExceptionUtil.getExceptionMessage(e));
}
}
public static void main(String[] args) { public static void main(String[] args) {
String result = " {\"status\":0,\"result\":{\"location\":{\"lng\":113.29950224957551,\"lat\":23.019282373814027},\"formatted_address\":\"广东省广州市番禺区安平路\",\"business\":\"大石\",\"addressComponent\":{\"country\":\"中国\",\"country_code\":0,\"country_code_iso\":\"CHN\",\"country_code_iso2\":\"CN\",\"province\":\"广东省\",\"city\":\"广州市\",\"city_level\":2,\"district\":\"番禺区\",\"town\":\"\",\"town_code\":\"\",\"distance\":\"\",\"direction\":\"\",\"adcode\":\"440113\",\"street\":\"安平路\",\"street_number\":\"\"},\"pois\":[],\"roads\":[],\"poiRegions\":[],\"sematic_description\":\"\",\"cityCode\":257}}\n"; String result = " {\"status\":0,\"result\":{\"location\":{\"lng\":113.29950224957551,\"lat\":23.019282373814027},\"formatted_address\":\"广东省广州市番禺区安平路\",\"business\":\"大石\",\"addressComponent\":{\"country\":\"中国\",\"country_code\":0,\"country_code_iso\":\"CHN\",\"country_code_iso2\":\"CN\",\"province\":\"广东省\",\"city\":\"广州市\",\"city_level\":2,\"district\":\"番禺区\",\"town\":\"\",\"town_code\":\"\",\"distance\":\"\",\"direction\":\"\",\"adcode\":\"440113\",\"street\":\"安平路\",\"street_number\":\"\"},\"pois\":[],\"roads\":[],\"poiRegions\":[],\"sematic_description\":\"\",\"cityCode\":257}}\n";

View File

@ -57,38 +57,135 @@ public class WorkerAreaController extends BaseController {
* 查询某个师傅的所有接单地区 * 查询某个师傅的所有接单地区
* *
* @param workerId 师傅ID * @param workerId 师傅ID
* @param serviceType 服务类型1=服务, 2=商品不传则查询所有
*/ */
@GetMapping("worker") @GetMapping("worker")
@ResponseBody @ResponseBody
public AjaxResult getByWorker(Long workerId) { public AjaxResult getByWorker(Long workerId, Integer serviceType) {
if (serviceType != null) {
// 根据服务类型查询
return AjaxResult.success(workerAreaService.getByWorkerIdAndType(workerId, serviceType));
} else {
// 查询所有区域保持向后兼容
return AjaxResult.success(workerAreaService.getByWorker(workerId)); return AjaxResult.success(workerAreaService.getByWorker(workerId));
} }
}
/**
* 查询某个师傅的服务区域类型1
*
* @param workerId 师傅ID
*/
@GetMapping("worker/service")
@ResponseBody
public AjaxResult getServiceAreasByWorker(Long workerId) {
return AjaxResult.success(workerAreaService.getByWorkerIdAndType(workerId, 1));
}
/**
* 查询某个师傅的商品区域类型2
*
* @param workerId 师傅ID
*/
@GetMapping("worker/goods")
@ResponseBody
public AjaxResult getGoodsAreasByWorker(Long workerId) {
return AjaxResult.success(workerAreaService.getByWorkerIdAndType(workerId, 2));
}
@GetMapping("worker/edit") @GetMapping("worker/edit")
@ResponseBody @ResponseBody
public AjaxResult getEditWorker(Long workerId){ public AjaxResult getEditWorker(Long workerId, Integer type){
Map<Long, List<String>> countryMap = new HashMap<>(); Map<Long, List<String>> countryMap = new HashMap<>();
List<WorkerArea> byWorker = workerAreaService.getByWorker(workerId);
// 需求1根据类型查询对应的区域数据
List<WorkerArea> byWorker;
if (Integer.valueOf(1).equals(type)) {
// 服务类型查询服务区域
byWorker = workerAreaService.getByWorkerIdAndType(workerId, 1);
} else if (Integer.valueOf(2).equals(type)) {
// 商品类型查询商品区域
byWorker = workerAreaService.getByWorkerIdAndType(workerId, 2);
} else {
// 不传类型查询所有区域
byWorker = workerAreaService.getByWorker(workerId);
}
// 收集只有cityId没有districtId的城市ID集合用于单独处理
Set<Long> cityOnlyIds = new HashSet<>();
// 按districtId分组收集cityIds
Map<Long, Set<Long>> districtCityMap = new HashMap<>();
for (WorkerArea area : byWorker){ for (WorkerArea area : byWorker){
Long districtId = area.getDistrictId();
// 如果有districtId按districtId分组处理
if (districtId != null) {
List<String> ids; List<String> ids;
if(countryMap.containsKey(area.getDistrictId())){ if(countryMap.containsKey(districtId)){
ids = countryMap.get(area.getDistrictId()); ids = countryMap.get(districtId);
}else { } else {
ids = new ArrayList<>(); ids = new ArrayList<>();
} }
ids.add(String.valueOf(area.getStreetId()));
countryMap.put(area.getDistrictId(), ids); // 收集该district下的城市ID
if (area.getCityId() != null) {
districtCityMap.computeIfAbsent(districtId, k -> new HashSet<>()).add(area.getCityId());
} }
// 添加街道ID
if (area.getStreetId() != null) {
ids.add(String.valueOf(area.getStreetId()));
}
countryMap.put(districtId, ids);
} else {
// 如果只有cityId没有districtId收集到cityOnlyIds
if (area.getCityId() != null) {
cityOnlyIds.add(area.getCityId());
}
}
}
List<WorkerArea> result = new ArrayList<>(); List<WorkerArea> result = new ArrayList<>();
// 处理有districtId的情况正常流程
for (Map.Entry<Long, List<String>> longListEntry : countryMap.entrySet()) { for (Map.Entry<Long, List<String>> longListEntry : countryMap.entrySet()) {
WorkerArea model = new WorkerArea(); WorkerArea model = new WorkerArea();
SysArea countryArea = sysAreaService.selectById(longListEntry.getKey()); Long districtId = longListEntry.getKey();
SysArea countryArea = sysAreaService.selectById(districtId);
model.setDistrictArea(countryArea); model.setDistrictArea(countryArea);
SysArea cityArea = sysAreaService.selectById(Long.valueOf(countryArea.getParentCode())); SysArea cityArea = sysAreaService.selectById(Long.valueOf(countryArea.getParentCode()));
model.setCityArea(cityArea); model.setCityArea(cityArea);
SysArea provinceArea = sysAreaService.selectById(Long.valueOf(cityArea.getParentCode())); SysArea provinceArea = sysAreaService.selectById(Long.valueOf(cityArea.getParentCode()));
model.setProvinceArea(provinceArea); model.setProvinceArea(provinceArea);
// 设置该district下的cityIds
Set<Long> districtCityIds = districtCityMap.getOrDefault(districtId, new HashSet<>());
List<Long> cityIdList = new ArrayList<>(districtCityIds);
model.setCityIds(cityIdList);
// 设置街道ID列表
model.setStreetIds(longListEntry.getValue()); model.setStreetIds(longListEntry.getValue());
result.add(model);
}
// 处理只有cityId没有districtId的情况只返回省市信息
for (Long cityId : cityOnlyIds) {
WorkerArea model = new WorkerArea();
SysArea cityArea = sysAreaService.selectById(cityId);
model.setCityArea(cityArea);
// 获取省份信息
SysArea provinceArea = sysAreaService.selectById(Long.valueOf(cityArea.getParentCode()));
model.setProvinceArea(provinceArea);
// 只设置城市ID列表
List<Long> cityIdList = new ArrayList<>();
cityIdList.add(cityId);
model.setCityIds(cityIdList);
// 不设置district和street信息
result.add(model); result.add(model);
} }
return AjaxResult.success(result); return AjaxResult.success(result);

View File

@ -162,6 +162,8 @@ public class WorkerCertificationController extends BaseController
@ResponseBody @ResponseBody
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public AjaxResult appAddCertify(@RequestBody WorkerCertification request) { public AjaxResult appAddCertify(@RequestBody WorkerCertification request) {
Worker worker = workerService.selectById(request.getWorkerId());
request.setWorkerPhone(worker != null ? worker.getPhone() : null);
// 将师傅状态设置为冻结 // 将师傅状态设置为冻结
// Worker worker = new Worker(); // Worker worker = new Worker();
// worker.setWorkerId(request.getWorkerId()); // worker.setWorkerId(request.getWorkerId());

View File

@ -85,7 +85,16 @@ public class WorkerController extends BaseController {
Assert.notNull(worker.getPhone(), "手机号码为空"); Assert.notNull(worker.getPhone(), "手机号码为空");
List<Worker> workerList = workerService.getWorkByPhoneAndPwd(worker); List<Worker> workerList = workerService.getWorkByPhoneAndPwd(worker);
if(workerList.size() > 0){ if(workerList.size() > 0){
return AjaxResult.success(workerList.get(0)); Worker loginWorker = workerList.get(0);
// 检查师傅状态0=生效,1=冻结,2=删除
if(loginWorker.getStatus() == WorkerStatus.DELETED.getCode()){
return AjaxResult.error("账户已被删除,无法登录!");
}
// 检查登录状态0=允许登录,1=禁止登录
if(loginWorker.getLoginStatus() != null && loginWorker.getLoginStatus() == 1){
return AjaxResult.error("账户登录已被禁用,无法登录!");
}
return AjaxResult.success(loginWorker);
}else { }else {
return AjaxResult.error("用户名或密码错误!"); return AjaxResult.error("用户名或密码错误!");
} }
@ -327,6 +336,7 @@ public class WorkerController extends BaseController {
startPage(); startPage();
// worker.setWorkerIds(CollectionUtils.isNotEmpty(resWorkerIds) ? resWorkerIds : null); // worker.setWorkerIds(CollectionUtils.isNotEmpty(resWorkerIds) ? resWorkerIds : null);
worker.setName(workerListRequest.getWorkerName()); worker.setName(workerListRequest.getWorkerName());
worker.setStatus(0);
List<Worker> list = workerService.getWorkList(worker); List<Worker> list = workerService.getWorkList(worker);
list.forEach(w -> { list.forEach(w -> {
Goods goods = new Goods(); Goods goods = new Goods();
@ -411,19 +421,106 @@ public class WorkerController extends BaseController {
} }
} }
@PostMapping("/changeLoginStatus")
@ResponseBody
public AjaxResult changeLoginStatus(Worker worker){
try {
workerService.updateWorker(worker);
return AjaxResult.success("登录状态修改成功");
}catch (Exception e){
logger.error(ExceptionUtil.getExceptionMessage(e));
return AjaxResult.error(e.getMessage());
}
}
@PostMapping("/settled") @PostMapping("/settled")
@ResponseBody @ResponseBody
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public AjaxResult settled(@RequestBody WorkerSettledRequest request) { public AjaxResult settled(@RequestBody WorkerSettledRequest request) {
// 入驻区域信息持久化 // 入驻服务区域信息持久化类型1
workerAreaService.updateWorkerServArea(request.getWorkerId(), request.getWorkerAreas()); workerAreaService.updateWorkerAreaByType(request.getWorkerId(), request.getWorkerAreas(), 1);
// 入驻服务品类信息持久化
workerGoodsCategoryService.updateWorkerGoodsCategory(request.getWorkerId(), request.getGoodsCategories()); // 入驻商品区域信息持久化类型2
workerAreaService.updateWorkerAreaByType(request.getWorkerId(), request.getWorkerGoodsAreas(), 2);
// 入驻服务类目信息持久化类型1
if (request.getServiceCategories() != null && !request.getServiceCategories().isEmpty()) {
// 使用对象数组方式
workerGoodsCategoryService.updateWorkerGoodsCategoryByType(request.getWorkerId(), request.getServiceCategories(), 1);
} else if (request.getServiceCategoryIds() != null && !request.getServiceCategoryIds().isEmpty()) {
// 使用ID数组方式兼容旧版本
List<WorkerGoodsCategory> serviceCategories = request.getServiceCategoryIds().stream()
.map(id -> {
WorkerGoodsCategory category = new WorkerGoodsCategory();
category.setGoodsCategoryId(id);
return category;
}).collect(Collectors.toList());
workerGoodsCategoryService.updateWorkerGoodsCategoryByType(request.getWorkerId(), serviceCategories, 1);
}
// 入驻商品类目信息持久化类型2
if (request.getGoodsCategories() != null && !request.getGoodsCategories().isEmpty()) {
// 使用对象数组方式
workerGoodsCategoryService.updateWorkerGoodsCategoryByType(request.getWorkerId(), request.getGoodsCategories(), 2);
} else if (request.getGoodsCategoryIds() != null && !request.getGoodsCategoryIds().isEmpty()) {
// 使用ID数组方式兼容旧版本
List<WorkerGoodsCategory> goodsCategories = request.getGoodsCategoryIds().stream()
.map(id -> {
WorkerGoodsCategory category = new WorkerGoodsCategory();
category.setGoodsCategoryId(id);
return category;
}).collect(Collectors.toList());
workerGoodsCategoryService.updateWorkerGoodsCategoryByType(request.getWorkerId(), goodsCategories, 2);
}
// 更新师傅入驻类型为服务商 // 更新师傅入驻类型为服务商
Worker worker = new Worker(); Worker worker = new Worker();
worker.setWorkerId(request.getWorkerId()); worker.setWorkerId(request.getWorkerId());
worker.setType(WorkerType.SP.getCode()); worker.setType(WorkerType.SP.getCode());
workerService.updateWorker(worker); workerService.updateWorker(worker);
return AjaxResult.success("保存成功"); return AjaxResult.success("保存成功");
} }
/**
* 通过师傅ID获取师傅详情
* @param workerId 师傅ID
* @return 师傅实体对象
*/
@GetMapping("/detail/{workerId}")
@ResponseBody
public AjaxResult getWorkerDetailById(@PathVariable("workerId") Long workerId) {
try {
// 参数校验
if (workerId == null) {
return AjaxResult.error("师傅ID不能为空");
}
// 查询师傅基本信息
Worker worker = workerService.selectById(workerId);
if (worker == null) {
return AjaxResult.error("师傅不存在");
}
return AjaxResult.success(worker);
} catch (Exception e) {
logger.error("获取师傅详情失败: " + ExceptionUtil.getExceptionMessage(e));
return AjaxResult.error("获取师傅详情失败: " + e.getMessage());
}
}
/**
* 删除师傅软删除
*/
@RequiresPermissions("worker:worker:remove")
@PostMapping("/remove")
@ResponseBody
public AjaxResult remove(String ids) {
try {
return toAjax(workerService.deleteWorkerByIds(ids));
} catch (Exception e) {
logger.error(ExceptionUtil.getExceptionMessage(e));
return AjaxResult.error("删除失败: " + e.getMessage());
}
}
} }

View File

@ -71,11 +71,20 @@ public class WorkerGoodsCategoryController extends BaseController {
* 查询某个师傅的所有服务类目 * 查询某个师傅的所有服务类目
* *
* @param workerId 师傅ID * @param workerId 师傅ID
* @param serviceType 服务类型1=服务, 2=商品不传则查询所有
*/ */
@GetMapping("worker") @GetMapping("worker")
@ResponseBody @ResponseBody
public AjaxResult getByWorker(Long workerId) { public AjaxResult getByWorker(Long workerId, Integer serviceType) {
List<WorkerGoodsCategory> list = workerGoodsCategoryService.getByWorker(workerId); List<WorkerGoodsCategory> list;
if (serviceType != null) {
// 根据服务类型查询
list = workerGoodsCategoryService.getByWorkerIdAndType(workerId, serviceType);
} else {
// 查询所有类目保持向后兼容
list = workerGoodsCategoryService.getByWorker(workerId);
}
for (WorkerGoodsCategory item: list) { for (WorkerGoodsCategory item: list) {
List<String> nameList = new ArrayList<String>(); List<String> nameList = new ArrayList<String>();
// 查询所有父级服务类目拼接服务名称 // 查询所有父级服务类目拼接服务名称
@ -89,14 +98,75 @@ public class WorkerGoodsCategoryController extends BaseController {
} }
return AjaxResult.success(list); return AjaxResult.success(list);
} }
/**
* 查询某个师傅的服务类目类型1
*
* @param workerId 师傅ID
*/
@GetMapping("worker/service")
@ResponseBody
public AjaxResult getServiceCategoriesByWorker(Long workerId) {
List<WorkerGoodsCategory> list = workerGoodsCategoryService.getByWorkerIdAndType(workerId, 1);
for (WorkerGoodsCategory item: list) {
List<String> nameList = new ArrayList<String>();
// 查询所有父级服务类目拼接服务名称
GoodsCategory goodsCategory = goodsCategoryService.selectById(item.getGoodsCategoryId());
while (goodsCategory.getParentCategoryId() != null) {
nameList.add(goodsCategory.getGoodsCategoryName());
goodsCategory = goodsCategoryService.selectById(goodsCategory.getParentCategoryId());
}
Collections.reverse(nameList);
item.setMergeName(StringUtils.join(nameList, Constants.JOIN_SYMBOL));
}
return AjaxResult.success(list);
}
/**
* 查询某个师傅的商品类目类型2
*
* @param workerId 师傅ID
*/
@GetMapping("worker/goods")
@ResponseBody
public AjaxResult getGoodsCategoriesByWorker(Long workerId) {
List<WorkerGoodsCategory> list = workerGoodsCategoryService.getByWorkerIdAndType(workerId, 2);
for (WorkerGoodsCategory item: list) {
List<String> nameList = new ArrayList<String>();
// 查询所有父级服务类目拼接服务名称
GoodsCategory goodsCategory = goodsCategoryService.selectById(item.getGoodsCategoryId());
while (goodsCategory.getParentCategoryId() != null) {
nameList.add(goodsCategory.getGoodsCategoryName());
goodsCategory = goodsCategoryService.selectById(goodsCategory.getParentCategoryId());
}
Collections.reverse(nameList);
item.setMergeName(StringUtils.join(nameList, Constants.JOIN_SYMBOL));
}
return AjaxResult.success(list);
}
@GetMapping("worker/edit") @GetMapping("worker/edit")
@ResponseBody @ResponseBody
public AjaxResult getEditWorker(Long workerId) { public AjaxResult getEditWorker(Long workerId, Integer type) {
Map<Long, List<String>> twoMap = new HashMap<>(); Map<Long, List<String>> twoMap = new HashMap<>();
List<WorkerGoodsCategory> list = workerGoodsCategoryService.getByWorker(workerId);
// 根据类型查询对应的商品分类数据
List<WorkerGoodsCategory> list;
if (Integer.valueOf(1).equals(type)) {
// 服务类型查询服务商品分类
list = workerGoodsCategoryService.getByWorkerIdAndType(workerId, 1);
} else if (Integer.valueOf(2).equals(type)) {
// 商品类型查询商品分类
list = workerGoodsCategoryService.getByWorkerIdAndType(workerId, 2);
} else {
// 不传类型查询所有商品分类
list = workerGoodsCategoryService.getByWorker(workerId);
}
if(CollectionUtils.isEmpty(list)){ if(CollectionUtils.isEmpty(list)){
return AjaxResult.success(); return AjaxResult.success();
} }
for (WorkerGoodsCategory item: list) { for (WorkerGoodsCategory item: list) {
List<String> ids; List<String> ids;
GoodsCategory goodsCategory = goodsCategoryService.selectById(item.getGoodsCategoryId()); GoodsCategory goodsCategory = goodsCategoryService.selectById(item.getGoodsCategoryId());
@ -108,6 +178,7 @@ public class WorkerGoodsCategoryController extends BaseController {
ids.add(String.valueOf(goodsCategory.getGoodsCategoryId())); ids.add(String.valueOf(goodsCategory.getGoodsCategoryId()));
twoMap.put(goodsCategory.getParentCategoryId(), ids); twoMap.put(goodsCategory.getParentCategoryId(), ids);
} }
List<WorkerGoodsCategory> result = new ArrayList<>(); List<WorkerGoodsCategory> result = new ArrayList<>();
for (Map.Entry<Long, List<String>> longListEntry : twoMap.entrySet()) { for (Map.Entry<Long, List<String>> longListEntry : twoMap.entrySet()) {
WorkerGoodsCategory model = new WorkerGoodsCategory(); WorkerGoodsCategory model = new WorkerGoodsCategory();

View File

@ -8,6 +8,7 @@ import java.math.BigDecimal;
/** /**
* 改价请求实体 * 改价请求实体
*
* @author clunt * @author clunt
*/ */
@Data @Data
@ -21,4 +22,10 @@ public class OrderChangePriceRequest implements Serializable {
private Integer type; private Integer type;
private String remark; private String remark;
private String urls;
private String fileNames;
private String reason;
} }

View File

@ -36,4 +36,6 @@ public class OrderListRequest {
private Integer timeout; private Integer timeout;
private Boolean needImgs = true; private Boolean needImgs = true;
private Integer orderType;
} }

View File

@ -2,12 +2,15 @@ package com.ghy.web.pojo.vo;
import com.alibaba.fastjson.annotation.JSONField; import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
import com.ghy.common.annotation.Excel;
import com.ghy.goods.domain.Goods; import com.ghy.goods.domain.Goods;
import com.ghy.goods.domain.GoodsArea; import com.ghy.goods.domain.GoodsArea;
import com.ghy.goods.domain.InsuranceManager; import com.ghy.goods.domain.InsuranceManager;
import com.ghy.order.domain.AfterServiceRecord; import com.ghy.order.domain.AfterServiceRecord;
import com.ghy.payment.domain.FinancialChangeRecord; import com.ghy.payment.domain.FinancialChangeRecord;
import com.ghy.payment.domain.OrderTimeoutRecord; import com.ghy.payment.domain.OrderTimeoutRecord;
import com.ghy.shop.domain.Shop;
import lombok.Data; import lombok.Data;
import java.math.BigDecimal; import java.math.BigDecimal;
@ -30,12 +33,16 @@ public class OrderListResponse {
private String orderMasterCode; private String orderMasterCode;
private String originalOrderMasterCode;
private Long orderDetailId; private Long orderDetailId;
private String orderDetailCode; private String orderDetailCode;
private Long workerId; private Long workerId;
private Long masterWorkerId;
private String workerName; private String workerName;
private String workerPhone; private String workerPhone;
@ -46,6 +53,8 @@ public class OrderListResponse {
private String masterCompanyName; private String masterCompanyName;
private String masterCompanyPhone;
private String customerName; private String customerName;
private String customerPhone; private String customerPhone;
@ -82,6 +91,11 @@ public class OrderListResponse {
private Integer payStatus; private Integer payStatus;
/**
* 退款支付状态0=未支付1=已支付
*/
private Integer refundPayStatus;
private Integer payType; private Integer payType;
private BigDecimal totalMoney; private BigDecimal totalMoney;
@ -163,6 +177,12 @@ public class OrderListResponse {
*/ */
private Integer timeoutFineTimes; private Integer timeoutFineTimes;
/**
* 超时时间用于排序
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date timeoutTime;
private Integer afterTimeout; private Integer afterTimeout;
private String orderMode; private String orderMode;
@ -198,4 +218,122 @@ public class OrderListResponse {
private Long countryId; private Long countryId;
/**
* 是否为监控但
* */
private Boolean isMonitoredOrder;
private BigDecimal addMoney;
private String addMoneyRemark;
private BigDecimal paymentMoney;
/**
* 是否已派发服务订单0=未派发1=已派发
*/
private Integer hasServiceOrder;
/**
* 下单图片
*/
@Excel(name = "下单图片", cellType = Excel.ColumnType.STRING)
private String orderImages;
/**
* 是否发货到服务店0=1=
*/
@Excel(name = "是否发货到服务店", cellType = Excel.ColumnType.NUMERIC)
private Integer isDeliveryToStore;
/**
* 是否已开票0=1=
*/
@Excel(name = "是否已开票", cellType = Excel.ColumnType.NUMERIC, readConverterExp = "0=是,1=否")
private Integer isInvoiced;
/**
* 是否需要开票0=不需要1=需要
*/
@Excel(name = "是否需要开票", cellType = Excel.ColumnType.NUMERIC, readConverterExp = "0=不需要,1=需要")
private Integer isNeedBill;
/**
* 原师傅id转单前的师傅
*/
@Excel(name = "原师傅id", cellType = Excel.ColumnType.NUMERIC)
private Long originalWorkerId;
private Long serverGoodsId;
/**
* 服务店铺ID
*/
private Long serviceShopId;
private Date confirmStartTime;
private Integer deliveryType;
private Long goodsId;
/**
* 发货备注
*/
@Excel(name = "发货备注", cellType = Excel.ColumnType.STRING)
private String deliveryRemark;
/**
* 发货图片
*/
@Excel(name = "发货图片", cellType = Excel.ColumnType.STRING)
private String deliveryImages;
@Excel(name = "交货图片", cellType = Excel.ColumnType.STRING)
private String handoverImages;
@Excel(name = "交货备注", cellType = Excel.ColumnType.STRING)
private String handoverRemark;
/**
* 快递单号
*/
private String trackingNumber;
/**
* 分账倒计时结束时间
*/
@Excel(name = "分账倒计时结束时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date shareAccountCountdownEndTime;
/**
* 分账倒计时时长小时
*/
@Excel(name = "分账倒计时时长(小时)")
private Integer shareAccountCountdownDuration;
private Shop shop;
private Shop serviceShop;
private Long goodsOrderMasterId;
/**
* 是否已撤销服务主单0=未撤销1=已撤销
*/
@Excel(name = "是否已撤销服务主单", cellType = Excel.ColumnType.NUMERIC, readConverterExp = "0=未撤销,1=已撤销")
private Integer serviceCancelled;
/**
* 是否显示售后记录0=不显示1=显示
*/
private Integer showAfterServiceRecord = 0;
@Excel(name = "是否显示在监控单", cellType = Excel.ColumnType.NUMERIC, readConverterExp = "0=不显示,1=显示在监控单")
private Integer showInMonitor;
@Excel(name = "售后状态0-无售后1-售后纠纷,2-售后已完成,3-售后已取消")
private Integer afterPlatformServiceStatus;
} }

View File

@ -82,4 +82,56 @@ public class OrderStandardDetail {
private Integer afterTimeout; private Integer afterTimeout;
private BigDecimal addMoney;
/**
* 退单原因
*/
private String returnReason;
/**
* 退单原因详情
*/
private String returnReasonDetail;
/**
* 退单图片
*/
private String returnImages;
/**
* 发货类型 - 订单的发货方式
*/
private Integer deliveryType;
/**
* 发货备注 - 发货相关备注信息
*/
private String deliveryRemark;
/**
* 发货图片 - 发货凭证图片
*/
private String deliveryImages;
/**
* 快递单号 - 物流跟踪单号
*/
private String trackingNumber;
private String handoverImages;
private String handoverRemark;
private String orderImages;
//= "售后状态0-无售后1-售后纠纷,2-售后已完成,3-售后已取消"
private Integer afterPlatformServiceStatus;
private Integer payStatus;
private Integer refundPayStatus;
} }

View File

@ -13,4 +13,6 @@ public class OrderStatisticsRequest {
private Long workerId; private Long workerId;
private Long deptId; private Long deptId;
private Integer orderType;
} }

View File

@ -21,9 +21,21 @@ public class WorkerSettledRequest {
// 入驻区域 // 入驻区域
private List<WorkerArea> workerAreas; private List<WorkerArea> workerAreas;
// 服务品类 // 入驻商品区域
private List<WorkerArea> workerGoodsAreas;
// 入驻服务类目
private List<WorkerGoodsCategory> serviceCategories;
// 入驻商品类目
private List<WorkerGoodsCategory> goodsCategories; private List<WorkerGoodsCategory> goodsCategories;
// 入驻服务类目ID列表兼容旧版本
private List<Long> serviceCategoryIds;
// 入驻商品类目ID列表兼容旧版本
private List<Long> goodsCategoryIds;
// 特殊技能 // 特殊技能
// private List<WorkerSpecialSkill> specialSkills; // private List<WorkerSpecialSkill> specialSkills;
} }

View File

@ -116,7 +116,7 @@ qiniu:
accessKey: 'QTNOppkvtufxTxLjt1V7YZwvzV2Rc6WLD5yXLBVY' accessKey: 'QTNOppkvtufxTxLjt1V7YZwvzV2Rc6WLD5yXLBVY'
secretKey: 'V8SM9nkbO-dft4JmG7UaCH6RYxXdqzrvQ0zWO2W3' secretKey: 'V8SM9nkbO-dft4JmG7UaCH6RYxXdqzrvQ0zWO2W3'
bucketName: 'gqz' bucketName: 'gqz'
mediaUrl: 'http://gqz.opsoul.com/' mediaUrl: 'https://gqz.opsoul.com/'
adapay: adapay:
debug: true debug: true
@ -130,7 +130,7 @@ jim:
# 百度地图应用api # 百度地图应用api
baidu: baidu:
ak: 'ZQTgMW7W0GTuE7Ripb0HDp5TqRaOI6PZ' ak: 'i0sdnIsMmJVik7vJhfqMHA6DmS6d0fMB'
url: 'https://api.map.baidu.com/reverse_geocoding/v3/?ak=#AK#&output=json&coordtype=wgs84ll&location=' url: 'https://api.map.baidu.com/reverse_geocoding/v3/?ak=#AK#&output=json&coordtype=wgs84ll&location='
sms: sms:
@ -145,3 +145,24 @@ aliyun:
accessSecret: EV4dzWRfKTQaPRjf3tFziMuVBCsThU accessSecret: EV4dzWRfKTQaPRjf3tFziMuVBCsThU
endpoint: dytnsapi.aliyuncs.com endpoint: dytnsapi.aliyuncs.com
authCode: od2FgE9a9g authCode: od2FgE9a9g
# 物流API配置
# logistics:
# kdniao:
# # 快递鸟 API配置
# appId: '1889454' # 快递鸟应用ID需要到快递鸟官网申请
# appKey: 'b2483529-807d-49af-b0e1-1ed218baa4db' # 快递鸟API密钥需要到快递鸟官网申请
# url: 'https://api.kdniao.com/api/dist' # 快递鸟即时查询接口地址
# 快递鸟支持两种接口:
# 1. 即时查询接口https://api.kdniao.com/api/dist
# 2. 物流跟踪接口https://api.kdniao.com/Ebusiness/EbusinessOrderHandle.aspx
logistics:
# 阿里云快递查询API配置
aliyun:
# 阿里云API网关AppCode需要在阿里云市场购买快递查询服务后获取
appCode: "78064b8aaa294f64ab07a285f129aea6"
# API主机地址
host: "https://kzexpress.market.alicloudapi.com"
# API路径
path: "/api-mall/api/express/query"

View File

@ -273,8 +273,7 @@
var _defaultRootFlag = item[options.parentCode] == '0' || var _defaultRootFlag = item[options.parentCode] == '0' ||
item[options.parentCode] == 0 || item[options.parentCode] == 0 ||
item[options.parentCode] == null || item[options.parentCode] == null ||
item[options.parentCode] == '' || item[options.parentCode] == '';
$.inArray(item[options.code], parentCodes) > 0 && !rootFlag;
if (!item[options.parentCode] || (_root ? (item[options.parentCode] == options.rootIdValue) : _defaultRootFlag)) { if (!item[options.parentCode] || (_root ? (item[options.parentCode] == options.rootIdValue) : _defaultRootFlag)) {
rootFlag = true; rootFlag = true;
if (!target.data_list["_root_"]) { if (!target.data_list["_root_"]) {

View File

@ -99,10 +99,39 @@
function submitHandler() { function submitHandler() {
if ($.validate.form()) { if ($.validate.form()) {
$.operate.save(prefix + "/add", $('#form-dept-add').serialize()); // 方法一使用saveModal新增成功后不刷新表格只显示提示信息
//$.operate.saveModal(prefix + "/add", $('#form-dept-add').serialize());
// 方法二使用自定义Ajax保存如果需要更多控制
customSave();
} }
} }
// 自定义保存方法(可选)
function customSave() {
$.ajax({
url: prefix + "/add",
type: "post",
dataType: "json",
data: $('#form-dept-add').serialize(),
beforeSend: function () {
$.modal.loading("正在处理中,请稍候...");
},
success: function(result) {
$.modal.closeLoading();
if (result.code == web_status.SUCCESS) {
$.modal.alertSuccess(result.msg);
// 如果需要关闭窗口,取消注释下面这行
$.modal.close();
} else if (result.code == web_status.WARNING) {
$.modal.alertWarning(result.msg);
} else {
$.modal.alertError(result.msg);
}
}
});
}
/*部门管理-新增-选择父部门树*/ /*部门管理-新增-选择父部门树*/
function selectDeptTree() { function selectDeptTree() {
var treeId = $("#treeId").val(); var treeId = $("#treeId").val();

View File

@ -124,8 +124,29 @@
function remove(id) { function remove(id) {
$.modal.confirm("确认要删除吗", function () { $.modal.confirm("确认要删除吗", function () {
$.operate.post(prefix + '/remove/' + id); customRemove(id);
}) });
}
function customRemove(id) {
$.ajax({
url: prefix + '/remove/' + id,
type: "post",
dataType: "json",
beforeSend: function () {
$.modal.loading("正在处理中,请稍候...");
},
success: function(result) {
$.modal.closeLoading();
if (result.code == web_status.SUCCESS) {
$.modal.alertSuccess(result.msg);
} else if (result.code == web_status.WARNING) {
$.modal.alertWarning(result.msg);
} else {
$.modal.alertError(result.msg);
}
}
});
} }
</script> </script>
</body> </body>

View File

@ -48,7 +48,8 @@
<label class="col-sm-3 control-label">类目类别:</label> <label class="col-sm-3 control-label">类目类别:</label>
<div class="col-sm-8"> <div class="col-sm-8">
<div class="radio-box" th:each="dict : ${@dict.getType('goods_category_type')}"> <div class="radio-box" th:each="dict : ${@dict.getType('goods_category_type')}">
<input type="radio" th:id="${dict.dictCode}" name="type" th:value="${dict.dictValue}" th:checked="${dict.default}"> <input type="radio" th:id="${dict.dictCode}" name="type" th:value="${dict.dictValue}"
th:checked="${goodsCategory.type != null ? goodsCategory.type.toString() == dict.dictValue : dict.default}">
<label th:for="${dict.dictCode}" th:text="${dict.dictLabel}"></label> <label th:for="${dict.dictCode}" th:text="${dict.dictLabel}"></label>
</div> </div>
</div> </div>
@ -118,10 +119,36 @@
var data = $("#form-dept-edit").serializeArray(); var data = $("#form-dept-edit").serializeArray();
var insuranceIds = $.form.selectCheckeds("insurance"); var insuranceIds = $.form.selectCheckeds("insurance");
data.push({"name": "insuranceIds", "value": insuranceIds}); data.push({"name": "insuranceIds", "value": insuranceIds});
$.operate.save(prefix + "/edit", data);
// 自定义保存,不刷新表格
customEditSave(data);
} }
} }
// 自定义编辑保存方法
function customEditSave(data) {
$.ajax({
url: prefix + "/edit",
type: "post",
dataType: "json",
data: data,
beforeSend: function () {
$.modal.loading("正在处理中,请稍候...");
},
success: function(result) {
$.modal.closeLoading();
if (result.code == web_status.SUCCESS) {
$.modal.alertSuccess(result.msg);
$.modal.close();
} else if (result.code == web_status.WARNING) {
$.modal.alertWarning(result.msg);
} else {
$.modal.alertError(result.msg);
}
}
});
}
/*部门管理-修改-选择部门树*/ /*部门管理-修改-选择部门树*/
function selectDeptTree() { function selectDeptTree() {
var treeId = $("#treeId").val(); var treeId = $("#treeId").val();

View File

@ -0,0 +1,69 @@
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
<head>
<th:block th:include="include :: header('选择服务类目')" />
<th:block th:include="include :: ztree-css" />
</head>
<body class="white-bg">
<div class="wrapper wrapper-content animated fadeInRight ibox-content">
<form class="form-horizontal m" id="form-tree">
<div class="form-group">
<label class="col-sm-3 control-label">服务类目:</label>
<div class="col-sm-8">
<div class="input-group">
<input class="form-control" type="text" id="treeName" readonly="true">
<input type="hidden" id="treeId">
<span class="input-group-addon"><i class="fa fa-search"></i></span>
</div>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label"></label>
<div class="col-sm-8">
<div id="tree" class="ztree"></div>
</div>
</div>
<!-- <div class="form-group">
<div class="form-control-static col-sm-offset-9">
<button type="button" class="btn btn-sm btn-primary" onclick="submitHandler()"><i class="fa fa-check"></i>保 存</button>&nbsp;
<button type="button" class="btn btn-sm btn-danger" onclick="closeItem()"><i class="fa fa-reply-all"></i>关 闭</button>
</div>
</div> -->
</form>
</div>
<th:block th:include="include :: footer" />
<th:block th:include="include :: ztree-js" />
<script type="text/javascript">
$(function() {
var url = ctx + "goods/category/serviceTreeData";
var options = {
url: url,
expandLevel: 2,
onClick : zOnClick
};
$.tree.init(options);
});
function zOnClick(event, treeId, treeNode) {
$("#treeId").val(treeNode.id);
$("#treeName").val(treeNode.name);
}
function submitHandler() {
var treeId = $("#treeId").val();
var treeName = $("#treeName").val();
if (treeId == "" || treeName == "") {
$.modal.alertWarning("请选择服务类目");
return;
}
var index = parent.layer.getFrameIndex(window.name);
parent.layer.close(index);
}
function closeItem() {
var index = parent.layer.getFrameIndex(window.name);
parent.layer.close(index);
}
</script>
</body>
</html>

View File

@ -145,9 +145,7 @@
formatter: function (value, row, index) { formatter: function (value, row, index) {
if (row.parentId !== 0) { if (row.parentId !== 0) {
var actions = []; var actions = [];
actions.push('<a class="btn btn-success btn-xs ' + editFlag + '" href="javascript:void(0)" onclick="$.operate.editTab(\'' + row.deptGoodsCategoryId + '\')"><i class="fa fa-edit"></i>编辑</a> '); actions.push('<a class="btn btn-success btn-xs ' + editFlag + '" href="javascript:void(0)" onclick="$.operate.edit(\'' + row.deptGoodsCategoryId + '\')"><i class="fa fa-edit"></i>编辑</a> ');
// actions.push('<a class="btn btn-info btn-xs ' + addFlag + '" href="javascript:void(0)" onclick="$.operate.add(\'' + row.deptGoodsCategoryId + '\')"><i class="fa fa-plus"></i>新增</a> ');
// actions.push('<a class="btn btn-danger btn-xs ' + removeFlag + '" href="javascript:void(0)" onclick="remove(\'' + row.deptGoodsCategoryId + '\')"><i class="fa fa-trash"></i>删除</a>');
return actions.join(''); return actions.join('');
} else { } else {
return ""; return "";

View File

@ -11,6 +11,7 @@
<div class="main-content"> <div class="main-content">
<form class="form-horizontal" id="form-deptGoodsCategory-edit" th:object="${deptGoodsCategory}"> <form class="form-horizontal" id="form-deptGoodsCategory-edit" th:object="${deptGoodsCategory}">
<input id="deptGoodsCategoryId" name="deptGoodsCategoryId" type="hidden" th:field="*{deptGoodsCategoryId}"/> <input id="deptGoodsCategoryId" name="deptGoodsCategoryId" type="hidden" th:field="*{deptGoodsCategoryId}"/>
<input id="type" name="type" type="hidden" th:field="*{type}"/>
<div class="row"> <div class="row">
<div class="col-sm-6"> <div class="col-sm-6">
@ -23,6 +24,19 @@
</div> </div>
</div> </div>
</div> </div>
<div class="col-sm-6" th:if="*{type != 1}">
<div class="form-group">
<label class="col-sm-6 control-label">服务类目:</label>
<div class="col-sm-6">
<div class="input-group">
<input class="form-control" type="text" onclick="selectServiceCategoryTree()" id="treeName" readonly="true" th:value="*{serviceCategoryName}">
<input type="hidden" id="treeId" name="serviceCategoryId" th:value="*{serviceCategoryId}">
<input type="hidden" id="serviceCategoryName" name="serviceCategoryName" th:value="*{serviceCategoryName}">
<span class="input-group-addon"><i class="fa fa-search"></i></span>
</div>
</div>
</div>
</div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-sm-6"> <div class="col-sm-6">
@ -238,6 +252,15 @@
</div> </div>
</div> </div>
</div> </div>
<div class="col-sm-6">
<div class="form-group">
<label class="col-sm-6 control-label">倒计时小时数:</label>
<div class="col-sm-6">
<input name="countdownHours" placeholder="请输入倒计时小时数" class="form-control" type="number" min="1" max="168"
th:field="*{countdownHours}">
</div>
</div>
</div>
</div> </div>
<div class="row"> <div class="row">
<div class="row"> <div class="row">
@ -375,6 +398,26 @@
} }
}); });
/*选择服务类目树*/
function selectServiceCategoryTree() {
var treeId = $("#treeId").val();
var options = {
title: '服务类目选择',
width: "380",
url: ctx + "goods/category/selectServiceCategoryTree",
callBack: doSubmit
};
$.modal.openOptions(options);
}
function doSubmit(index, layero) {
var body = $.modal.getChildFrame(index);
$("#treeId").val(body.find('#treeId').val());
$("#treeName").val(body.find('#treeName').val());
$("#serviceCategoryName").val(body.find('#treeName').val());
$.modal.close(index);
}
</script> </script>
</body> </body>
</html> </html>

View File

@ -30,7 +30,7 @@
<script th:src="@{/ajax/libs/validate/jquery.validate.extend.js?v=1.19.3}"></script> <script th:src="@{/ajax/libs/validate/jquery.validate.extend.js?v=1.19.3}"></script>
<script th:src="@{/ajax/libs/validate/messages_zh.js?v=1.19.3}"></script> <script th:src="@{/ajax/libs/validate/messages_zh.js?v=1.19.3}"></script>
<!-- bootstrap-table 表格树插件 --> <!-- bootstrap-table 表格树插件 -->
<script th:src="@{/ajax/libs/bootstrap-table/extensions/tree/bootstrap-table-tree.min.js?v=1.18.3}"></script> <script th:src="@{/ajax/libs/bootstrap-table/extensions/tree/bootstrap-table-tree.js?v=1.18.3}"></script>
<!-- 遮罩层 --> <!-- 遮罩层 -->
<script th:src="@{/ajax/libs/blockUI/jquery.blockUI.js?v=2.70.0}"></script> <script th:src="@{/ajax/libs/blockUI/jquery.blockUI.js?v=2.70.0}"></script>
<script th:src="@{/ajax/libs/iCheck/icheck.min.js?v=1.0.3}"></script> <script th:src="@{/ajax/libs/iCheck/icheck.min.js?v=1.0.3}"></script>

View File

@ -127,7 +127,7 @@
</a> </a>
<a class="btn btn-default btn-outline" onclick="selectConditionBtn(this, {orderStatusName : '售后纠纷', orderStatus: -1})"> <a class="btn btn-default btn-outline" onclick="selectConditionBtn(this, {orderStatusName : '售后纠纷', orderStatus: -1})">
售后纠纷 售后纠纷
(<span>0</span>) (<span id="afterServiceDisputeNum">0</span>)
</a> </a>
</div> </div>
<div class="flex-board"> <div class="flex-board">
@ -553,6 +553,20 @@
} }
} }
}) })
// 售后纠纷数量统计
$.ajax({
type: "GET",
dataType:"json",
url: prefix + '/after/dispute/count',
success: function (result) {
if (result.code == web_status.SUCCESS) {
$('#afterServiceDisputeNum').text(result.data);
} else {
$.modal.msgError("售后纠纷数量加载错误,请重试!")
}
}
})
}); });
function changeOrderMode(orderMode) { function changeOrderMode(orderMode) {
@ -687,10 +701,14 @@
actions.push('<a class="btn btn-success btn-xs " href="javascript:void(0)" onclick="callDetail(\'' + row.orderMasterId + '\')"><i class="fa fa-info"></i>拨号详情</a> '); actions.push('<a class="btn btn-success btn-xs " href="javascript:void(0)" onclick="callDetail(\'' + row.orderMasterId + '\')"><i class="fa fa-info"></i>拨号详情</a> ');
actions.push('<a class="btn btn-success btn-xs " href="javascript:void(0)" onclick="orderDetailReject(\'' + row.id + '\')"></i>师傅退单</a> '); actions.push('<a class="btn btn-success btn-xs " href="javascript:void(0)" onclick="orderDetailReject(\'' + row.id + '\')"></i>师傅退单</a> ');
// actions.push('<a class="btn btn-success btn-xs " href="javascript:void(0)" onclick="orderMasterReject(\'' + row.orderMasterId + '\')"></i>商家退单</a> '); // actions.push('<a class="btn btn-success btn-xs " href="javascript:void(0)" onclick="orderMasterReject(\'' + row.orderMasterId + '\')"></i>商家退单</a> ');
<!-- actions.push('<a class="btn btn-success btn-xs " href="javascript:void(0)" onclick="showOrderWorker(\'' + row.id + '\')"></i>指派</a> ');--> // actions.push('<a class="btn btn-success btn-xs " href="javascript:void(0)" onclick="showOrderWorker(\'' + row.id + '\')"></i>指派</a> ');
if (row.payStatus == 0) { if (row.payStatus == 0) {
actions.push('<a class="btn btn-success btn-xs " href="javascript:void(0)" onclick="showPayQrcode(\'' + row.id + '\')"></i>付款</a> '); actions.push('<a class="btn btn-success btn-xs " href="javascript:void(0)" onclick="showPayQrcode(\'' + row.id + '\')"></i>付款</a> ');
} }
// 售后纠纷处理按钮
if (row.afterServiceStatus == 1) {
actions.push('<a class="btn btn-warning btn-xs" href="javascript:void(0)" onclick="handleAfterServiceDispute(\'' + row.id + '\')"><i class="fa fa-gavel"></i>售后纠纷处理</a> ');
}
return actions.join(''); return actions.join('');
} }
}, },
@ -703,6 +721,29 @@
field: 'orderImgs', field: 'orderImgs',
title: '完单图片' title: '完单图片'
}, },
{
field: 'orderImages',
title: '下单图片',
align: 'center',
formatter: function (value, row, index) {
if (value && value.trim() !== '') {
return '<img src="' + value + '" style="width: 50px; height: 50px; object-fit: cover;" onclick="showImagePreview(\'' + value + '\')">';
}
return '-';
}
},
{
field: 'isDeliveryToStore',
title: '是否发货到服务店',
align: 'center',
formatter: function (value, row, index) {
if (value === 1) {
return '<span class="label label-success"></span>';
} else {
return '<span class="label label-default"></span>';
}
}
},
{ {
field: 'orderType', field: 'orderType',
title: '订单类型', title: '订单类型',
@ -932,12 +973,276 @@
}); });
} }
// 设置定时器,每隔一分钟执行一次 fetchData 函数 // 设置定时器,每隔一分钟执行一次 fetchData 函数
setInterval(fetchData, 60000); setInterval(fetchData, 60000);
// 图片预览功能
function showImagePreview(imageUrl) {
if (!imageUrl || imageUrl.trim() === '') {
$.modal.msgError("图片地址不能为空");
return;
}
layer.open({
type: 1,
title: '图片预览',
shadeClose: true,
shade: 0.8,
area: ['auto', 'auto'],
content: '<div style="text-align: center; padding: 20px;"><img src="' + imageUrl + '" style="max-width: 800px; max-height: 600px; object-fit: contain;" /></div>'
});
}
// 售后纠纷处理
function handleAfterServiceDispute(orderDetailId) {
if (!orderDetailId) {
$.modal.msgError("子单ID不能为空");
return;
}
// 查询该子单的售后纠纷记录
$.ajax({
type: "POST",
dataType: "json",
url: prefix + "/after/records/byDetailId",
data: {orderDetailId: orderDetailId},
success: function (result) {
if (result.code == web_status.SUCCESS) {
showAfterServiceDisputeModal(orderDetailId, result.data);
} else {
$.modal.msgError("查询售后纠纷记录失败:" + result.msg);
}
},
error: function () {
$.modal.msgError("查询售后纠纷记录失败,请重试");
}
});
}
// 显示售后纠纷处理弹窗
function showAfterServiceDisputeModal(orderDetailId, disputeRecords) {
if (!disputeRecords || disputeRecords.length === 0) {
$.modal.msgError("没有找到售后纠纷记录");
return;
}
// 直接获取订单详情来判断订单类型
getOrderDetailInfo(orderDetailId, function(orderDetail) {
showDisputeModalWithOrderInfo(orderDetailId, disputeRecords, orderDetail);
});
}
// 获取订单详情信息
function getOrderDetailInfo(orderDetailId, callback) {
$.ajax({
type: "POST",
dataType: "json",
contentType: "application/json",
url: prefix + "/app/detail",
data: JSON.stringify({id: orderDetailId}),
success: function(result) {
if (result.code == web_status.SUCCESS) {
callback(result.data);
} else {
callback(null);
}
},
error: function() {
callback(null);
}
});
}
// 根据订单信息显示售后纠纷处理弹窗
function showDisputeModalWithOrderInfo(orderDetailId, disputeRecords, orderDetail) {
var isGoodsOrder = orderDetail && orderDetail.orderType === 1; // 1表示商品订单
var modalContent = '<div class="modal-body">';
modalContent += '<div class="table-responsive">';
modalContent += '<table class="table table-bordered table-striped">';
modalContent += '<thead><tr>';
modalContent += '<th>子单号</th>';
modalContent += '<th>申请退款金额</th>';
modalContent += '<th>师傅反馈</th>';
modalContent += '<th>客户确认</th>';
modalContent += '<th style="width: 280px;">操作</th>';
modalContent += '</tr></thead><tbody>';
disputeRecords.forEach(function(record) {
modalContent += '<tr>';
modalContent += '<td>' + (record.orderDetailCode || record.id) + '</td>';
modalContent += '<td>¥' + (record.refund || record.agreedRefund || 0) + '</td>';
// 师傅反馈列 - 如果是商品订单则增加workerAgreeType显示
var workerFeedbackContent = getWorkerFeedbackText(record.workerFeedbackResult) + '<br/><span style="color: #666; font-size: 12px;">反馈金额:¥' + (record.agreedRefund || 0) + '</span>';
if (isGoodsOrder && record.workerAgreeType) {
workerFeedbackContent += '<br/><span style="color: #007bff; font-size: 12px;">处理方式:' + getWorkerAgreeTypeText(record.workerAgreeType) + '</span>';
}
modalContent += '<td>' + workerFeedbackContent + '</td>';
modalContent += '<td>' + getCustomerCheckText(record.customerFinalCheck) + '</td>';
modalContent += '<td>';
modalContent += '<div style="margin-bottom: 8px;">';
modalContent += '<label style="font-size: 12px; color: #666; margin-bottom: 2px; display: block;">退款金额:</label>';
modalContent += '<input type="number" class="form-control" id="refundAmount_' + record.id + '" placeholder="输入退款金额" value="' + (record.agreedRefund || 0) + '" style="width: 100%; font-size: 12px; height: 28px;">';
modalContent += '</div>';
modalContent += '<div style="margin-bottom: 8px;">';
modalContent += '<label style="font-size: 12px; color: #666; margin-bottom: 2px; display: block;">处理原因:</label>';
modalContent += '<input type="text" class="form-control" id="processReason_' + record.id + '" placeholder="请输入处理原因" style="width: 100%; font-size: 12px; height: 28px;">';
modalContent += '</div>';
modalContent += '<button class="btn btn-primary btn-xs" onclick="processRefund(' + record.id + ', ' + orderDetailId + ')" style="width: 100%; font-size: 12px; height: 28px;">处理退款</button>';
modalContent += '<button class="btn btn-warning btn-xs" onclick="cancelAfterService(' + record.id + ', ' + orderDetailId + ')" style="width: 100%; font-size: 12px; height: 28px; margin-top: 4px;">取消售后</button>';
modalContent += '</td>';
modalContent += '</tr>';
});
modalContent += '</tbody></table>';
// 如果是商品订单,在表格下方增加提示文字
if (isGoodsOrder) {
modalContent += '<div style="margin-top: 15px; padding: 10px; background-color: #fff3cd; border: 1px solid #ffeaa7; border-radius: 4px;">';
modalContent += '<i class="fa fa-exclamation-triangle" style="color: #856404; margin-right: 5px;"></i>';
modalContent += '<span style="color: #856404; font-weight: bold;">商品退款需确认是否存在退货,并确认退完与否再操作退款!同时确认子单款项金额是否符合退款要求!</span>';
modalContent += '</div>';
}
modalContent += '</div>';
modalContent += '</div>';
layer.open({
type: 1,
title: '售后纠纷处理 - 子单ID: ' + orderDetailId,
shadeClose: true,
shade: 0.8,
area: ['90%', '80%'],
content: modalContent
});
}
// 获取师傅同意处理方式文本
function getWorkerAgreeTypeText(type) {
var typeMap = {
1: '即时退单退款',
2: '货物拦截后退单退款',
3: '快递返回货物后退单退款',
4: '退回货物后退单退款'
};
return typeMap[type] || '未知';
}
// 处理退款
function processRefund(recordId, orderDetailId) {
var refundAmount = $('#refundAmount_' + recordId).val();
var processReason = $('#processReason_' + recordId).val();
if (!refundAmount || refundAmount <= 0) {
$.modal.msgError("请输入有效的退款金额");
return;
}
if (!processReason || processReason.trim() === '') {
$.modal.msgError("请输入处理原因");
return;
}
$.modal.confirm("确认处理退款吗?<br/>退款金额:¥" + refundAmount + "<br/>处理原因:" + processReason, function() {
$.ajax({
type: "POST",
dataType: "json",
contentType: "application/json",
url: ctx + "order/master/after/dispute/handle",
data: JSON.stringify({
recordId: recordId,
orderDetailId: orderDetailId,
refundAmount: refundAmount,
platformHandleReason: processReason
}),
success: function (result) {
if (result.code == web_status.SUCCESS) {
$.modal.msgSuccess("退款处理成功");
// 关闭弹窗
layer.closeAll();
// 刷新表格
$.table.refresh();
} else {
$.modal.msgError("退款处理失败:" + result.msg);
}
},
error: function () {
$.modal.msgError("退款处理失败,请重试");
}
});
});
}
// 取消售后
function cancelAfterService(recordId, orderDetailId) {
// 获取处理原因输入框的值
var processReason = $('#processReason_' + recordId).val();
if (!processReason || processReason.trim() === '') {
$.modal.msgError("请输入处理原因");
return;
}
$.modal.confirm("确认取消该售后申请吗?", function() {
$.ajax({
type: "POST",
dataType: "json",
url: ctx + "order/master/after/cancel",
data: {
afterServiceRecordId: recordId,
platformHandleReason: processReason.trim()
},
success: function (result) {
if (result.code == web_status.SUCCESS) {
$.modal.msgSuccess("取消售后成功");
// 关闭弹窗
layer.closeAll();
// 刷新表格
$.table.refresh();
} else {
$.modal.msgError("取消售后失败:" + result.msg);
}
},
error: function () {
$.modal.msgError("取消售后失败,请重试");
}
});
});
}
// 获取售后类型文本
function getAfterServiceTypeText(type) {
var typeMap = {
1: '未收到货',
2: '已收到货',
3: '质量问题',
4: '其他'
};
return typeMap[type] || '未知';
}
// 获取师傅反馈文本
function getWorkerFeedbackText(result) {
var resultMap = {
0: '拒绝',
1: '同意',
2: '上门补做/重做',
3: '重做/补做完成'
};
return resultMap[result] || '未处理';
}
// 获取客户确认文本
function getCustomerCheckText(check) {
var checkMap = {
0: '不同意',
1: '同意',
2: '未处理'
};
return checkMap[check] || '未处理';
}
</script> </script>
</body> </body>

View File

@ -56,6 +56,37 @@
border-color: #1c84c6; border-color: #1c84c6;
color: #fff; color: #fff;
} }
/* 表格滚动条样式 */
.table-scroll-container {
overflow-x: auto;
overflow-y: auto;
max-height: 600px;
}
.table-scroll-container::-webkit-scrollbar {
width: 8px;
height: 8px;
}
.table-scroll-container::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
.table-scroll-container::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 4px;
}
.table-scroll-container::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
/* 表格最小宽度 */
#bootstrap-table {
min-width: 1000px;
}
</style> </style>
</head> </head>
<body class="gray-bg"> <body class="gray-bg">
@ -134,7 +165,7 @@
</a> </a>
<a class="btn btn-default btn-outline" onclick="selectConditionBtn(this, {orderStatusName : '售后纠纷', orderStatus: -1})"> <a class="btn btn-default btn-outline" onclick="selectConditionBtn(this, {orderStatusName : '售后纠纷', orderStatus: -1})">
售后纠纷 售后纠纷
(<span>0</span>) (<span id="afterServiceDisputeNum">0</span>)
</a> </a>
</div> </div>
<div class="flex-board"> <div class="flex-board">
@ -331,7 +362,7 @@
</div> </div>
</div> </div>
<div class="col-sm-12 select-table table-striped"> <div class="col-sm-12 select-table table-striped table-scroll-container">
<table id="bootstrap-table"></table> <table id="bootstrap-table"></table>
</div> </div>
</div> </div>
@ -542,6 +573,22 @@
} }
} }
}) })
<!-- 售后纠纷订单数量统计-->
$.ajax({
type: "POST",
dataType:"json",
url: prefix + '/after/dispute/count',
contentType: 'application/json',
data: JSON.stringify({}),
success: function (result) {
if (result.code == web_status.SUCCESS) {
$('#afterServiceDisputeNum').text(result.data);
} else {
$.modal.msgError("售后纠纷数据加载错误,请重试!")
}
}
})
}); });
function changeOrderMode(orderMode) { function changeOrderMode(orderMode) {
@ -645,17 +692,18 @@
field: 'goods', field: 'goods',
title: '订单信息', title: '订单信息',
formatter: function (value, row) { formatter: function (value, row) {
var imgUrl = (value && value.goodsImgUrl) ? value.goodsImgUrl : '';
return '<div style="display:flex;justify-content: center;align-items: center;">' return '<div style="display:flex;justify-content: center;align-items: center;">'
+ '<img decoding="async" src="' + value.goodsImgUrl + '" width="100" height="100" />' + '<img decoding="async" src="' + imgUrl + '" width="100" height="100" />'
+ '<div>' + '<div>'
+ '<p>' + row.code + ' + <p/>' + '<p>' + (row.code || '') + '</p>'
+ '<p> ' + row.consoleGoodsName + '<p/>' + '<p>' + (row.consoleGoodsName || '') + '</p>'
+ '<p> 联系人:' + row.addressName + '</p>' + '<p>联系人:' + (row.addressName || '') + '</p>'
+ '<p> 联系电话:' + row.addressPhone + '</p>' + '<p>联系电话:' + (row.addressPhone || '') + '</p>'
+ '<p> 联系地址:' + row.address + '</p>' + '<p>联系地址:' + (row.address || '') + '</p>'
+ '<p> 下单时间:' + row.createTime + '</p>' + '<p>下单时间:' + (row.createTime || '') + '</p>'
+ '<p> 预约时间:' + row.mixExpectTime + '</p>' + '<p>预约时间:' + (row.mixExpectTime || '') + '</p>'
+ '<p> 下单总金额:' + row.financialMasterMoney + '元,师傅实收金额: '+ row.financialMasterPayMoney + ' </p>' + '<p>下单总金额:' + (row.financialMasterMoney || '') + '元,师傅实收金额: ' + (row.financialMasterPayMoney || '') + '</p>'
+ '</div>' + '</div>'
+ '</div>'; + '</div>';
} }
@ -725,6 +773,10 @@
} else { } else {
actions.push('<a class="btn btn-success btn-xs " href="javascript:void(0)" onclick="pcOrderInsurance(\'' + row.id + '\')"></i>选择保险</a> '); actions.push('<a class="btn btn-success btn-xs " href="javascript:void(0)" onclick="pcOrderInsurance(\'' + row.id + '\')"></i>选择保险</a> ');
} }
// 售后纠纷处理按钮
if (row.afterServiceStatus == 1) {
actions.push('<a class="btn btn-warning btn-xs" href="javascript:void(0)" onclick="handleAfterServiceDispute(\'' + row.id + '\')"><i class="fa fa-gavel"></i>售后纠纷处理</a> ');
}
return actions.join(''); return actions.join('');
} }
}, },
@ -790,6 +842,44 @@
{ {
field: 'payTime', field: 'payTime',
title: '付款时间' title: '付款时间'
},
{
field: 'returnReason',
title: '退单原因',
align: 'center',
formatter: function (value, row, index) {
return value || '-';
}
},
{
field: 'returnReasonDetail',
title: '退单原因详情',
align: 'center',
formatter: function (value, row, index) {
if (value && value.length > 20) {
return value.substring(0, 20) + '...';
}
return value || '-';
}
},
{
field: 'returnImages',
title: '退单图片',
align: 'center',
formatter: function (value, row, index) {
if (value) {
var images = value.split(',');
var html = '';
for (var i = 0; i < Math.min(images.length, 3); i++) {
html += '<img src="' + images[i] + '" style="width:30px;height:30px;margin:2px;" onclick="showImage(\'' + images[i] + '\')" />';
}
if (images.length > 3) {
html += '<span style="color:#999;">+' + (images.length - 3) + '</span>';
}
return html;
}
return '-';
}
}] }]
}; };
$.table.init(options); $.table.init(options);
@ -888,9 +978,18 @@
$.modal.confirm("确定要退单吗?", function() { $.modal.confirm("确定要退单吗?", function() {
const url = "console/cancel"; const url = "console/cancel";
const data = { "id": id }; const data = { "id": id };
$.operate.post(url, data); $.ajax({
}); type: "POST",
dataType: "json",
url: url,
data: JSON.stringify(data),
contentType: 'application/json',
success: function (result) {
$.operate.ajaxSuccess(result);
} }
});
});
}
function changeConditionBtnChosenStyle(e) { function changeConditionBtnChosenStyle(e) {
$('.condition-btn .btn').removeClass('active-condition-btn'); $('.condition-btn .btn').removeClass('active-condition-btn');
@ -1034,6 +1133,26 @@
showPayQrcode(rows.join()); showPayQrcode(rows.join());
} }
// 显示退单图片
function showImage(imageUrl) {
if (!imageUrl) {
return;
}
var html = '<div style="text-align:center;">' +
'<img src="' + imageUrl + '" style="max-width:100%;max-height:500px;" />' +
'</div>';
layer.open({
type: 1,
title: '退单图片',
area: ['600px', '500px'],
content: html,
shadeClose: true,
btn: ['关闭']
});
}
// 主订单全部 // 主订单全部
var orderMasterHaveReleasedUrl = ""; var orderMasterHaveReleasedUrl = "";
// 主订单新订单 // 主订单新订单
@ -1122,6 +1241,252 @@
// 设置定时器,每隔一分钟执行一次 fetchData 函数 // 设置定时器,每隔一分钟执行一次 fetchData 函数
setInterval(fetchData, 60000); setInterval(fetchData, 60000);
// 售后纠纷处理弹窗
function handleAfterServiceDispute(orderMasterId) {
// 查询该主单的售后记录
$.ajax({
type: "POST",
dataType: "json",
url: prefix + '/after/records/byMasterId',
contentType: 'application/json',
data: JSON.stringify({id: orderMasterId}),
success: function (result) {
if (result.code == web_status.SUCCESS) {
showAfterServiceDisputeModal(orderMasterId, result.data);
} else {
$.modal.msgError("查询售后记录失败:" + result.msg);
}
},
error: function() {
$.modal.msgError("查询售后记录失败,请重试!");
}
});
}
// 获取订单详情信息
function getOrderDetailInfo(orderMasterId, callback) {
$.ajax({
type: "POST",
dataType: "json",
url: prefix + "/app/detail",
contentType: "application/json",
data: JSON.stringify({id: orderMasterId}),
success: function (result) {
if (result.code == web_status.SUCCESS) {
callback(result.data);
} else {
$.modal.msgError("获取订单详情失败:" + result.msg);
}
},
error: function() {
$.modal.msgError("获取订单详情失败,请重试!");
}
});
}
// 获取师傅同意类型文本
function getWorkerAgreeTypeText(type) {
var types = {
1: '同意',
2: '不同意',
3: '部分同意'
};
return types[type] || '未处理';
}
// 显示售后纠纷处理弹窗
function showAfterServiceDisputeModal(orderMasterId, afterServiceRecords) {
// 先获取订单详情以判断订单类型
getOrderDetailInfo(orderMasterId, function(orderDetail) {
showDisputeModalWithOrderInfo(orderMasterId, afterServiceRecords, orderDetail);
});
}
// 根据订单信息显示售后纠纷处理弹窗
function showDisputeModalWithOrderInfo(orderMasterId, afterServiceRecords, orderDetail) {
var isGoodsOrder = orderDetail && orderDetail.orderType === 1;
var html = '<div class="modal fade" id="afterServiceDisputeModal" tabindex="-1" role="dialog">';
html += '<div class="modal-dialog modal-lg" role="document">';
html += '<div class="modal-content">';
html += '<div class="modal-header">';
html += '<h4 class="modal-title">售后纠纷处理</h4>';
html += '<button type="button" class="close" data-dismiss="modal">&times;</button>';
html += '</div>';
html += '<div class="modal-body">';
if (afterServiceRecords && afterServiceRecords.length > 0) {
html += '<div class="table-responsive">';
html += '<table class="table table-bordered table-striped">';
// 根据订单类型决定表头
if (isGoodsOrder) {
html += '<thead><tr><th>子单号</th><th>申请退款金额</th><th>师傅反馈</th><th>师傅同意类型</th><th>客户确认</th><th>操作</th></tr></thead>';
} else {
html += '<thead><tr><th>子单号</th><th>申请退款金额</th><th>师傅反馈</th><th>客户确认</th><th>操作</th></tr></thead>';
}
html += '<tbody>';
afterServiceRecords.forEach(function(record) {
html += '<tr>';
html += '<td>' + (record.orderDetailCode || '') + '</td>';
html += '<td>¥' + (record.refund || record.agreedRefund || 0) + '</td>';
html += '<td>' + getWorkerFeedbackText(record.workerFeedbackResult) + '<br><small style="color: #999;">反馈金额:¥' + (record.agreedRefund || 0) + '</small></td>';
// 如果是商品订单,添加师傅同意类型列
if (isGoodsOrder) {
html += '<td>' + getWorkerAgreeTypeText(record.workerAgreeType) + '</td>';
}
html += '<td>' + getCustomerCheckText(record.customerFinalCheck) + '</td>';
html += '<td style="width: 280px;">';
html += '<div style="margin-bottom: 8px;">';
html += '<label style="font-size: 12px; color: #666; margin-bottom: 2px; display: block;">退款金额:</label>';
html += '<input type="number" class="form-control" id="refundAmount_' + record.id + '" placeholder="输入退款金额" value="' + (record.refund || 0) + '" style="width: 100%; font-size: 12px; height: 28px;" min="0" step="0.01">';
html += '</div>';
html += '<div style="margin-bottom: 8px;">';
html += '<label style="font-size: 12px; color: #666; margin-bottom: 2px; display: block;">处理原因:</label>';
html += '<input type="text" class="form-control" id="processReason_' + record.id + '" placeholder="请输入处理原因" style="width: 100%; font-size: 12px; height: 28px;">';
html += '</div>';
html += '<button class="btn btn-primary btn-xs" onclick="processRefund(\'' + record.id + '\', \'' + orderMasterId + '\')" style="width: 100%; font-size: 12px; height: 28px;">处理退款</button>';
html += '<button class="btn btn-warning btn-xs" onclick="cancelAfterService(\'' + record.id + '\', \'' + orderMasterId + '\')" style="width: 100%; font-size: 12px; height: 28px; margin-top: 4px;">取消售后</button>';
html += '</td>';
html += '</tr>';
});
html += '</tbody></table></div>';
// 如果是商品订单,添加提示文字
if (isGoodsOrder) {
html += '<div class="alert alert-warning" style="margin-top: 15px;">';
html += '<strong>提示:</strong>商品退款需确认是否存在退货,并确认退完与否再操作退款!同时确认子单款项金额是否符合退款要求!';
html += '</div>';
}
} else {
html += '<div class="alert alert-info">该订单暂无售后纠纷记录</div>';
}
html += '</div>';
html += '<div class="modal-footer">';
html += '<button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>';
html += '</div>';
html += '</div></div></div>';
// 移除已存在的弹窗
$('#afterServiceDisputeModal').remove();
// 添加弹窗到页面
$('body').append(html);
// 显示弹窗
$('#afterServiceDisputeModal').modal('show');
}
// 处理退款
function processRefund(recordId, orderMasterId) {
var refundAmount = $('#refundAmount_' + recordId).val();
var processReason = $('#processReason_' + recordId).val();
if (!refundAmount || refundAmount <= 0) {
$.modal.msgError("请输入有效的退款金额");
return;
}
if (!processReason || processReason.trim() === '') {
$.modal.msgError("请输入处理原因");
return;
}
$.modal.confirm("确认处理退款 ¥" + refundAmount + " 吗?", function() {
$.ajax({
type: "POST",
dataType: "json",
url: prefix + '/after/dispute/handle',
contentType: 'application/json',
data: JSON.stringify({
recordId: recordId,
orderMasterId: orderMasterId,
refundAmount: parseFloat(refundAmount),
platformHandleReason: processReason.trim()
}),
success: function (result) {
if (result.code == web_status.SUCCESS) {
$.modal.msgSuccess("退款处理成功");
$('#afterServiceDisputeModal').modal('hide');
// 刷新页面数据
$.table.refresh();
} else {
$.modal.msgError("退款处理失败:" + result.msg);
}
},
error: function() {
$.modal.msgError("退款处理失败,请重试!");
}
});
});
}
// 取消售后
function cancelAfterService(recordId, orderMasterId) {
// 获取处理原因输入框的值
var processReason = $('#processReason_' + recordId).val();
if (!processReason || processReason.trim() === '') {
$.modal.msgError("请输入处理原因");
return;
}
$.modal.confirm("确认取消该售后申请吗?", function() {
$.ajax({
type: "POST",
dataType: "json",
url: prefix + '/after/cancel',
data: {
afterServiceRecordId: recordId,
platformHandleReason: processReason.trim()
},
success: function (result) {
if (result.code == web_status.SUCCESS) {
$.modal.msgSuccess("取消售后成功");
$('#afterServiceDisputeModal').modal('hide');
// 刷新页面数据
$.table.refresh();
} else {
$.modal.msgError("取消售后失败:" + result.msg);
}
},
error: function() {
$.modal.msgError("取消售后失败,请重试!");
}
});
});
}
// 获取售后类型文本
function getAfterServiceTypeText(type) {
var types = {
1: '未收到货退单退款',
2: '未收到货退款',
3: '已收到货退款退货'
};
return types[type] || '未知';
}
// 获取师傅反馈文本
function getWorkerFeedbackText(result) {
var results = {
0: '拒绝',
1: '同意',
2: '上门补做/重做',
3: '重做/补做完成'
};
return results[result] || '未处理';
}
// 获取客户确认文本
function getCustomerCheckText(check) {
if (check === 1) return '同意';
if (check === 0) return '不同意';
return '未确认';
}
</script> </script>
</body> </body>
<script id="importTpl" type="text/template"> <script id="importTpl" type="text/template">

View File

@ -588,9 +588,18 @@
$.modal.confirm("确定要退单吗?", function() { $.modal.confirm("确定要退单吗?", function() {
const url = "console/cancel"; const url = "console/cancel";
const data = { "id": id }; const data = { "id": id };
$.operate.post(url, data); $.ajax({
}); type: "POST",
dataType: "json",
url: url,
data: JSON.stringify(data),
contentType: 'application/json',
success: function (result) {
$.operate.ajaxSuccess(result);
} }
});
});
}
function changeConditionBtnChosenStyle(e) { function changeConditionBtnChosenStyle(e) {
$('.condition-btn .btn').removeClass('active-condition-btn'); $('.condition-btn .btn').removeClass('active-condition-btn');

View File

@ -77,6 +77,10 @@
title: '师傅者id', title: '师傅者id',
visible: false visible: false
}, },
{
field: 'workerPhone',
title: '手机号'
},
{ {
field: 'name', field: 'name',
title: '真实姓名' title: '真实姓名'

View File

@ -72,9 +72,9 @@
</div> </div>
<div class="btn-group-sm" id="toolbar" role="group"> <div class="btn-group-sm" id="toolbar" role="group">
<a class="btn btn-danger multiple disabled" onclick="$.operate.removeAll()" shiro:hasPermission="worker:worker:remove"> <!-- <a class="btn btn-danger multiple disabled" onclick="$.operate.removeAll()" shiro:hasPermission="worker:worker:remove">
<i class="fa fa-remove"></i> 删除 <i class="fa fa-remove"></i> 禁止登录
</a> </a> -->
<a class="btn btn-info" onclick="$.table.importExcel()" shiro:hasPermission="worker:worker:import"> <a class="btn btn-info" onclick="$.table.importExcel()" shiro:hasPermission="worker:worker:import">
<i class="fa fa-upload"></i> 导入 <i class="fa fa-upload"></i> 导入
</a> </a>
@ -232,6 +232,14 @@
return statusTools(row); return statusTools(row);
} }
}, },
{
visible: editFlag == 'hidden' ? false : true,
title: '登录状态',
align: 'center',
formatter: function (value, row, index) {
return loginStatusTools(row);
}
},
{ {
field: 'createTime', field: 'createTime',
title: '创建时间', title: '创建时间',
@ -245,7 +253,7 @@
var actions = []; var actions = [];
actions.push('<a class="btn btn-success btn-xs " href="javascript:void(0)" onclick="areaDetail(\'' + row.workerId + '\')"><i class="fa fa-info"></i>服务区域</a> '); actions.push('<a class="btn btn-success btn-xs " href="javascript:void(0)" onclick="areaDetail(\'' + row.workerId + '\')"><i class="fa fa-info"></i>服务区域</a> ');
actions.push('<a class="btn btn-success btn-xs " href="javascript:void(0)" onclick="categoryDetail(\'' + row.workerId + '\')"><i class="fa fa-info"></i>服务技能</a> '); actions.push('<a class="btn btn-success btn-xs " href="javascript:void(0)" onclick="categoryDetail(\'' + row.workerId + '\')"><i class="fa fa-info"></i>服务技能</a> ');
actions.push('<a class="btn btn-danger btn-xs ' + removeFlag + '" href="javascript:void(0)" onclick="$.operate.remove(\'' + row.workerId + '\')"><i class="fa fa-remove"></i>删除</a> '); // actions.push('<a class="btn btn-danger btn-xs ' + removeFlag + '" href="javascript:void(0)" onclick="$.operate.remove(\'' + row.workerId + '\')><i class="fa fa-remove"></i>禁止登录</a> ');
return actions.join(''); return actions.join('');
} else { } else {
return ""; return "";
@ -276,9 +284,9 @@
/* 用户状态显示 */ /* 用户状态显示 */
function statusTools(row) { function statusTools(row) {
if (row.status == 0) { if (row.status == 0) {
return '<i class=\"fa fa-toggle-off text-info fa-2x\" onclick="enable(\'' + row.workerId + '\')"></i> '; return '<i class="fa fa-toggle-on text-info fa-2x" onclick="disable(\'' + row.workerId + '\')" title="点击停用"></i> ';
} else { } else {
return '<i class=\"fa fa-toggle-on text-info fa-2x\" onclick="disable(\'' + row.workerId + '\')"></i> '; return '<i class="fa fa-toggle-off text-info fa-2x" onclick="enable(\'' + row.workerId + '\')" title="点击启用"></i> ';
} }
} }
@ -296,6 +304,29 @@
}) })
} }
/* 登录状态显示 */
function loginStatusTools(row) {
if (row.loginStatus == null || row.loginStatus == 0) {
return '<i class="fa fa-toggle-on text-success fa-2x" onclick="disableLogin(\'' + row.workerId + '\')" title="点击禁用登录"></i> ';
} else {
return '<i class="fa fa-toggle-off text-danger fa-2x" onclick="enableLogin(\'' + row.workerId + '\')" title="点击启用登录"></i> ';
}
}
/* 禁用登录 */
function disableLogin(workerId) {
$.modal.confirm("确认要禁用该师傅的登录权限吗?", function() {
$.operate.post(prefix + "/changeLoginStatus", { "workerId": workerId, "loginStatus": 1 });
})
}
/* 启用登录 */
function enableLogin(workerId) {
$.modal.confirm("确认要启用该师傅的登录权限吗?", function() {
$.operate.post(prefix + "/changeLoginStatus", { "workerId": workerId, "loginStatus": 0 });
})
}
// 区域联动处理 // 区域联动处理
function areaChange(obj, nextId) { function areaChange(obj, nextId) {
var parentCode = $(obj).val(); var parentCode = $(obj).val();

View File

@ -13,7 +13,8 @@ public enum FinancialDetailType {
PLACE_FEE(3, "分销金额,可能存在多级"), PLACE_FEE(3, "分销金额,可能存在多级"),
RETURN_FEE(4, "退款金额"), RETURN_FEE(4, "退款金额"),
FINE_FEE(5, "超时罚金"), FINE_FEE(5, "超时罚金"),
COMMISSION_FEE(6,"手续费"); COMMISSION_FEE(6,"手续费"),
TRANSFER_DIFFERENCE_FEE(7, "转单差价");
private final Integer code; private final Integer code;
private final String desc; private final String desc;

View File

@ -23,7 +23,11 @@ public enum OrderBehaviorEnum {
/** /**
* 派单 * 派单
*/ */
ASSIGN_ORDER("ASSIGN_ORDER", "派单"); ASSIGN_ORDER("ASSIGN_ORDER", "派单"),
/**
* 拒绝验收
*/
REJECT_ACCEPTANCE("REJECT_ACCEPTANCE", "拒绝验收");
public final String code; public final String code;
public final String desc; public final String desc;

View File

@ -23,7 +23,8 @@ public enum OrderStatus implements IEnumType {
SERVER(3, "服务中"), SERVER(3, "服务中"),
FINISH_CHECK(4, "待确认"), FINISH_CHECK(4, "待确认"),
FINISH(5, "已完成"), FINISH(5, "已完成"),
CANCEL(6, "已取消"); CANCEL(6, "已取消"),
Pending(7,"待提现");
private final Integer code; private final Integer code;
private final String desc; private final String desc;

View File

@ -0,0 +1,74 @@
package com.ghy.common.enums;
/**
* 微信消息通知枚举
* @author clunt
*/
public enum WxStatusEnum {
/** 任务大厅订单通知 */
ORDER_PLAN("", "yqd3p4qsqn1RiyUb8kO4dPqoGKipRQg_y99nGw0jtLE"),
/** 新订单通知 */
NEW_ORDER("","/pages/order-manage/order-manage?stateCur=6"),
/** 商城端消息通知 */
CUSTOMER_ORDER("", "JtsGFPDjYhL2GbHfKxvTJaR_lLp8xLyw8VeR01Y0JHM"),
/** 测试 **/
TEXT("", "dnjTqmqr7OsnOXJR_SikVNNQOPSZLJ6pcDoysOksjNQ"),
NORMAL_ORDER("", "/pages/order-manage/order-manage"),
/** 拒单 **/
REJECT_ORDER("", "/pages/order-manage/order-manage"),
/** 新工单 默认 **/
DEFAULT_ORDER("", "/pages/order-manage/order-manage?stateCur=6"),
/**售后工单待处理提醒**/
AFTER_SALES_ORDER("", "/pages/order-manage/order-manage?tabCur=4"),
/**售后工单待处理提醒**/
AFTER_SALES_TIMEOUT_ORDER("", "/pages/order-manage/order-manage?stateCur=2"),
/**服务工单超时通知**/
TIMEOUT_ORDER("", "/pages/order-manage/order-manage?stateCur=2"),
/** 一小时订单 工单处理提醒*/
ONEHOUR_ORDER("", "/pages/order-manage/order-manage"),
/** 急报 **/
WARN_ORDER("", "0RSuVHHP_okErJ1acQmIirBU7TrQYR0xPBgBHyt_azA"),
/** 默认工单处理提醒 **/
DEFAULT_HANDLE_ORDER("", "/pages/order-manage/order-manage"),
/** 超时消息通知 */
// OVER_TIME("","8I5BnJMfwj-Z7udhNm9Z-kdjdg4__MV5ug1x8KKsbc0"),
/** 今日单消息通知 */
// TODAY_ORDER("", "yqd3p4qsqn1RiyUb8kO4dPqoGKipRQg_y99nGw0jtLE"),
/** 明日单通知 工单处理提醒*/
TOMORROW_ORDER("", " /pages/order-manage/order-manage?stateCur=4");
/** 不同意排单通知 */
// NOT_AGREE_PLAIN("", "yqd3p4qsqn1RiyUb8kO4dPqoGKipRQg_y99nGw0jtLE"),
/** 不同意完单通知 */
// NOT_AGREE_FINISH("","Yd2PJIdgBhEadi3EkAGyS4DiFp1Rd5ErsEs_jEt-HX4"),
/** 子师傅拒绝接单/退单通知 */
// DETAIL_REJECT("", "Yd2PJIdgBhEadi3EkAGyS4DiFp1Rd5ErsEs_jEt-HX4")
private String type;
private String tempCode;
WxStatusEnum(String type, String tempCode) {
this.type = type;
this.tempCode = tempCode;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getTempCode() {
return tempCode;
}
public void setTempCode(String tempCode) {
this.tempCode = tempCode;
}
}

View File

@ -0,0 +1,208 @@
package com.ghy.common.utils;
import com.alibaba.fastjson.JSONObject;
import com.ghy.common.utils.http.HttpUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
/**
* 百度地图API工具类
* 专门用于地址转经纬度等地理编码功能
*
* @author system
*/
@Component
public class BaiduMapUtils {
private static final Logger logger = LoggerFactory.getLogger(BaiduMapUtils.class);
@Value("${baidu.ak}")
private String baiduAk;
/**
* 通过详细地址获取经纬度坐标
*
* @param provinceName 省份名称
* @param cityName 城市名称
* @param countryName 区县名称
* @param streetName 街道名称
* @param detailAddress 详细地址
* @return 包含经纬度的Map失败返回null
*/
public Map<String, Double> getCoordinatesByAddress(String provinceName, String cityName,
String countryName, String streetName,
String detailAddress) {
try {
// 构建完整地址
StringBuilder fullAddress = new StringBuilder();
if (provinceName != null && !provinceName.trim().isEmpty()) {
fullAddress.append(provinceName);
}
if (cityName != null && !cityName.trim().isEmpty()) {
fullAddress.append(cityName);
}
if (countryName != null && !countryName.trim().isEmpty()) {
fullAddress.append(countryName);
}
if (streetName != null && !streetName.trim().isEmpty()) {
fullAddress.append(streetName);
}
if (detailAddress != null && !detailAddress.trim().isEmpty()) {
fullAddress.append(detailAddress);
}
String address = fullAddress.toString();
if (address.trim().isEmpty()) {
logger.warn("地址信息为空,无法获取坐标");
return null;
}
logger.info("开始调用百度地图API解析地址: {}", address);
// 调用百度地图正向地理编码API
String encoded = URLEncoder.encode(address, StandardCharsets.UTF_8.name());
String url = "https://api.map.baidu.com/geocoding/v3/?output=json&ak=" + baiduAk + "&address=" + encoded;
// 使用HttpUtils发送GET请求
String result = HttpUtils.sendGet(url);
if (result == null || result.trim().isEmpty()) {
logger.error("百度地图API返回结果为空");
return null;
}
result = result.replaceAll("\n", "").replaceAll("\t", "");
// 解析返回结果
JSONObject resultJson = JSONObject.parseObject(result);
if ("0".equals(resultJson.getString("status"))) {
JSONObject location = resultJson.getJSONObject("result").getJSONObject("location");
Double lng = location.getDouble("lng");
Double lat = location.getDouble("lat");
Map<String, Double> coordinates = new HashMap<>();
coordinates.put("longitude", lng);
coordinates.put("latitude", lat);
logger.info("百度地图API解析成功: {} -> 经度={}, 纬度={}", address, lng, lat);
return coordinates;
} else {
logger.error("百度地图API解析失败: {} - {}", address, resultJson.getString("msg"));
return null;
}
} catch (Exception e) {
logger.error("调用百度地图API失败: {} - {}",
String.format("省:%s,市:%s,区:%s,街道:%s,详细:%s",
provinceName, cityName, countryName, streetName, detailAddress),
e.getMessage(), e);
return null;
}
}
/**
* 通过完整地址字符串获取经纬度坐标
*
* @param fullAddress 完整地址字符串
* @return 包含经纬度的Map失败返回null
*/
public Map<String, Double> getCoordinatesByFullAddress(String fullAddress) {
try {
if (fullAddress == null || fullAddress.trim().isEmpty()) {
logger.warn("完整地址为空,无法获取坐标");
return null;
}
logger.info("开始调用百度地图API解析完整地址: {}", fullAddress);
// 调用百度地图正向地理编码API
String encoded = URLEncoder.encode(fullAddress, StandardCharsets.UTF_8.name());
String url = "https://api.map.baidu.com/geocoding/v3/?output=json&ak=" + baiduAk + "&address=" + encoded;
// 使用HttpUtils发送GET请求
String result = HttpUtils.sendGet(url);
if (result == null || result.trim().isEmpty()) {
logger.error("百度地图API返回结果为空");
return null;
}
result = result.replaceAll("\n", "").replaceAll("\t", "");
// 解析返回结果
JSONObject resultJson = JSONObject.parseObject(result);
if ("0".equals(resultJson.getString("status"))) {
JSONObject location = resultJson.getJSONObject("result").getJSONObject("location");
Double lng = location.getDouble("lng");
Double lat = location.getDouble("lat");
Map<String, Double> coordinates = new HashMap<>();
coordinates.put("longitude", lng);
coordinates.put("latitude", lat);
logger.info("百度地图API解析成功: {} -> 经度={}, 纬度={}", fullAddress, lng, lat);
return coordinates;
} else {
logger.error("百度地图API解析失败: {} - {}", fullAddress, resultJson.getString("msg"));
return null;
}
} catch (Exception e) {
logger.error("调用百度地图API失败: {} - {}", fullAddress, e.getMessage(), e);
return null;
}
}
/**
* 通过经纬度获取地址信息逆地理编码
*
* @param longitude 经度
* @param latitude 纬度
* @return 包含地址信息的JSONObject失败返回null
*/
public JSONObject getAddressByCoordinates(Double longitude, Double latitude) {
try {
if (longitude == null || latitude == null) {
logger.warn("经纬度参数为空,无法获取地址");
return null;
}
logger.info("开始调用百度地图API逆解析坐标: 经度={}, 纬度={}", longitude, latitude);
// 调用百度地图逆地理编码API
String location = longitude + "," + latitude;
String url = "https://api.map.baidu.com/reverse_geocoding/v3/?ak=" + baiduAk +
"&output=json&coordtype=wgs84ll&location=" + location;
// 使用HttpUtils发送GET请求
String result = HttpUtils.sendGet(url);
if (result == null || result.trim().isEmpty()) {
logger.error("百度地图API返回结果为空");
return null;
}
result = result.replaceAll("\n", "").replaceAll("\t", "");
// 解析返回结果
JSONObject resultJson = JSONObject.parseObject(result);
if ("0".equals(resultJson.getString("status"))) {
logger.info("百度地图API逆解析成功: 经度={}, 纬度={}", longitude, latitude);
return resultJson;
} else {
logger.error("百度地图API逆解析失败: 经度={}, 纬度={} - {}",
longitude, latitude, resultJson.getString("msg"));
return null;
}
} catch (Exception e) {
logger.error("调用百度地图API逆解析失败: 经度={}, 纬度={} - {}",
longitude, latitude, e.getMessage(), e);
return null;
}
}
}

View File

@ -0,0 +1,166 @@
package com.ghy.common.utils;
/**
* 地理位置计算工具类
* 提供经纬度之间的距离计算功能
*
* @author clunt
*/
public class LocationUtils {
/** 地球半径(千米) */
private static final double EARTH_RADIUS_KM = 6371.0;
/** 地球半径(米) */
private static final double EARTH_RADIUS_M = 6371000.0;
/**
* 使用Haversine公式计算两点间的距离
*
* @param lat1 第一个点的纬度
* @param lng1 第一个点的经度
* @param lat2 第二个点的纬度
* @param lng2 第二个点的经度
* @return 距离
*/
public static double getDistanceInMeters(double lat1, double lng1, double lat2, double lng2) {
return getDistanceInKilometers(lat1, lng1, lat2, lng2) * 1000;
}
/**
* 使用Haversine公式计算两点间的距离千米
*
* @param lat1 第一个点的纬度
* @param lng1 第一个点的经度
* @param lat2 第二个点的纬度
* @param lng2 第二个点的经度
* @return 距离千米
*/
public static double getDistanceInKilometers(double lat1, double lng1, double lat2, double lng2) {
// 将角度转换为弧度
double lat1Rad = Math.toRadians(lat1);
double lng1Rad = Math.toRadians(lng1);
double lat2Rad = Math.toRadians(lat2);
double lng2Rad = Math.toRadians(lng2);
// 计算经纬度差值
double deltaLat = lat2Rad - lat1Rad;
double deltaLng = lng2Rad - lng1Rad;
// Haversine公式
double a = Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2) +
Math.cos(lat1Rad) * Math.cos(lat2Rad) *
Math.sin(deltaLng / 2) * Math.sin(deltaLng / 2);
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return EARTH_RADIUS_KM * c;
}
/**
* 判断某个点是否在指定范围内
*
* @param centerLat 中心点纬度
* @param centerLng 中心点经度
* @param targetLat 目标点纬度
* @param targetLng 目标点经度
* @param radiusKm 半径千米
* @return 是否在范围内
*/
public static boolean isWithinRadius(double centerLat, double centerLng,
double targetLat, double targetLng,
double radiusKm) {
double distance = getDistanceInKilometers(centerLat, centerLng, targetLat, targetLng);
return distance <= radiusKm;
}
/**
* 格式化距离显示
*
* @param distanceInMeters 距离
* @return 格式化后的距离字符串
*/
public static String formatDistance(double distanceInMeters) {
if (distanceInMeters < 1000) {
return String.format("%.0f米", distanceInMeters);
} else {
double km = distanceInMeters / 1000;
if (km < 10) {
return String.format("%.1f公里", km);
} else {
return String.format("%.0f公里", km);
}
}
}
/**
* 计算两个坐标点的方位角以正北为0度顺时针
*
* @param lat1 起点纬度
* @param lng1 起点经度
* @param lat2 终点纬度
* @param lng2 终点经度
* @return 方位角度数0-360
*/
public static double getBearing(double lat1, double lng1, double lat2, double lng2) {
double lat1Rad = Math.toRadians(lat1);
double lat2Rad = Math.toRadians(lat2);
double deltaLngRad = Math.toRadians(lng2 - lng1);
double y = Math.sin(deltaLngRad) * Math.cos(lat2Rad);
double x = Math.cos(lat1Rad) * Math.sin(lat2Rad) -
Math.sin(lat1Rad) * Math.cos(lat2Rad) * Math.cos(deltaLngRad);
double bearingRad = Math.atan2(y, x);
double bearingDeg = Math.toDegrees(bearingRad);
// 转换为0-360度
return (bearingDeg + 360) % 360;
}
/**
* 将方位角转换为方向描述
*
* @param bearing 方位角度数
* @return 方向描述
*/
public static String getDirectionDescription(double bearing) {
String[] directions = {"", "东北", "", "东南", "", "西南", "西", "西北"};
int index = (int) Math.round(bearing / 45) % 8;
return directions[index];
}
/**
* 验证经纬度的有效性
*
* @param latitude 纬度
* @param longitude 经度
* @return 是否有效
*/
public static boolean isValidCoordinate(Double latitude, Double longitude) {
if (latitude == null || longitude == null) {
return false;
}
return latitude >= -90 && latitude <= 90 && longitude >= -180 && longitude <= 180;
}
/**
* 计算中心点坐标多个点的几何中心
*
* @param coordinates 坐标点数组每个元素为[纬度, 经度]
* @return 中心点坐标 [纬度, 经度]
*/
public static double[] getCenterPoint(double[][] coordinates) {
if (coordinates == null || coordinates.length == 0) {
return null;
}
double sumLat = 0, sumLng = 0;
for (double[] coord : coordinates) {
sumLat += coord[0];
sumLng += coord[1];
}
return new double[]{sumLat / coordinates.length, sumLng / coordinates.length};
}
}

View File

@ -32,7 +32,7 @@ public class QiniuUtils {
//解析上传成功的结果 //解析上传成功的结果
DefaultPutRet putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class); DefaultPutRet putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class);
return "http://gqz.opsoul.com/" + putRet.key; return "https://gqz.opsoul.com/" + putRet.key;
} }
} }

View File

@ -4,6 +4,7 @@ import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.ghy.common.config.WxConfig; import com.ghy.common.config.WxConfig;
import com.ghy.common.enums.WxMsgEnum; import com.ghy.common.enums.WxMsgEnum;
import com.ghy.common.enums.WxStatusEnum;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -13,7 +14,9 @@ import java.io.InputStream;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
/** /**
@ -61,6 +64,12 @@ public class WechatMsgUtils {
* @param token 鉴权token信息 * @param token 鉴权token信息
*/ */
public static void sendWeChatMsg(String token, String userOpenId, WxMsgEnum mesType, Map<String, Object> dataMap) { public static void sendWeChatMsg(String token, String userOpenId, WxMsgEnum mesType, Map<String, Object> dataMap) {
String type=mesType+"";
String wxStatusByWxMsgUrl=getWxStatusByWxMsg(WxStatusEnum.valueOf(type));
if (StringUtils.isEmpty(wxStatusByWxMsgUrl)){
wxStatusByWxMsgUrl="/pages/order-manage/order-manage";
}
log.info("传入的参数值{}获取到的url{}",type,wxStatusByWxMsgUrl);
// 接口地址 // 接口地址
String sendMsgApi = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" + token; String sendMsgApi = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" + token;
//整体参数map //整体参数map
@ -70,8 +79,8 @@ public class WechatMsgUtils {
paramMap.put("template_id", mesType.getTempCode()); paramMap.put("template_id", mesType.getTempCode());
paramMap.put("url",""); paramMap.put("url","");
Map<String, Object> miniprogram=new HashMap<>(); Map<String, Object> miniprogram=new HashMap<>();
miniprogram.put("appid",""); miniprogram.put("appid","wx105ce607b514ff2a");
miniprogram.put("pagepath",""); miniprogram.put("pagepath",wxStatusByWxMsgUrl);
paramMap.put("miniprogram",miniprogram); paramMap.put("miniprogram",miniprogram);
JSONObject jsonObject = new JSONObject(); JSONObject jsonObject = new JSONObject();
for (Map.Entry<String, Object> objectEntry : dataMap.entrySet()) { for (Map.Entry<String, Object> objectEntry : dataMap.entrySet()) {
@ -143,4 +152,7 @@ public class WechatMsgUtils {
} }
return result; return result;
} }
public static String getWxStatusByWxMsg(WxStatusEnum type){
return type.getTempCode();
}
} }

View File

@ -4,6 +4,7 @@ import com.ghy.common.annotation.Excel;
import com.ghy.common.core.domain.BaseEntity; import com.ghy.common.core.domain.BaseEntity;
import lombok.Data; import lombok.Data;
import java.math.BigDecimal;
import java.util.List; import java.util.List;
/** /**
@ -64,6 +65,16 @@ public class CustomerAddress extends BaseEntity {
private Boolean needNameFlag = false; private Boolean needNameFlag = false;
/**
* 经度
*/
private BigDecimal longitude;
/**
* 纬度
*/
private BigDecimal latitude;
/*是否删除id*/ /*是否删除id*/
private Long isDelete; private Long isDelete;
} }

View File

@ -42,6 +42,10 @@ public class CustomerSelection extends BaseEntity
@Excel(name = "选品类型 1.选取 2.屏蔽") @Excel(name = "选品类型 1.选取 2.屏蔽")
private Long selectionType; private Long selectionType;
/** 类型 */
@Excel(name = "类型")
private Integer type;
public List<Long> getDeptCategoryIds() { public List<Long> getDeptCategoryIds() {
return deptCategoryIds; return deptCategoryIds;
} }
@ -105,6 +109,16 @@ public class CustomerSelection extends BaseEntity
return selectionType; return selectionType;
} }
public void setType(Integer type)
{
this.type = type;
}
public Integer getType()
{
return type;
}
@Override @Override
public String toString() { public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
@ -114,6 +128,7 @@ public class CustomerSelection extends BaseEntity
.append("customerId", getCustomerId()) .append("customerId", getCustomerId())
.append("deptCategoryId", getDeptCategoryId()) .append("deptCategoryId", getDeptCategoryId())
.append("selectionType", getSelectionType()) .append("selectionType", getSelectionType())
.append("type", getType())
.append("createBy", getCreateBy()) .append("createBy", getCreateBy())
.append("createTime", getCreateTime()) .append("createTime", getCreateTime())
.append("updateBy", getUpdateBy()) .append("updateBy", getUpdateBy())

View File

@ -55,4 +55,11 @@ public interface CustomerAddressMapper {
@Param("cityId") Long cityId, @Param("cityId") Long cityId,
@Param("countryId") Long countryId, @Param("countryId") Long countryId,
@Param("address") String address); @Param("address") String address);
/**
* 批量查询地址信息
* @param customerAddressIds 地址ID列表
* @return 地址列表
*/
List<CustomerAddress> selectByIds(@Param("customerAddressIds") List<Long> customerAddressIds);
} }

View File

@ -51,4 +51,11 @@ public interface CustomerAddressService {
int updateCustomerAddress(CustomerAddress customerAddress); int updateCustomerAddress(CustomerAddress customerAddress);
CustomerAddress selectByCustomerAndAddress(Long customerId, Long provinceId, Long cityId, Long countryId, String address); CustomerAddress selectByCustomerAndAddress(Long customerId, Long provinceId, Long cityId, Long countryId, String address);
/**
* 批量查询地址信息
* @param customerAddressIds 地址ID列表
* @return 地址列表
*/
List<CustomerAddress> selectByIds(List<Long> customerAddressIds);
} }

View File

@ -10,7 +10,11 @@ import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.List; import java.util.List;
import java.util.Map;
import com.alibaba.fastjson.JSONObject;
import com.ghy.common.utils.http.HttpUtils;
@Slf4j @Slf4j
@Service @Service
@ -21,6 +25,7 @@ public class CustomerAddressServiceImpl implements CustomerAddressService {
@Resource @Resource
private ISysAreaService iSysAreaService; private ISysAreaService iSysAreaService;
private static final Logger logger = LoggerFactory.getLogger(CustomerAddressServiceImpl.class); private static final Logger logger = LoggerFactory.getLogger(CustomerAddressServiceImpl.class);
@Override @Override
@ -62,16 +67,27 @@ public class CustomerAddressServiceImpl implements CustomerAddressService {
@Override @Override
public CustomerAddress selectByCustomerAddressId(Long customerAddressId) { public CustomerAddress selectByCustomerAddressId(Long customerAddressId) {
return customerAddressMapper.selectByCustomerAddressId(customerAddressId); CustomerAddress address= customerAddressMapper.selectByCustomerAddressId(customerAddressId);
address.setCountryName(iSysAreaService.selectById(address.getCountryId()).getAreaName());
address.setCityName(iSysAreaService.selectById(address.getCityId()).getAreaName());
address.setProvinceName(iSysAreaService.selectById(address.getProvinceId()).getAreaName());
if(address.getStreetId()!=null){
address.setStreetName(iSysAreaService.selectById(address.getStreetId()).getAreaName());
}
return address;
} }
@Override @Override
public int insertCustomerAddress(CustomerAddress customerAddress) { public int insertCustomerAddress(CustomerAddress customerAddress) {
// 在插入前自动获取经纬度
setGeocodingForAddress(customerAddress);
return customerAddressMapper.insertCustomerAddress(customerAddress); return customerAddressMapper.insertCustomerAddress(customerAddress);
} }
@Override @Override
public int updateCustomerAddress(CustomerAddress customerAddress) { public int updateCustomerAddress(CustomerAddress customerAddress) {
// 在更新前自动获取经纬度
setGeocodingForAddress(customerAddress);
return customerAddressMapper.updateCustomerAddress(customerAddress); return customerAddressMapper.updateCustomerAddress(customerAddress);
} }
@ -79,4 +95,97 @@ public class CustomerAddressServiceImpl implements CustomerAddressService {
public CustomerAddress selectByCustomerAndAddress(Long customerId, Long provinceId, Long cityId, Long countryId, String address) { public CustomerAddress selectByCustomerAndAddress(Long customerId, Long provinceId, Long cityId, Long countryId, String address) {
return customerAddressMapper.selectByCustomerAndAddress(customerId, provinceId, cityId, countryId, address); return customerAddressMapper.selectByCustomerAndAddress(customerId, provinceId, cityId, countryId, address);
} }
@Override
public List<CustomerAddress> selectByIds(List<Long> customerAddressIds) {
if (customerAddressIds == null || customerAddressIds.isEmpty()) {
return null;
}
return customerAddressMapper.selectByIds(customerAddressIds);
}
/**
* 为地址设置经纬度信息
*
* @param customerAddress 客户地址对象
*/
private void setGeocodingForAddress(CustomerAddress customerAddress) {
try {
// 如果已经有经纬度信息则跳过
if (customerAddress.getLongitude() != null && customerAddress.getLatitude() != null) {
logger.info("地址已有经纬度信息,跳过获取");
return;
}
// 获取地址名称信息
String provinceName = null;
String cityName = null;
String countryName = null;
String streetName = null;
if (customerAddress.getProvinceId() != null) {
try {
provinceName = iSysAreaService.selectById(customerAddress.getProvinceId()).getAreaName();
} catch (Exception e) {
logger.warn("获取省份名称失败: {}", e.getMessage());
}
}
if (customerAddress.getCityId() != null) {
try {
cityName = iSysAreaService.selectById(customerAddress.getCityId()).getAreaName();
} catch (Exception e) {
logger.warn("获取城市名称失败: {}", e.getMessage());
}
}
if (customerAddress.getCountryId() != null) {
try {
countryName = iSysAreaService.selectById(customerAddress.getCountryId()).getAreaName();
} catch (Exception e) {
logger.warn("获取区县名称失败: {}", e.getMessage());
}
}
if (customerAddress.getStreetId() != null) {
try {
streetName = iSysAreaService.selectById(customerAddress.getStreetId()).getAreaName();
} catch (Exception e) {
logger.warn("获取街道名称失败: {}", e.getMessage());
}
}
// 调用百度地理编码接口获取经纬度
try {
JSONObject requestBody = new JSONObject();
requestBody.put("provinceName", provinceName);
requestBody.put("cityName", cityName);
requestBody.put("countryName", countryName);
requestBody.put("streetName", streetName);
requestBody.put("address", customerAddress.getAddress());
// 调用百度地理编码接口
String url = "https://gmhl.gmjlb.com/tool/baidu/geocode";
String result = HttpUtils.sendPost(url, requestBody.toJSONString());
JSONObject responseJson = JSONObject.parseObject(result);
if (responseJson.getInteger("code") == 200) {
JSONObject data = responseJson.getJSONObject("data");
BigDecimal longitude = data.getBigDecimal("longitude");
BigDecimal latitude = data.getBigDecimal("latitude");
customerAddress.setLongitude(longitude);
customerAddress.setLatitude(latitude);
logger.info("成功获取地址经纬度: 经度={}, 纬度={}", longitude, latitude);
} else {
logger.warn("百度地理编码接口调用失败: {}", responseJson.getString("msg"));
}
} catch (Exception e) {
logger.error("调用百度地理编码接口异常: {}", e.getMessage(), e);
}
} catch (Exception e) {
logger.error("获取地址经纬度失败: {}", e.getMessage(), e);
}
}
} }

View File

@ -30,6 +30,9 @@ public class CustomerServiceImpl implements CustomerService {
@Override @Override
public Customer selectByCustomerId(Long customerId) { public Customer selectByCustomerId(Long customerId) {
if (customerId == null) {
return null;
}
return customerMapper.selectByCustomerId(customerId); return customerMapper.selectByCustomerId(customerId);
} }

View File

@ -23,11 +23,13 @@
<result property="cityName" column="city_name" /> <result property="cityName" column="city_name" />
<result property="countryName" column="country_name" /> <result property="countryName" column="country_name" />
<result property="streetName" column="street_name" /> <result property="streetName" column="street_name" />
<result property="longitude" column="longitude" />
<result property="latitude" column="latitude" />
</resultMap> </resultMap>
<sql id="selectCustomerAddress"> <sql id="selectCustomerAddress">
SELECT customer_address_id, customer_id, name, phone, province_id, city_id, country_id, street_id, status, SELECT customer_address_id, customer_id, name, phone, province_id, city_id, country_id, street_id, status,
address, create_by, create_time, remark, is_default address, create_by, create_time, remark, is_default, longitude, latitude
FROM customer_address FROM customer_address
</sql> </sql>
@ -114,6 +116,8 @@
<if test="isDefault != null">is_default,</if> <if test="isDefault != null">is_default,</if>
<if test="createBy != null and createBy != ''">create_by,</if> <if test="createBy != null and createBy != ''">create_by,</if>
<if test="remark != null and remark != ''">remark,</if> <if test="remark != null and remark != ''">remark,</if>
<if test="longitude != null">longitude,</if>
<if test="latitude != null">latitude,</if>
deleted, deleted,
create_time create_time
)values( )values(
@ -129,6 +133,8 @@
<if test="isDefault != null">#{isDefault},</if> <if test="isDefault != null">#{isDefault},</if>
<if test="createBy != null and createBy != ''">#{createBy},</if> <if test="createBy != null and createBy != ''">#{createBy},</if>
<if test="remark != null and remark != ''">#{remark},</if> <if test="remark != null and remark != ''">#{remark},</if>
<if test="longitude != null">#{longitude},</if>
<if test="latitude != null">#{latitude},</if>
0, 0,
sysdate() sysdate()
) )
@ -147,10 +153,21 @@
<if test="isDefault != null">is_default = #{isDefault},</if> <if test="isDefault != null">is_default = #{isDefault},</if>
<if test="status != null and status != ''">status = #{status},</if> <if test="status != null and status != ''">status = #{status},</if>
<if test="remark != null">remark = #{remark},</if> <if test="remark != null">remark = #{remark},</if>
<if test="longitude != null">longitude = #{longitude},</if>
<if test="latitude != null">latitude = #{latitude},</if>
<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if> <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
update_time = sysdate() update_time = sysdate()
</set> </set>
where customer_address_id = #{customerAddressId} where customer_address_id = #{customerAddressId}
</update> </update>
<select id="selectByIds" resultMap="CustomerAddressResult">
<include refid="selectCustomerAddress"/>
WHERE customer_address_id IN
<foreach collection="customerAddressIds" item="id" open="(" separator="," close=")">
#{id}
</foreach>
AND deleted = 0
</select>
</mapper> </mapper>

View File

@ -11,6 +11,7 @@
<result property="customerId" column="customer_id" /> <result property="customerId" column="customer_id" />
<result property="deptCategoryId" column="dept_category_id" /> <result property="deptCategoryId" column="dept_category_id" />
<result property="selectionType" column="selection_type" /> <result property="selectionType" column="selection_type" />
<result property="type" column="type" />
<result property="createBy" column="create_by" /> <result property="createBy" column="create_by" />
<result property="createTime" column="create_time" /> <result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" /> <result property="updateBy" column="update_by" />
@ -19,7 +20,7 @@
</resultMap> </resultMap>
<sql id="selectCustomerSelectionVo"> <sql id="selectCustomerSelectionVo">
select id, work_id, dept_id, customer_id, dept_category_id, selection_type, create_by, create_time, update_by, update_time, remark from customer_selection select id, work_id, dept_id, customer_id, dept_category_id, selection_type, type, create_by, create_time, update_by, update_time, remark from customer_selection
</sql> </sql>
<select id="selectCustomerSelectionList" parameterType="CustomerSelection" resultMap="CustomerSelectionResult"> <select id="selectCustomerSelectionList" parameterType="CustomerSelection" resultMap="CustomerSelectionResult">
@ -30,6 +31,7 @@
<if test="customerId != null "> and customer_id = #{customerId}</if> <if test="customerId != null "> and customer_id = #{customerId}</if>
<if test="deptCategoryId != null "> and dept_category_id = #{deptCategoryId}</if> <if test="deptCategoryId != null "> and dept_category_id = #{deptCategoryId}</if>
<if test="selectionType != null "> and selection_type = #{selectionType}</if> <if test="selectionType != null "> and selection_type = #{selectionType}</if>
<if test="type != null and type != ''"> and type = #{type}</if>
</where> </where>
</select> </select>
@ -46,6 +48,7 @@
<if test="customerId != null">customer_id,</if> <if test="customerId != null">customer_id,</if>
<if test="deptCategoryId != null">dept_category_id,</if> <if test="deptCategoryId != null">dept_category_id,</if>
<if test="selectionType != null">selection_type,</if> <if test="selectionType != null">selection_type,</if>
<if test="type != null">type,</if>
<if test="createBy != null">create_by,</if> <if test="createBy != null">create_by,</if>
<if test="createTime != null">create_time,</if> <if test="createTime != null">create_time,</if>
<if test="updateBy != null">update_by,</if> <if test="updateBy != null">update_by,</if>
@ -58,6 +61,7 @@
<if test="customerId != null">#{customerId},</if> <if test="customerId != null">#{customerId},</if>
<if test="deptCategoryId != null">#{deptCategoryId},</if> <if test="deptCategoryId != null">#{deptCategoryId},</if>
<if test="selectionType != null">#{selectionType},</if> <if test="selectionType != null">#{selectionType},</if>
<if test="type != null">#{type},</if>
<if test="createBy != null">#{createBy},</if> <if test="createBy != null">#{createBy},</if>
<if test="createTime != null">#{createTime},</if> <if test="createTime != null">#{createTime},</if>
<if test="updateBy != null">#{updateBy},</if> <if test="updateBy != null">#{updateBy},</if>
@ -74,6 +78,7 @@
<if test="customerId != null">customer_id = #{customerId},</if> <if test="customerId != null">customer_id = #{customerId},</if>
<if test="deptCategoryId != null">dept_category_id = #{deptCategoryId},</if> <if test="deptCategoryId != null">dept_category_id = #{deptCategoryId},</if>
<if test="selectionType != null">selection_type = #{selectionType},</if> <if test="selectionType != null">selection_type = #{selectionType},</if>
<if test="type != null">type = #{type},</if>
<if test="createBy != null">create_by = #{createBy},</if> <if test="createBy != null">create_by = #{createBy},</if>
<if test="createTime != null">create_time = #{createTime},</if> <if test="createTime != null">create_time = #{createTime},</if>
<if test="updateBy != null">update_by = #{updateBy},</if> <if test="updateBy != null">update_by = #{updateBy},</if>

View File

@ -283,6 +283,7 @@ public class ShiroConfig
filterChainDefinitionMap.put("/special/skill/**", "anon"); filterChainDefinitionMap.put("/special/skill/**", "anon");
filterChainDefinitionMap.put("/customer/**", "anon"); filterChainDefinitionMap.put("/customer/**", "anon");
filterChainDefinitionMap.put("/goods/**", "anon"); filterChainDefinitionMap.put("/goods/**", "anon");
filterChainDefinitionMap.put("/shop/**", "anon");
filterChainDefinitionMap.put("/financial/**", "anon"); filterChainDefinitionMap.put("/financial/**", "anon");
filterChainDefinitionMap.put("/tool/**", "anon"); filterChainDefinitionMap.put("/tool/**", "anon");
filterChainDefinitionMap.put("/adapay/**", "anon"); filterChainDefinitionMap.put("/adapay/**", "anon");

View File

@ -22,6 +22,14 @@
<groupId>com.ghy</groupId> <groupId>com.ghy</groupId>
<artifactId>ghy-common</artifactId> <artifactId>ghy-common</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.ghy</groupId>
<artifactId>ghy-custom</artifactId>
</dependency>
<dependency>
<groupId>com.ghy</groupId>
<artifactId>ghy-shop</artifactId>
</dependency>
</dependencies> </dependencies>

View File

@ -105,4 +105,16 @@ public class DeptGoodsCategory extends GoodsCategory {
private String shopInsuranceIds; private String shopInsuranceIds;
private Long customerId;
private Integer isSetting;
@Excel(name = "服务类目ID", cellType = Excel.ColumnType.NUMERIC)
private Long serviceCategoryId; // 服务类目ID
@Excel(name = "服务类目名称", cellType = Excel.ColumnType.STRING)
private String serviceCategoryName; // 服务类目名称
@Excel(name = "倒计时小时数", cellType = Excel.ColumnType.NUMERIC)
private Integer countdownHours; // 倒计时小时数
} }

View File

@ -2,6 +2,7 @@ package com.ghy.goods.domain;
import com.ghy.common.annotation.Excel; import com.ghy.common.annotation.Excel;
import com.ghy.common.core.domain.BaseEntity; import com.ghy.common.core.domain.BaseEntity;
import com.ghy.shop.domain.Shop;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
@ -28,6 +29,12 @@ public class Goods extends BaseEntity {
@Excel(name = "部门id") @Excel(name = "部门id")
private Long deptId; private Long deptId;
@Excel(name = "店铺id")
private Long shopId;
@Excel(name = "店铺名称")
private String shopName;
@Excel(name = "名称") @Excel(name = "名称")
private String goodsName; private String goodsName;
@ -90,6 +97,36 @@ public class Goods extends BaseEntity {
private String keyword; private String keyword;
/*类型:服务商品 1和 配件商品 2*/
private Integer type;
private List<InsuranceManager> insuranceManagers; private List<InsuranceManager> insuranceManagers;
private String distance;
/** 纬度 */
private Double latitude;
/** 经度 */
private Double longitude;
/** 到店服务 1-到店服务 2-到店服务+配送 3-到店服务+上门 */
@Excel(name = "到店服务")
private Integer storeService;
/** 安装服务 1-包安装 2-不包安装 3-自费安装 */
@Excel(name = "安装服务", readConverterExp = "1=包安装,2=不包安装,3=自费安装")
private Integer installService;
/** 配送服务 1-包邮 2-同城包送 3-邮费自付 4-自提 */
@Excel(name = "配送服务", readConverterExp = "1=包邮,2=同城包送,3=邮费自付,4=自提")
private Integer deliveryService;
private Shop shop;
private Shop serviceShop;
private List<Long> goodsIds;
private String goodsStandard;
} }

View File

@ -68,4 +68,7 @@ public class GoodsStandard extends BaseEntity {
@Excel(name = "截留金额", cellType = Excel.ColumnType.STRING) @Excel(name = "截留金额", cellType = Excel.ColumnType.STRING)
private BigDecimal retainMoney; private BigDecimal retainMoney;
@Excel(name = "图片地址")
private String imageUrl;
} }

View File

@ -64,4 +64,18 @@ public interface GoodsStandardMapper {
int update(GoodsStandard goodsStandard); int update(GoodsStandard goodsStandard);
/**
* 根据规格名称模糊查询商品规格
* @param standardName 规格名称支持模糊查询
* @return 符合条件的商品规格列表
*/
List<GoodsStandard> selectByStandardNameLike(@Param("standardName") String standardName);
/**
* 根据部门商品分类ID查询商品规格
* @param deptGoodsCategoryId 部门商品分类ID
* @return 符合条件的商品规格列表
*/
List<GoodsStandard> selectByDeptGoodsCategoryId(@Param("deptGoodsCategoryId") Long deptGoodsCategoryId);
} }

View File

@ -81,4 +81,12 @@ public interface DeptGoodsCategoryService {
*/ */
AjaxResult batchAdd(Long parentId, Long[] checked); AjaxResult batchAdd(Long parentId, Long[] checked);
/**
* 递归查找类目的倒计时小时数如果当前类目没有设置则查找上级类目
*
* @param deptGoodsCategoryId 分公司类目ID
* @return 倒计时小时数如果所有上级类目都没有设置则返回null
*/
Integer findCountdownHoursRecursively(Long deptGoodsCategoryId);
} }

View File

@ -95,4 +95,12 @@ public interface GoodsService {
int edit(Goods goods); int edit(Goods goods);
/**
* 通过商品ID查找其服务类目然后通过服务类目的四级类目查询所有相关商品
*
* @param goodsId 商品ID
* @return 相关商品列表只包含上架商品
*/
List<Goods> selectGoodsByServiceCategory(Long goodsId);
} }

View File

@ -59,4 +59,18 @@ public interface GoodsStandardService {
int update(GoodsStandard goodsStandard); int update(GoodsStandard goodsStandard);
/**
* 根据规格名称模糊查询商品规格
* @param standardName 规格名称支持模糊查询
* @return 符合条件的商品规格列表
*/
List<GoodsStandard> selectByStandardNameLike(String standardName);
/**
* 根据部门商品分类ID获取所有商品
* @param deptGoodsCategoryId 部门商品分类ID
* @return 商品列表
*/
List<Goods> selectGoodsByDeptGoodsCategoryId(Long deptGoodsCategoryId);
} }

View File

@ -6,6 +6,10 @@ import com.ghy.common.core.domain.Ztree;
import com.ghy.common.core.text.Convert; import com.ghy.common.core.text.Convert;
import com.ghy.common.utils.StringUtils; import com.ghy.common.utils.StringUtils;
import com.ghy.common.utils.bean.BeanUtils; import com.ghy.common.utils.bean.BeanUtils;
import com.ghy.customer.domain.Customer;
import com.ghy.customer.domain.CustomerSelection;
import com.ghy.customer.service.CustomerService;
import com.ghy.customer.service.ICustomerSelectionService;
import com.ghy.goods.domain.DeptGoodsCategory; import com.ghy.goods.domain.DeptGoodsCategory;
import com.ghy.goods.domain.GoodsCategory; import com.ghy.goods.domain.GoodsCategory;
import com.ghy.goods.domain.GoodsImgs; import com.ghy.goods.domain.GoodsImgs;
@ -36,6 +40,11 @@ public class DeptGoodsCategoryServiceImpl implements DeptGoodsCategoryService {
private DeptGoodsCategoryMapper deptGoodsCategoryMapper; private DeptGoodsCategoryMapper deptGoodsCategoryMapper;
@Resource @Resource
private GoodsImgsService goodsImgsService; private GoodsImgsService goodsImgsService;
@Resource
private CustomerService customerService;
@Resource
private ICustomerSelectionService customerSelectionService;
@Override @Override
public List<DeptGoodsCategory> list(DeptGoodsCategory category) { public List<DeptGoodsCategory> list(DeptGoodsCategory category) {
@ -85,8 +94,33 @@ public class DeptGoodsCategoryServiceImpl implements DeptGoodsCategoryService {
@Override @Override
public List<DeptGoodsCategory> appList(DeptGoodsCategory deptGoodsCategory) { public List<DeptGoodsCategory> appList(DeptGoodsCategory deptGoodsCategory) {
// 第一层 Customer customer= customerService.selectByCustomerId(deptGoodsCategory.getCustomerId());
List<DeptGoodsCategory> goodsCategoryList = deptGoodsCategoryMapper.appList(deptGoodsCategory); List<DeptGoodsCategory> goodsCategoryList = new ArrayList<>();
if (customer == null) {
log.warn("未找到客户customerId: {}", deptGoodsCategory.getCustomerId());
// 返回全部类目或默认类目
goodsCategoryList = deptGoodsCategoryMapper.appList(deptGoodsCategory);
} else {
Long customerId = customer.getCustomerPlace();
if (customer.getPlaceStatus() == 2) {
customerId = customer.getCustomerId();
}
CustomerSelection customerSelection = new CustomerSelection();
customerSelection.setCustomerId(customerId);
List<CustomerSelection> customerSelections = customerSelectionService.selectCustomerSelectionList(customerSelection);
log.info("用户id{}获取到的类目列表{}", customerId, customerSelections);
if (deptGoodsCategory.getIsSetting() == 1) {
goodsCategoryList = deptGoodsCategoryMapper.appList(deptGoodsCategory);
} else {
for (CustomerSelection customerSelection1 : customerSelections) {
DeptGoodsCategory deptGoodsCategory1 = deptGoodsCategoryMapper.selectOneByGoodsCategoryId(customerSelection1.getDeptCategoryId());
if (customerSelection1.getSelectionType() == 1) {
goodsCategoryList.add(deptGoodsCategory1);
}
}
}
}
log.info("筛选完的类目列表{}",goodsCategoryList);
// 第二层 // 第二层
this.fillChild(goodsCategoryList); this.fillChild(goodsCategoryList);
// 第三层 // 第三层
@ -130,8 +164,11 @@ public class DeptGoodsCategoryServiceImpl implements DeptGoodsCategoryService {
GoodsCategory goodsCategory = goodsCategoryService.selectById(checked[0]); GoodsCategory goodsCategory = goodsCategoryService.selectById(checked[0]);
Assert.notNull(goodsCategory, "不存在该类目: " + checked[0]); Assert.notNull(goodsCategory, "不存在该类目: " + checked[0]);
List<DeptGoodsCategory> oldList = deptGoodsCategoryMapper.selectByDeptId(parentId); List<DeptGoodsCategory> oldList = deptGoodsCategoryMapper.selectByDeptId(parentId);
Map<Long, Long> oldMap = oldList.stream().filter(x -> x.getType().equals(goodsCategory.getType()))
.collect(Collectors.toMap(GoodsCategory::getGoodsCategoryId, DeptGoodsCategory::getDeptGoodsCategoryId)); Map<Long, Long> oldMap = oldList.stream()
.filter(x -> x.getType() != null && goodsCategory.getType() != null && x.getType().equals(goodsCategory.getType()))
.collect(Collectors.toMap(DeptGoodsCategory::getGoodsCategoryId, DeptGoodsCategory::getDeptGoodsCategoryId));
HashSet<Long> newSet = new HashSet<>(Arrays.asList(checked)); HashSet<Long> newSet = new HashSet<>(Arrays.asList(checked));
Set<Long> intersection = oldList.stream().map(DeptGoodsCategory::getGoodsCategoryId).collect(Collectors.toSet()); Set<Long> intersection = oldList.stream().map(DeptGoodsCategory::getGoodsCategoryId).collect(Collectors.toSet());
intersection.addAll(Arrays.asList(checked)); intersection.addAll(Arrays.asList(checked));
@ -177,4 +214,42 @@ public class DeptGoodsCategoryServiceImpl implements DeptGoodsCategoryService {
} }
return ztrees; return ztrees;
} }
@Override
public Integer findCountdownHoursRecursively(Long deptGoodsCategoryId) {
if (deptGoodsCategoryId == null) {
log.warn("分公司类目ID为空无法查找倒计时小时数");
return null;
}
// 查询当前类目
DeptGoodsCategory currentCategory = deptGoodsCategoryMapper.selectById(deptGoodsCategoryId);
if (currentCategory == null) {
log.warn("未找到分公司类目ID: {}", deptGoodsCategoryId);
return null;
}
// 如果当前类目有设置倒计时小时数直接返回
if (currentCategory.getCountdownHours() != null) {
log.info("找到类目[{}]的倒计时小时数: {}", currentCategory.getGoodsCategoryName(), currentCategory.getCountdownHours());
return currentCategory.getCountdownHours();
}
// 如果当前类目没有设置查找上级类目
if (currentCategory.getParentCategoryId() != null) {
log.info("类目[{}]未设置倒计时小时数查找上级类目ID: {}", currentCategory.getGoodsCategoryName(), currentCategory.getParentCategoryId());
// 通过商品类目ID查找对应的分公司类目
DeptGoodsCategory parentDeptCategory = deptGoodsCategoryMapper.selectOneByGoodsCategoryId(currentCategory.getParentCategoryId());
if (parentDeptCategory != null) {
return findCountdownHoursRecursively(parentDeptCategory.getDeptGoodsCategoryId());
} else {
log.warn("未找到上级类目对应的分公司类目商品类目ID: {}", currentCategory.getParentCategoryId());
}
} else {
log.info("类目[{}]已是顶级类目,无上级类目可查找", currentCategory.getGoodsCategoryName());
}
return null;
}
} }

View File

@ -12,8 +12,12 @@ import com.ghy.goods.service.GoodsAreaService;
import com.ghy.goods.service.GoodsImgsService; import com.ghy.goods.service.GoodsImgsService;
import com.ghy.goods.service.GoodsService; import com.ghy.goods.service.GoodsService;
import com.ghy.goods.service.GoodsStandardService; import com.ghy.goods.service.GoodsStandardService;
import com.ghy.goods.service.DeptGoodsCategoryService;
import com.ghy.goods.domain.DeptGoodsCategory;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.stream.Collectors;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
@ -40,6 +44,8 @@ public class GoodsServiceImpl implements GoodsService {
private GoodsImgsService goodsImgsService; private GoodsImgsService goodsImgsService;
@Resource @Resource
private GoodsStandardService goodsStandardService; private GoodsStandardService goodsStandardService;
@Resource
private DeptGoodsCategoryService deptGoodsCategoryService;
@Override @Override
@Transactional @Transactional
@ -210,4 +216,40 @@ public class GoodsServiceImpl implements GoodsService {
return 0; return 0;
} }
@Override
public List<Goods> selectGoodsByServiceCategory(Long goodsId) {
// Get goods info by goodsId
Goods goods = selectById(goodsId);
if (goods == null || goods.getDeptGoodsCategoryId() == null) {
return new ArrayList<>();
}
// Get DeptGoodsCategory by deptGoodsCategoryId to obtain serviceCategory
DeptGoodsCategory deptGoodsCategory = deptGoodsCategoryService.selectOneByGoodsCategoryId(goods.getDeptGoodsCategoryId());
if (deptGoodsCategory == null || deptGoodsCategory.getServiceCategoryId() == null) {
return new ArrayList<>();
}
// Query all goods using this service category ID
Goods queryGoods = new Goods();
queryGoods.setDeptGoodsCategoryId(deptGoodsCategory.getServiceCategoryId());
queryGoods.setStatus(0); // Only query active goods
List<Goods> goodsList = selectGoodsList(queryGoods);
// If no goods found, try using fourth-level category query
if (goodsList.isEmpty()) {
DeptGoodsCategory serviceDeptCategory = deptGoodsCategoryService.selectOneByGoodsCategoryId(deptGoodsCategory.getServiceCategoryId());
if (serviceDeptCategory != null) {
// Use GoodsStandardService method to get goods list
goodsList = goodsStandardService.selectGoodsByDeptGoodsCategoryId(serviceDeptCategory.getDeptGoodsCategoryId());
// Filter to keep only active goods
goodsList = goodsList.stream()
.filter(item -> item.getStatus() != null && item.getStatus() == 0)
.collect(Collectors.toList());
}
}
return goodsList;
}
} }

View File

@ -1,8 +1,10 @@
package com.ghy.goods.service.impl; package com.ghy.goods.service.impl;
import com.ghy.goods.domain.Goods;
import com.ghy.goods.domain.GoodsStandard; import com.ghy.goods.domain.GoodsStandard;
import com.ghy.goods.mapper.GoodsStandardMapper; import com.ghy.goods.mapper.GoodsStandardMapper;
import com.ghy.goods.request.AppGoodsRequest; import com.ghy.goods.request.AppGoodsRequest;
import com.ghy.goods.service.GoodsService;
import com.ghy.goods.service.GoodsStandardService; import com.ghy.goods.service.GoodsStandardService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -27,6 +29,9 @@ public class GoodsStandardServiceImpl implements GoodsStandardService {
@Resource @Resource
private GoodsStandardMapper goodsStandardMapper; private GoodsStandardMapper goodsStandardMapper;
@Resource
private GoodsService goodsService;
@Override @Override
public GoodsStandard selectById(Long goodsStandardId) { public GoodsStandard selectById(Long goodsStandardId) {
return goodsStandardMapper.selectById(goodsStandardId); return goodsStandardMapper.selectById(goodsStandardId);
@ -86,4 +91,24 @@ public class GoodsStandardServiceImpl implements GoodsStandardService {
public int update(GoodsStandard goodsStandard) { public int update(GoodsStandard goodsStandard) {
return goodsStandardMapper.update(goodsStandard); return goodsStandardMapper.update(goodsStandard);
} }
@Override
public List<GoodsStandard> selectByStandardNameLike(String standardName) {
return goodsStandardMapper.selectByStandardNameLike(standardName);
}
@Override
public List<Goods> selectGoodsByDeptGoodsCategoryId(Long deptGoodsCategoryId) {
// 1. 根据deptGoodsCategoryId查询所有商品规格
List<GoodsStandard> goodsStandardList = goodsStandardMapper.selectByDeptGoodsCategoryId(deptGoodsCategoryId);
// 2. 提取所有不重复的goodsId
Set<Long> goodsIds = goodsStandardList.stream()
.map(GoodsStandard::getGoodsId)
.collect(Collectors.toSet());
// 4. 通过goodsId批量查询Goods实体
return goodsService.selectByIds(goodsIds);
}
} }

View File

@ -23,6 +23,9 @@
<result property="deptMoney" column="dept_money"/> <result property="deptMoney" column="dept_money"/>
<result property="retainRate" column="retain_rate"/> <result property="retainRate" column="retain_rate"/>
<result property="retainMoney" column="retain_money"/> <result property="retainMoney" column="retain_money"/>
<result property="serviceCategoryId" column="service_category_id"/>
<result property="serviceCategoryName" column="service_category_name"/>
<result property="countdownHours" column="countdown_hours"/>
<!-- pc扣点部分 start--> <!-- pc扣点部分 start-->
<result property="pcOneRate" column="pc_one_rate"/> <result property="pcOneRate" column="pc_one_rate"/>
<result property="pcTwoRate" column="pc_two_rate"/> <result property="pcTwoRate" column="pc_two_rate"/>
@ -45,7 +48,8 @@
SELECT dept_goods_category_id, dept_id, goods_category_id, category_sort, is_hot, cover, hot_name, SELECT dept_goods_category_id, dept_id, goods_category_id, category_sort, is_hot, cover, hot_name,
simple_goods_category_name, is_sure, is_cert, one_rate, two_rate, three_rate, dept_rate, simple_goods_category_name, is_sure, is_cert, one_rate, two_rate, three_rate, dept_rate,
pc_one_rate, pc_two_rate, pc_three_rate, pc_dept_rate, pc_dept_money, pc_retain_rate, pc_retain_money, pc_one_rate, pc_two_rate, pc_three_rate, pc_dept_rate, pc_dept_money, pc_retain_rate, pc_retain_money,
dept_money, retain_rate, retain_money, create_by, create_time, remark dept_money, retain_rate, retain_money, create_by, create_time, remark, service_category_id, service_category_name,
countdown_hours
FROM dept_goods_category FROM dept_goods_category
</sql> </sql>
@ -55,6 +59,7 @@
dgc.is_hot, dgc.cover, dgc.hot_name, dgc.is_sure, dgc.is_cert, dgc.dept_rate, dgc.dept_money, dgc.is_hot, dgc.cover, dgc.hot_name, dgc.is_sure, dgc.is_cert, dgc.dept_rate, dgc.dept_money,
dgc.retain_rate, dgc.retain_money, dgc.simple_goods_category_name, dgc.retain_rate, dgc.retain_money, dgc.simple_goods_category_name,
dgc.pc_one_rate, dgc.pc_two_rate, dgc.pc_three_rate, dgc.pc_dept_rate, dgc.pc_dept_money, dgc.pc_retain_rate, dgc.pc_retain_money, dgc.pc_one_rate, dgc.pc_two_rate, dgc.pc_three_rate, dgc.pc_dept_rate, dgc.pc_dept_money, dgc.pc_retain_rate, dgc.pc_retain_money,
dgc.service_category_id, dgc.service_category_name, dgc.countdown_hours,
gc.goods_category_name, gc.level, gc.parent_category_id, gc.type, gc.status gc.goods_category_name, gc.level, gc.parent_category_id, gc.type, gc.status
FROM dept_goods_category dgc LEFT JOIN goods_category gc ON dgc.goods_category_id = gc.goods_category_id FROM dept_goods_category dgc LEFT JOIN goods_category gc ON dgc.goods_category_id = gc.goods_category_id
</sql> </sql>
@ -92,6 +97,12 @@
<if test="deptMoney != null">dept_money = #{deptMoney},</if> <if test="deptMoney != null">dept_money = #{deptMoney},</if>
<if test="retainRate != null and retainRate != ''">retain_rate = #{retainRate},</if> <if test="retainRate != null and retainRate != ''">retain_rate = #{retainRate},</if>
<if test="retainMoney != null">retain_money = #{retainMoney},</if> <if test="retainMoney != null">retain_money = #{retainMoney},</if>
<if test="serviceCategoryId != null">service_category_id = #{serviceCategoryId},</if>
<if test="serviceCategoryName != null and serviceCategoryName != ''">service_category_name = #{serviceCategoryName},</if>
<choose>
<when test="countdownHours != null">countdown_hours = #{countdownHours},</when>
<otherwise>countdown_hours = NULL,</otherwise>
</choose>
<if test="pcDeptRate != null and pcDeptRate != ''">pc_dept_rate = #{pcDeptRate},</if> <if test="pcDeptRate != null and pcDeptRate != ''">pc_dept_rate = #{pcDeptRate},</if>
<if test="pcDeptMoney != null">pc_dept_money = #{pcDeptMoney},</if> <if test="pcDeptMoney != null">pc_dept_money = #{pcDeptMoney},</if>
@ -133,6 +144,9 @@
<if test="deptMoney != null and deptMoney != ''">dept_money,</if> <if test="deptMoney != null and deptMoney != ''">dept_money,</if>
<if test="retainRate != null and retainRate != ''">retain_rate,</if> <if test="retainRate != null and retainRate != ''">retain_rate,</if>
<if test="retainMoney != null and retainMoney != ''">retain_money,</if> <if test="retainMoney != null and retainMoney != ''">retain_money,</if>
<if test="serviceCategoryId != null">service_category_id,</if>
<if test="serviceCategoryName != null and serviceCategoryName != ''">service_category_name,</if>
countdown_hours,
<if test="pcDeptRate != null and pcDeptRate != ''">pc_dept_rate,</if> <if test="pcDeptRate != null and pcDeptRate != ''">pc_dept_rate,</if>
<if test="pcDeptMoney != null and pcDeptMoney != ''">pc_dept_money,</if> <if test="pcDeptMoney != null and pcDeptMoney != ''">pc_dept_money,</if>
@ -158,6 +172,9 @@
<if test="deptMoney != null and deptMoney != ''">#{deptMoney},</if> <if test="deptMoney != null and deptMoney != ''">#{deptMoney},</if>
<if test="retainRate != null and retainRate != ''">#{retainRate},</if> <if test="retainRate != null and retainRate != ''">#{retainRate},</if>
<if test="retainMoney != null and retainMoney != ''">#{retainMoney},</if> <if test="retainMoney != null and retainMoney != ''">#{retainMoney},</if>
<if test="serviceCategoryId != null">#{serviceCategoryId},</if>
<if test="serviceCategoryName != null and serviceCategoryName != ''">#{serviceCategoryName},</if>
#{countdownHours},
<if test="pcDeptRate != null and pcDeptRate != ''">#{pcDeptRate},</if> <if test="pcDeptRate != null and pcDeptRate != ''">#{pcDeptRate},</if>
<if test="pcDeptMoney != null and pcDeptMoney != ''">#{pcDeptMoney},</if> <if test="pcDeptMoney != null and pcDeptMoney != ''">#{pcDeptMoney},</if>

View File

@ -7,6 +7,8 @@
<id property="goodsId" column="goods_id" /> <id property="goodsId" column="goods_id" />
<result property="goodsCode" column="goods_code" /> <result property="goodsCode" column="goods_code" />
<result property="deptId" column="dept_id" /> <result property="deptId" column="dept_id" />
<result property="shopId" column="shop_id" />
<result property="shopName" column="shop_name" />
<result property="goodsName" column="goods_name" /> <result property="goodsName" column="goods_name" />
<result property="goodsDesc" column="goods_desc" /> <result property="goodsDesc" column="goods_desc" />
<result property="goodsUnit" column="goods_unit" /> <result property="goodsUnit" column="goods_unit" />
@ -26,17 +28,21 @@
<result property="remark" column="remark" /> <result property="remark" column="remark" />
<result property="areaDesc" column="area_desc" /> <result property="areaDesc" column="area_desc" />
<result property="keyword" column="keyword" /> <result property="keyword" column="keyword" />
<result property="type" column="type" />
<result property="storeService" column="store_service" />
<result property="installService" column="install_service" />
<result property="deliveryService" column="delivery_service" />
</resultMap> </resultMap>
<sql id="selectGoods"> <sql id="selectGoods">
SELECT goods_id, goods_code, dept_id, goods_name, goods_desc, warranty_period, serv_activity, goods_unit, expect_duration, goods_sort, worker_id, SELECT goods_id, goods_code, dept_id, shop_id, shop_name, goods_name, goods_desc, warranty_period, serv_activity, goods_unit, expect_duration, goods_sort, worker_id,
dept_goods_category_id, goods_img_url, goods_video_url, status, create_by, create_time, remark, area_desc, keyword dept_goods_category_id, goods_img_url, goods_video_url, status, create_by, create_time, remark, area_desc, keyword,type,store_service,install_service,delivery_service
FROM goods FROM goods
</sql> </sql>
<sql id="selectGoodsWithArea"> <sql id="selectGoodsWithArea">
SELECT DISTINCT g.goods_id, goods_code, dept_id, goods_name, goods_desc, goods_sort, worker_id, goods_unit, warranty_period, serv_activity, expect_duration, SELECT DISTINCT g.goods_id, goods_code, dept_id, shop_id, shop_name, goods_name, goods_desc, goods_sort, worker_id, goods_unit, warranty_period, serv_activity, expect_duration,
dept_goods_category_id, goods_img_url, goods_video_url, status, g.create_by, g.create_time, g.remark, g.area_desc, g.keyword dept_goods_category_id, goods_img_url, goods_video_url, status, g.create_by, g.create_time, g.remark, g.area_desc, g.keyword,g.type,g.store_service,g.install_service,g.delivery_service
FROM goods g FROM goods g
LEFT JOIN goods_area ga ON g.goods_id = ga.goods_id LEFT JOIN goods_area ga ON g.goods_id = ga.goods_id
</sql> </sql>
@ -44,20 +50,26 @@
<update id="updateGoods" parameterType="com.ghy.goods.domain.Goods"> <update id="updateGoods" parameterType="com.ghy.goods.domain.Goods">
UPDATE goods UPDATE goods
<set> <set>
<if test="goodsName != null and goodsName != ''">goods_name = #{goodsName},</if> <if test="goodsName != null">goods_name = #{goodsName},</if>
<if test="goodsDesc != null and goodsDesc != ''">goods_desc = #{goodsDesc},</if> <if test="goodsDesc != null">goods_desc = #{goodsDesc},</if>
<if test="goodsSort != null and goodsSort != ''">goods_sort = #{goodsSort},</if> <if test="goodsSort != null">goods_sort = #{goodsSort},</if>
<if test="deptGoodsCategoryId != null and deptGoodsCategoryId != ''">dept_goods_category_id = #{deptGoodsCategoryId},</if> <if test="deptGoodsCategoryId != null and deptGoodsCategoryId != ''">dept_goods_category_id = #{deptGoodsCategoryId},</if>
<if test="goodsImgUrl != null and goodsImgUrl != ''">goods_img_url = #{goodsImgUrl},</if> <if test="goodsImgUrl != null">goods_img_url = #{goodsImgUrl},</if>
<if test="goodsVideoUrl != null and goodsVideoUrl != ''">goods_video_url = #{goodsVideoUrl},</if> <if test="goodsVideoUrl != null">goods_video_url = #{goodsVideoUrl},</if>
<if test="status != null">`status` = #{status},</if> <if test="status != null">`status` = #{status},</if>
<if test="remark != null">remark = #{remark},</if> <if test="remark != null">remark = #{remark},</if>
<if test="goodsUnit != null and goodsUnit != ''">goods_unit = #{goodsUnit},</if> <if test="goodsUnit != null">goods_unit = #{goodsUnit},</if>
<if test="warrantyPeriod != null and warrantyPeriod != ''">warranty_period = #{warrantyPeriod},</if> <if test="warrantyPeriod != null">warranty_period = #{warrantyPeriod},</if>
<if test="servActivity != null and servActivity!=''">serv_activity = #{servActivity},</if> <if test="servActivity != null">serv_activity = #{servActivity},</if>
<if test="expectDuration != null and expectDuration!=''">expect_duration = #{expectDuration},</if> <if test="expectDuration != null">expect_duration = #{expectDuration},</if>
<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if> <if test="updateBy != null">update_by = #{updateBy},</if>
<if test="areaDesc != null and areaDesc != ''">area_desc = #{areaDesc},</if> <if test="areaDesc != null">area_desc = #{areaDesc},</if>
<if test="type != null">type = #{type},</if>
store_service = #{storeService},
install_service = #{installService},
delivery_service = #{deliveryService},
shop_id = #{shopId},
<if test="shopName != null">shop_name = #{shopName},</if>
update_time = sysdate() update_time = sysdate()
</set> </set>
WHERE goods_id = #{goodsId} WHERE goods_id = #{goodsId}
@ -73,6 +85,8 @@
insert into goods( insert into goods(
<if test="goodsCode != null and goodsCode != ''">goods_code,</if> <if test="goodsCode != null and goodsCode != ''">goods_code,</if>
<if test="deptId != null and deptId != ''">dept_id,</if> <if test="deptId != null and deptId != ''">dept_id,</if>
<if test="shopId != null">shop_id,</if>
<if test="shopName != null and shopName != ''">shop_name,</if>
<if test="goodsName != null and goodsName != ''">goods_name,</if> <if test="goodsName != null and goodsName != ''">goods_name,</if>
<if test="goodsDesc != null and goodsDesc != ''">goods_desc,</if> <if test="goodsDesc != null and goodsDesc != ''">goods_desc,</if>
<if test="goodsUnit != null and goodsUnit != ''">goods_unit,</if> <if test="goodsUnit != null and goodsUnit != ''">goods_unit,</if>
@ -88,10 +102,16 @@
<if test="remark != null and remark != ''">remark,</if> <if test="remark != null and remark != ''">remark,</if>
<if test="areaDesc != null and areaDesc != ''">area_desc,</if> <if test="areaDesc != null and areaDesc != ''">area_desc,</if>
<if test="createBy != null and createBy != ''">create_by,</if> <if test="createBy != null and createBy != ''">create_by,</if>
<if test="type != null">type,</if>
<if test="storeService != null">store_service,</if>
<if test="installService != null">install_service,</if>
<if test="deliveryService != null">delivery_service,</if>
create_time create_time
)values( )values(
<if test="goodsCode != null and goodsCode != ''">#{goodsCode},</if> <if test="goodsCode != null and goodsCode != ''">#{goodsCode},</if>
<if test="deptId != null and deptId != ''">#{deptId},</if> <if test="deptId != null and deptId != ''">#{deptId},</if>
<if test="shopId != null">#{shopId},</if>
<if test="shopName != null and shopName != ''">#{shopName},</if>
<if test="goodsName != null and goodsName != ''">#{goodsName},</if> <if test="goodsName != null and goodsName != ''">#{goodsName},</if>
<if test="goodsDesc != null and goodsDesc != ''">#{goodsDesc},</if> <if test="goodsDesc != null and goodsDesc != ''">#{goodsDesc},</if>
<if test="goodsUnit != null and goodsUnit != ''">#{goodsUnit},</if> <if test="goodsUnit != null and goodsUnit != ''">#{goodsUnit},</if>
@ -107,6 +127,10 @@
<if test="remark != null and remark != ''">#{remark},</if> <if test="remark != null and remark != ''">#{remark},</if>
<if test="areaDesc != null and areaDesc != ''">#{areaDesc},</if> <if test="areaDesc != null and areaDesc != ''">#{areaDesc},</if>
<if test="createBy != null and createBy != ''">#{createBy},</if> <if test="createBy != null and createBy != ''">#{createBy},</if>
<if test="type != null">#{type},</if>
<if test="storeService != null">#{storeService},</if>
<if test="installService != null">#{installService},</if>
<if test="deliveryService != null">#{deliveryService},</if>
sysdate() sysdate()
) )
</insert> </insert>
@ -143,15 +167,38 @@
AND (goods_name like concat('%', #{goodsName}, '%') AND (goods_name like concat('%', #{goodsName}, '%')
OR g.goods_desc LIKE concat('%', #{goodsName}, '%')) OR g.goods_desc LIKE concat('%', #{goodsName}, '%'))
</if> </if>
<if test="keyword != null and keyword != ''">
AND (
g.goods_name like concat('%', #{keyword}, '%')
OR g.goods_desc LIKE concat('%', #{keyword}, '%')
OR sa.area_name LIKE concat('%', #{keyword}, '%')
OR sa.merger_name LIKE concat('%', #{keyword}, '%')
)
</if>
<if test="status != null"> <if test="status != null">
AND status = #{status} AND status = #{status}
</if> </if>
<if test="workerId != null"> <if test="workerId != null">
AND worker_id = #{workerId} AND worker_id = #{workerId}
</if> </if>
<if test="type != null">
AND g.type = #{type}
</if>
<if test="goodsId != null"> <if test="goodsId != null">
AND g.goods_id = #{goodsId} AND g.goods_id = #{goodsId}
</if> </if>
<if test="shopId != null">
AND g.shop_id = #{shopId}
</if>
<if test="storeService != null">
AND g.store_service = #{storeService}
</if>
<if test="goodsIds != null and goodsIds.size > 0">
AND g.goods_id in
<foreach collection="goodsIds" item="goodsIdItem" open="(" separator="," close=")">
#{goodsIdItem}
</foreach>
</if>
</where> </where>
/* 默认生成时间排序 */ /* 默认生成时间排序 */
order by create_time order by create_time

View File

@ -20,7 +20,7 @@
<result property="updateBy" column="update_by"/> <result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/> <result property="updateTime" column="update_time"/>
<result property="remark" column="remark"/> <result property="remark" column="remark"/>
<result property="imageUrl" column="image_url"/>
</resultMap> </resultMap>
<sql id="selectGoodsStandard"> <sql id="selectGoodsStandard">
@ -40,7 +40,8 @@
status, status,
update_by, update_by,
update_time, update_time,
remark remark,
image_url
FROM goods_standard FROM goods_standard
</sql> </sql>
@ -92,6 +93,7 @@
<if test="goodsNum != null and goodsNum != ''">goods_num,</if> <if test="goodsNum != null and goodsNum != ''">goods_num,</if>
<if test="status != null and status != ''">status,</if> <if test="status != null and status != ''">status,</if>
<if test="remark != null and remark != ''">remark,</if> <if test="remark != null and remark != ''">remark,</if>
<if test="imageUrl != null and imageUrl != ''">image_url,</if>
<if test="createBy != null and createBy != ''">create_by,</if> <if test="createBy != null and createBy != ''">create_by,</if>
create_time create_time
)values( )values(
@ -106,6 +108,7 @@
<if test="goodsNum != null and goodsNum != ''">#{goodsNum},</if> <if test="goodsNum != null and goodsNum != ''">#{goodsNum},</if>
<if test="status != null and status != ''">#{status},</if> <if test="status != null and status != ''">#{status},</if>
<if test="remark != null and remark != ''">#{remark},</if> <if test="remark != null and remark != ''">#{remark},</if>
<if test="imageUrl != null and imageUrl != ''">#{imageUrl},</if>
<if test="createBy != null and createBy != ''">#{createBy},</if> <if test="createBy != null and createBy != ''">#{createBy},</if>
sysdate() sysdate()
) )
@ -114,12 +117,12 @@
<insert id="batchInsert" parameterType="list"> <insert id="batchInsert" parameterType="list">
INSERT INTO goods_standard ( INSERT INTO goods_standard (
goods_standard_name, goods_id, dept_goods_category_id, goods_price, ext_money, discount_price, group_price, goods_unit, goods_num, goods_standard_name, goods_id, dept_goods_category_id, goods_price, ext_money, discount_price, group_price, goods_unit, goods_num,
sale_num, status, remark, create_by, create_time ) sale_num, status, remark, image_url, create_by, create_time )
VALUES VALUES
<foreach collection="goodsStandards" separator="," item="goodsStandard"> <foreach collection="goodsStandards" separator="," item="goodsStandard">
( (
#{goodsStandard.goodsStandardName}, #{goodsStandard.goodsId}, #{goodsStandard.deptGoodsCategoryId}, #{goodsStandard.goodsPrice}, #{goodsStandard.extMoney}, #{goodsStandard.discountPrice}, #{goodsStandard.goodsStandardName}, #{goodsStandard.goodsId}, #{goodsStandard.deptGoodsCategoryId}, #{goodsStandard.goodsPrice}, #{goodsStandard.extMoney}, #{goodsStandard.discountPrice},
#{goodsStandard.groupPrice}, #{goodsStandard.goodsUnit}, #{goodsStandard.goodsNum}, 0, #{goodsStandard.status}, #{goodsStandard.remark}, #{goodsStandard.createBy}, sysdate() #{goodsStandard.groupPrice}, #{goodsStandard.goodsUnit}, #{goodsStandard.goodsNum}, 0, #{goodsStandard.status}, #{goodsStandard.remark}, #{goodsStandard.imageUrl}, #{goodsStandard.createBy}, sysdate()
) )
</foreach> </foreach>
</insert> </insert>
@ -158,8 +161,29 @@
<if test="goodsNum != null">goods_num = #{goodsNum},</if> <if test="goodsNum != null">goods_num = #{goodsNum},</if>
<if test="status != null">`status` = #{status},</if> <if test="status != null">`status` = #{status},</if>
<if test="remark != null">remark = #{remark},</if> <if test="remark != null">remark = #{remark},</if>
<if test="imageUrl != null">image_url = #{imageUrl},</if>
update_time = sysdate() update_time = sysdate()
</set> </set>
WHERE goods_standard_id = #{goodsStandardId} WHERE goods_standard_id = #{goodsStandardId}
</update> </update>
<!-- 根据规格名称模糊查询商品规格 -->
<select id="selectByStandardNameLike" resultMap="GoodsStandardResult">
<include refid="selectGoodsStandard"/>
<where>
<if test="standardName != null and standardName != ''">
AND goods_standard_name LIKE CONCAT('%', #{standardName}, '%')
</if>
</where>
</select>
<!-- 根据部门商品分类ID查询商品规格 -->
<select id="selectByDeptGoodsCategoryId" resultMap="GoodsStandardResult">
<include refid="selectGoodsStandard"/>
<where>
<if test="deptGoodsCategoryId != null">
AND dept_goods_category_id = #{deptGoodsCategoryId}
</if>
</where>
</select>
</mapper> </mapper>

View File

@ -40,8 +40,8 @@ public class AfterServiceRecord extends BaseEntity
@Excel(name = "操作原因1为申请退款2为发起售后") @Excel(name = "操作原因1为申请退款2为发起售后")
private Long operType; private Long operType;
/** 师傅反馈结果0为拒绝1为同意2为上门补做/重做 */ /** 师傅反馈结果0为拒绝1为同意2为上门补做/重做3重做/补做完成 */
@Excel(name = "师傅反馈结果0为拒绝1为同意2为上门补做/重做") @Excel(name = "师傅反馈结果0为拒绝1为同意2为上门补做/重做3重做/补做完成")
private Long workerFeedbackResult; private Long workerFeedbackResult;
/** 师傅反馈原因类型1为客户原因2为师傅原因3为其他 */ /** 师傅反馈原因类型1为客户原因2为师傅原因3为其他 */
@ -52,16 +52,37 @@ public class AfterServiceRecord extends BaseEntity
@Excel(name = "师傅反馈原因描述") @Excel(name = "师傅反馈原因描述")
private String workerFeedbackReason; private String workerFeedbackReason;
/** 师傅反馈图片 */
@Excel(name = "师傅反馈图片")
private String workerFeedbackImages;
/** 本单退款金额 */ /** 本单退款金额 */
@Excel(name = "本单退款金额") @Excel(name = "本单退款金额")
private BigDecimal refund; private BigDecimal refund;
public BigDecimal getRefund() {
return refund;
}
public void setRefund(BigDecimal refund) {
this.refund = refund;
}
/** 售后状态0-进行中1-已完成2-已取消3-已超时 */
@Excel(name = "售后状态0-进行中1-已完成2-已取消3-已超时")
private Integer afterServiceStatus;
/** 协商后的退款金额 */ /** 协商后的退款金额 */
@Excel(name = "协商后的退款金额") @Excel(name = "协商后的退款金额")
private BigDecimal agreedRefund; private BigDecimal agreedRefund;
/** 平台退款金额 */
@Excel(name = "平台退款金额")
private BigDecimal platformRefund;
/** 客户最终确认0为不同意1为同意 */ /** 客户最终确认0为不同意1为同意 */
@Excel(name = "客户最终确认0为不同意1为同意") @Excel(name = "客户最终确认0为不同意1为同意2为取消")
private Long customerFinalCheck; private Long customerFinalCheck;
@Excel(name = "最终原路返还的金额") @Excel(name = "最终原路返还的金额")
@ -69,6 +90,136 @@ public class AfterServiceRecord extends BaseEntity
private Date refundApplyTime; private Date refundApplyTime;
/** 客户是否同意上门重做方案0-未处理1-同意2-不同意 */
@Excel(name = "客户是否同意上门重做方案0-未处理1-同意2-不同意")
private Integer customerAgreeRedo;
/** 客户操作时间 */
@Excel(name = "客户操作时间")
private Date customerOperationTime;
/** 重做/补做完成时间 */
@Excel(name = "重做/补做完成时间")
private Date redoCompleteTime;
/** 重做/补做完成备注 */
@Excel(name = "重做/补做完成备注")
private String redoCompleteRemark;
/** 重做/补做完成图片 */
@Excel(name = "重做/补做完成图片")
private String redoCompleteImages;
/** 是否自动处理0-否1-是(用于防止重复处理) */
@Excel(name = "是否自动处理0-否1-是")
private Integer isAutoProcessed;
/** 售后大类1-商品售后2-服务售后 */
@Excel(name = "售后大类1-服务售后2-商品售后")
private Integer afterServiceCategory;
/** 售后类型1-未收到货退单退款2-未收到货退款3-已收到货退款退货 */
@Excel(name = "售后类型1-未收到货退单退款2-未收到货退款3-已收到货退款退货")
private Integer afterServiceType;
/** 退货状态0-待处理1-同意退货2-拒绝退货3-客户已发货4-商家已收货5-退款完成 */
@Excel(name = "退货状态0-待处理1-同意退货2-拒绝退货3-客户已发货4-商家已收货5-退款完成")
private Integer returnStatus;
/** 退货地址 */
@Excel(name = "退货地址")
private String returnAddress;
/** 退货联系人 */
@Excel(name = "退货联系人")
private String returnContact;
/** 退货联系电话 */
@Excel(name = "退货联系电话")
private String returnPhone;
/** 退货类型1=发快递/物流2=送货上门3=自提 */
@Excel(name = "退货类型1=发快递/物流2=送货上门3=自提")
private Integer returnType;
/** 退货备注 */
@Excel(name = "退货备注")
private String returnRemark;
/** 退货图片 */
@Excel(name = "退货图片")
private String returnImages;
/** 退货物流单号 */
@Excel(name = "退货物流单号")
private String returnTrackingNumber;
/** 退货发货时间 */
@Excel(name = "退货发货时间")
private Date returnShipTime;
/** 商家收货时间 */
@Excel(name = "商家收货时间")
private Date merchantReceiveTime;
/** 师傅重发/补发方案1-重发/补发您无需退货2-重发/补发前您需先退货3-请您退回商品给您换货4-请退回商品,为您售后换货 */
@Excel(name = "师傅重发/补发方案")
private Integer workerResendPlan;
/** 师傅选择重发/补发方案时间 */
@Excel(name = "师傅选择重发/补发方案时间")
private Date workerResendPlanTime;
/** 师傅重发/补发时间 */
@Excel(name = "师傅重发/补发时间", cellType = Excel.ColumnType.STRING)
private Date workerResendTime;
/** 师傅重发/补发方式1-快递/物流2-送货上门3-自提 */
@Excel(name = "师傅重发/补发方式1-快递/物流2-送货上门3-自提")
private Integer workerResendType;
/** 师傅重发/补发物流单号(快递/物流时必填) */
@Excel(name = "师傅重发/补发物流单号")
private String workerResendTrackingNumber;
/** 师傅收货状态1-未发货2-已发货在途3-已收货4-售后保障期 */
@Excel(name = "师傅收货状态1-未发货2-已发货在途3-已收货4-售后保障期")
private Integer workerReceiveStatus;
/** 师傅同意处理方式1-即时退单退款2-货物拦截后退单退款3-快递返回货物后退单退款4-退回货物后退单退款 */
@Excel(name = "师傅同意处理方式1-即时退单退款2-货物拦截后退单退款3-快递返回货物后退单退款4-退回货物后退单退款")
private Integer workerAgreeType;
/** 师傅收货确认0-未收货1-已收货 */
@Excel(name = "师傅收货确认0-未收货1-已收货")
private Integer workerReceiveConfirm;
/** 师傅重发/补发备注 */
@Excel(name = "师傅重发/补发备注")
private String workerResendRemark;
/** 师傅重发/补发图片 */
@Excel(name = "师傅重发/补发图片")
private String workerResendImages;
/** 客户不同意图片 */
@Excel(name = "客户不同意图片")
private String customerDisagreeImages;
/** 客户不同意理由 */
@Excel(name = "客户不同意理由")
private String customerDisagreeReason;
/** 售后纠纷平台处理原因 */
@Excel(name = "售后纠纷平台处理原因")
private String platformHandleReason;
/** 子单号(用于显示,非数据库字段) */
private String orderDetailCode;
/** 子单ID列表用于IN查询非数据库字段 */
private String orderDetailIds;
private boolean excludeAfterServiceFinished; private boolean excludeAfterServiceFinished;
private List<AfterServiceImgs> imgsList; private List<AfterServiceImgs> imgsList;

View File

@ -0,0 +1,90 @@
package com.ghy.order.domain;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.util.Date;
import java.util.List;
/**
* 物流信息实体类
*
* @author clunt
*/
@Data
public class LogisticsInfo {
/**
* 快递单号
*/
private String trackingNumber;
/**
* 快递公司编码
*/
private String expressCode;
/**
* 快递公司名称
*/
private String expressName;
/**
* 物流状态0=在途1=揽收2=疑难3=签收4=退签5=派件6=退回
*/
private Integer status;
/**
* 物流状态描述
*/
private String statusDesc;
/**
* 物流轨迹信息
*/
private List<LogisticsTrace> traces;
/**
* 查询时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date queryTime;
/**
* 是否查询成功
*/
private Boolean success;
/**
* 错误信息
*/
private String errorMsg;
/**
* 物流轨迹详情
*/
@Data
public static class LogisticsTrace {
/**
* 时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date time;
/**
* 地点
*/
private String location;
/**
* 描述
*/
private String description;
/**
* 状态
*/
private String status;
}
}

View File

@ -42,4 +42,7 @@ public class OrderAttachmentRecord {
*/ */
private String paymentId; private String paymentId;
private Long financialChangeRecordId;
} }

View File

@ -1,5 +1,6 @@
package com.ghy.order.domain; package com.ghy.order.domain;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ghy.common.annotation.Excel; import com.ghy.common.annotation.Excel;
import com.ghy.common.core.domain.BaseEntity; import com.ghy.common.core.domain.BaseEntity;
import com.ghy.common.enums.OrderStatus; import com.ghy.common.enums.OrderStatus;
@ -73,6 +74,20 @@ public class OrderDetail extends BaseEntity {
private String orderImgs; private String orderImgs;
@Excel(name = "交货图片", cellType = Excel.ColumnType.STRING)
private String handoverImages;
@Excel(name = "交货备注", cellType = Excel.ColumnType.STRING)
private String handoverRemark;
@Excel(name = "进入确认中时间", cellType = Excel.ColumnType.STRING)
private Date confirmStartTime;
/**
* 确认中倒计时剩余时间毫秒- 售后暂停时记录
*/
private Long confirmTimeoutRemainingTime;
// 商品归属师傅 // 商品归属师傅
private Worker goodsWorker; private Worker goodsWorker;
// 接单师傅 // 接单师傅
@ -177,6 +192,11 @@ public class OrderDetail extends BaseEntity {
* */ * */
private Integer afterTimeout; private Integer afterTimeout;
/**
* 查询时是否包含售后超时条件
*/
private Boolean includeAfterTimeout;
/** /**
* 超时扣款次数 * 超时扣款次数
*/ */
@ -216,4 +236,79 @@ public class OrderDetail extends BaseEntity {
private String isCall; private String isCall;
/**
* 延期次数最大2次
*/
private Integer delayCount;
/**
* 退单原因
*/
private String returnReason;
/**
* 退单原因详情
*/
private String returnReasonDetail;
/**
* 退单图片
*/
private String returnImages;
/**
* 师傅备注
*/
private String workerRemark;
/**
* 售后状态0-无售后1-售后纠纷
*/
@Excel(name = "售后状态0-无售后1-售后纠纷2-售后已完成3-售后已取消")
private Integer afterServiceStatus;
/**
* 分账倒计时结束时间
*/
@Excel(name = "分账倒计时结束时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date shareAccountCountdownEndTime;
/**
* 分账倒计时时长小时
*/
@Excel(name = "分账倒计时时长(小时)")
private Integer shareAccountCountdownDuration;
/**
* 发货类型 - 订单的发货方式
*/
@Excel(name = "发货类型")
private Integer deliveryType;
/**
* 发货备注 - 发货相关备注信息
*/
@Excel(name = "发货备注")
private String deliveryRemark;
/**
* 发货图片 - 发货凭证图片
*/
@Excel(name = "发货图片")
private String deliveryImages;
/**
* 快递单号 - 物流跟踪单号
*/
@Excel(name = "快递单号")
private String trackingNumber;
/**
* 订单图片 - 客户下单时上传的图片
*/
@Excel(name = "订单图片")
private String orderImages;
//是否立即发货
private Integer isQuicklyDelivery;
} }

View File

@ -174,6 +174,8 @@ public class OrderMaster extends BaseEntity {
private BigDecimal serverMoney; private BigDecimal serverMoney;
private BigDecimal serverGoodsMoney;
private Boolean searchAfterList; private Boolean searchAfterList;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@ -235,4 +237,157 @@ public class OrderMaster extends BaseEntity {
private String countryName; private String countryName;
private String streetName; private String streetName;
private Integer withdrawn;
private Long serverGoodsId;
private Long goodsOrderMasterId;
/**
* 服务店铺ID
*/
private Long serviceShopId;
private String trackingNumber;
/**
* 是否已派发服务订单0=未派发1=已派发
*/
private Integer hasServiceOrder;
/**
* 下单图片
*/
@Excel(name = "下单图片", cellType = Excel.ColumnType.STRING)
private String orderImages;
/**
* 是否发货到服务店0=1=
*/
@Excel(name = "是否发货到服务店", cellType = Excel.ColumnType.NUMERIC)
private Integer isDeliveryToStore;
/**
* 发货类型1=发快递/物流2=送货上门3=自提
*/
@Excel(name = "发货类型", cellType = Excel.ColumnType.NUMERIC, readConverterExp = "1=发快递/物流,2=送货上门,3=自提")
private Integer deliveryType;
/**
* 发货备注
*/
@Excel(name = "发货备注", cellType = Excel.ColumnType.STRING)
private String deliveryRemark;
/**
* 发货图片
*/
@Excel(name = "发货图片", cellType = Excel.ColumnType.STRING)
private String deliveryImages;
/**
* 是否已开票0=1=
*/
@Excel(name = "是否已开票", cellType = Excel.ColumnType.NUMERIC, readConverterExp = "0=是,1=否")
private Integer isInvoiced;
/**
* 是否需要开票0=不需要1=需要
*/
@Excel(name = "是否需要开票", cellType = Excel.ColumnType.NUMERIC, readConverterExp = "0=不需要,1=需要")
private Integer isNeedBill;
/**
* 原师傅id转单前的师傅
*/
@Excel(name = "原师傅id", cellType = Excel.ColumnType.NUMERIC)
private Long originalWorkerId;
/**
* 退单原因
*/
private String returnReason;
/**
* 退单原因详情
*/
private String returnReasonDetail;
/**
* 退单图片
*/
private String returnImages;
/**
* 师傅备注
*/
private String workerRemark;
/**
* 售后状态0-无售后1-售后纠纷
*/
@Excel(name = "售后状态0-无售后1-售后纠纷,2-售后已完成,3-售后已取消")
private Integer afterServiceStatus;
/**
* 分账倒计时结束时间workFinishTime + 设置的倒计时字段
*/
@Excel(name = "分账倒计时结束时间", cellType = Excel.ColumnType.STRING)
private Date shareAccountCountdownEndTime;
/**
* 分账倒计时时长单位小时
*/
@Excel(name = "分账倒计时时长", cellType = Excel.ColumnType.NUMERIC)
private Integer shareAccountCountdownDuration;
/**
* 是否包含原始工人ID查询用于orderType=0的情况
*/
private Boolean includeOriginalWorker;
/**
* 是否显示在监控单0=不显示1=显示在监控单
*/
@Excel(name = "是否显示在监控单", cellType = Excel.ColumnType.NUMERIC, readConverterExp = "0=不显示,1=显示在监控单")
private Integer showInMonitor;
/**
* 是否已撤销服务主单0=未撤销1=已撤销
*/
@Excel(name = "是否已撤销服务主单", cellType = Excel.ColumnType.NUMERIC, readConverterExp = "0=未撤销,1=已撤销")
private Integer serviceCancelled;
/**
* 更新时间别名字段用于SQL查询中的update_time别名映射
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTimeAlias;
private String from;
/**
* 是否排除商品订单标识用于Service层过滤逻辑
*/
private Boolean excludeGoodsOrder;
/**
* 退款时是否已支付0=未支付1=已支付
*/
@Excel(name = "退款时是否已支付", cellType = Excel.ColumnType.NUMERIC, readConverterExp = "0=未支付,1=已支付")
private Integer refundPayStatus;
private Integer pageSize;
private Integer pageNum;
/**
* 仅用于前端/接口参数传递
* true = 查询结果要展开到当前城市下的所有街道
* false/null = 保持原样
*/
private Boolean showAllStreet;
} }

View File

@ -65,4 +65,44 @@ public interface AfterServiceRecordMapper {
* @return 未完成的售后记录 * @return 未完成的售后记录
*/ */
AfterServiceRecord unfinished(Long orderDetailId); AfterServiceRecord unfinished(Long orderDetailId);
/**
* 查询师傅反馈超时的售后记录
* 倒计时1客户发起后师傅24小时不操作任何反馈
*
* @return 超时的售后记录列表
*/
List<AfterServiceRecord> selectWorkerFeedbackTimeoutRecords();
/**
* 查询客户确认超时的售后记录
* 倒计时2师傅重做完成后客户36小时不操作
* 倒计时3师傅拒绝后客户36小时不操作
*
* @return 超时的售后记录列表
*/
List<AfterServiceRecord> selectCustomerConfirmTimeoutRecords();
/**
* 查询师傅重发补发超时的售后记录
* 倒计时4师傅重发补发后客户6天快递/物流或24小时非快递不操作
*
* @return 超时的售后记录列表
*/
List<AfterServiceRecord> selectWorkerResendTimeoutRecords();
/**
* 查询退货发货超时的售后记录
* 查询退货发货时间超过6天且师傅未确认收货的售后记录
*
* @return 超时的售后记录列表
*/
List<AfterServiceRecord> selectReturnShipTimeoutRecords();
/**
* 批量更新售后状态当客户最终确认为1时将售后状态设置为1
*
* @return 更新的记录数
*/
int updateAfterServiceStatusByCustomerFinalCheck();
} }

View File

@ -2,6 +2,7 @@ package com.ghy.order.mapper;
import java.util.List; import java.util.List;
import com.ghy.order.domain.OrderAttachmentRecord; import com.ghy.order.domain.OrderAttachmentRecord;
import org.apache.ibatis.annotations.Param;
/** /**
* 附件费Mapper接口 * 附件费Mapper接口
@ -58,4 +59,13 @@ public interface OrderAttachmentRecordMapper
* @return 结果 * @return 结果
*/ */
public int deleteOrderAttachmentRecordByIds(String[] ids); public int deleteOrderAttachmentRecordByIds(String[] ids);
/**
* 删除附件费 根据子单id
*
* @param id 子单id
* @return 结果
*/
public int deleteOrderAttachmentRecordByOrderDetailId(Long orderDetailId);
} }

View File

@ -35,6 +35,14 @@ public interface OrderDetailMapper {
Long countOrderDetailList(OrderDetail orderDetail); Long countOrderDetailList(OrderDetail orderDetail);
/**
* 查询符合条件的子单总数用于分页
*
* @param orderDetail 查询条件
* @return 总数
*/
Long selectOrderDetailCount(OrderDetail orderDetail);
/** /**
* @param orderDetailId 细单表id * @param orderDetailId 细单表id
* @return 细单表 * @return 细单表
@ -47,6 +55,14 @@ public interface OrderDetailMapper {
*/ */
List<OrderDetail> selectByOrderMasterId(@Param("orderMasterId") Long orderMasterId); List<OrderDetail> selectByOrderMasterId(@Param("orderMasterId") Long orderMasterId);
/**
* 根据主订单ID集合批量查询详细订单
*
* @param orderMasterIds 主订单ID集合
* @return 详细订单集合
*/
List<OrderDetail> selectByOrderMasterIds(@Param("orderMasterIds") List<Long> orderMasterIds);
/** /**
* 批量删除细单表信息 * 批量删除细单表信息
* *

View File

@ -35,6 +35,14 @@ public interface OrderGoodsMapper {
List<OrderGoods> selectByOrderId(@Param("orderId") Long orderId); List<OrderGoods> selectByOrderId(@Param("orderId") Long orderId);
/**
* 根据主订单ID集合批量查询订单商品
*
* @param orderMasterIds 主订单ID集合
* @return 订单商品集合
*/
List<OrderGoods> selectByOrderMasterIds(@Param("orderMasterIds") List<Long> orderMasterIds);
List<OrderGoods> selectByOrderDetailId(@Param("orderDetailId") Long orderDetailId); List<OrderGoods> selectByOrderDetailId(@Param("orderDetailId") Long orderDetailId);
int deleteByOrderDetailId(@Param("orderDetailId") Long orderDetailId); int deleteByOrderDetailId(@Param("orderDetailId") Long orderDetailId);

View File

@ -39,6 +39,14 @@ public interface OrderMasterMapper {
*/ */
Long countOrderMasterList(OrderMaster orderMaster); Long countOrderMasterList(OrderMaster orderMaster);
/**
* 查询符合条件的主单总数
*
* @param orderMaster 主订单入参
* @return 满足条件的主单总数
*/
Long selectOrderMasterCount(OrderMaster orderMaster);
/** /**
* @param orderMasterId 主订单id * @param orderMasterId 主订单id
* @return 主订单 * @return 主订单
@ -119,4 +127,12 @@ public interface OrderMasterMapper {
int updateCreateTime(Long id); int updateCreateTime(Long id);
int updateOrderMasterAddressById(OrderMaster orderMaster); int updateOrderMasterAddressById(OrderMaster orderMaster);
/**
* 根据商品主单ID查询服务主单
*
* @param goodsOrderMasterId 商品主单ID
* @return 服务主单
*/
OrderMaster selectByGoodsOrderMasterId(Long goodsOrderMasterId);
} }

View File

@ -0,0 +1,282 @@
package com.ghy.order.quartz;
import com.ghy.common.core.domain.AjaxResult;
import com.ghy.common.utils.DateUtils;
import com.ghy.order.domain.AfterServiceRecord;
import com.ghy.order.service.IAfterServiceRecordService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.List;
/**
* @Date: 2024-12-19
* @Author: 系统
* @Version: v1.0
* @Description: 售后倒计时定时任务
*/
@Slf4j
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class AfterServiceTimeoutTask {
private final IAfterServiceRecordService afterServiceRecordService;
/**
* 售后倒计时定时任务 - 每5分钟执行一次
* 处理3种倒计时场景
* 1. 师傅24小时不操作自动同意完单
* 2. 师傅重做完成后客户36小时不操作自动同意完单
* 3. 师傅拒绝后客户36小时不操作自动取消售后
*/
@Scheduled(cron = "0 0/5 * * * ?")
public void afterServiceTimeoutProcess() {
log.info("{}开始售后倒计时定时任务", DateUtils.timeFormat(new Date()));
try {
// 处理师傅24小时不操作的情况
processWorkerFeedbackTimeout();
// 处理客户36小时不操作的情况
processCustomerConfirmTimeout();
log.info("{}售后倒计时定时任务执行完成", DateUtils.timeFormat(new Date()));
} catch (Exception e) {
log.error("售后倒计时定时任务执行异常", e);
}
}
/**
* 处理师傅24小时不操作的情况
* 倒计时1客户发起后师傅24小时不操作任何反馈直接按同意完单
*/
private void processWorkerFeedbackTimeout() {
log.info("开始处理师傅反馈超时情况");
// 查询需要处理的售后记录
List<AfterServiceRecord> timeoutRecords = afterServiceRecordService.selectWorkerFeedbackTimeoutRecords();
for (AfterServiceRecord record : timeoutRecords) {
try {
log.info("处理师傅反馈超时售后记录:{}", record.getId());
// 师傅24小时不操作直接设置为师傅同意并客户同意完成售后
// record.setWorkerFeedbackResult(1L);
record.setCustomerFinalCheck(1L);
record.setRefundApplyTime(new Date());
record.setIsAutoProcessed(1); // 自动处理
// 根据售后类型选择更新方法
if (record.getAfterServiceCategory() != null && record.getAfterServiceCategory().equals(1)) {
// 商品售后
afterServiceRecordService.updateGoodsAfterServiceRecord(record);
} else {
// 服务售后或其他类型
afterServiceRecordService.updateAfterServiceRecord(record);
}
// 师傅24小时不操作需要执行退款逻辑
try {
afterServiceRecordService.executeRefundLogic(record);
log.info("师傅24小时不操作自动执行退款逻辑完成售后记录ID{}", record.getId());
} catch (Exception e) {
log.error("师傅24小时不操作自动执行退款逻辑异常售后记录ID{}", record.getId(), e);
}
log.info("师傅反馈超时自动处理完成售后记录ID{}", record.getId());
} catch (Exception e) {
log.error("处理师傅反馈超时异常售后记录ID{}", record.getId(), e);
}
}
log.info("师傅反馈超时处理完成,共处理{}条记录", timeoutRecords.size());
}
/**
* 处理客户36小时不操作的情况
* 倒计时2师傅重做完成后客户36小时不操作自动同意完单
* 倒计时3师傅拒绝或同意后客户36小时不操作自动取消售后
* 倒计时4师傅选择重发/补发方案后客户36小时不操作自动取消售后
*/
private void processCustomerConfirmTimeout() {
log.info("开始处理客户确认超时情况");
// 查询需要处理的售后记录
List<AfterServiceRecord> timeoutRecords = afterServiceRecordService.selectCustomerConfirmTimeoutRecords();
for (AfterServiceRecord record : timeoutRecords) {
try {
log.info("处理客户确认超时售后记录:{}", record.getId());
// 根据师傅反馈结果决定处理方式
if (record.getWorkerFeedbackResult() != null && record.getWorkerFeedbackResult().equals(3L)) {
// 倒计时2师傅重做完成后客户36小时不操作自动同意完单
record.setCustomerFinalCheck(1L);
// 师傅重做完成客户同意订单完成不需要退款不设置refundApplyTime
record.setIsAutoProcessed(1); // 自动处理
log.info("师傅重做完成后客户超时自动同意订单完成售后记录ID{}", record.getId());
} else if (record.getWorkerFeedbackResult() != null &&
(record.getWorkerFeedbackResult().equals(0L) || record.getWorkerFeedbackResult().equals(1L))) {
// 倒计时3师傅拒绝或同意后客户36小时不操作自动取消售后
record.setCustomerFinalCheck(2L);
record.setAfterServiceStatus(2); // 设置为已取消状态
record.setIsAutoProcessed(1); // 自动处理
record.setWorkerReceiveConfirm(0);
// // 检查是否需要恢复财务金额
// if (record.getOriginalRefund() != null && record.getOriginalRefund().compareTo(java.math.BigDecimal.ZERO) > 0) {
// log.info("售后取消开始恢复财务金额记录ID{},原退款金额:{}",
// record.getId(), record.getOriginalRefund());
// // 调用财务恢复逻辑
// try {
// afterServiceRecordService.restoreFinancialAmount(record);
// log.info("售后取消财务金额恢复成功记录ID{}", record.getId());
// } catch (Exception e) {
// log.error("售后取消财务金额恢复失败记录ID{},错误:{}",
// record.getId(), e.getMessage(), e);
// }
// }
log.info("师傅拒绝/同意后客户超时自动取消售后单取消完成售后记录ID{}", record.getId());
}else if (record.getWorkerFeedbackResult() != null && record.getWorkerFeedbackResult().equals(2L)) {
//师傅重做补做完成时候客户36小时不操作自动取消售后
record.setCustomerFinalCheck(2L);
record.setAfterServiceStatus(2); // 设置为已取消状态
record.setIsAutoProcessed(1); // 自动处理
record.setWorkerReceiveConfirm(0);
log.info("师傅重做补做完成时候客户36小时不操作自动取消售后订单完成售后记录ID{}", record.getId());
}
// 更新记录
if (record.getAfterServiceCategory() != null && record.getAfterServiceCategory().equals(1)) {
// 商品售后
afterServiceRecordService.updateGoodsAfterServiceRecord(record);
} else {
// 服务售后或其他类型
afterServiceRecordService.updateAfterServiceRecord(record);
}
// 恢复确认中倒计时
afterServiceRecordService.resumeConfirmTimeout(record.getOrderDetailId());
log.info("客户确认超时自动处理完成售后记录ID{}", record.getId());
} catch (Exception e) {
log.error("处理客户确认超时异常售后记录ID{}", record.getId(), e);
}
}
log.info("客户确认超时处理完成,共处理{}条记录", timeoutRecords.size());
}
/**
* 师傅重发补发超时处理
* 快递为4天 其他未1天
*
* 超时后自动按客户同意处理
*/
@Scheduled(fixedRate = 5 * 60 * 1000) // 5分钟执行一次
public void handleWorkerResendTimeout() {
log.info("开始处理师傅重发补发超时...");
// 查询需要处理的师傅重发补发超时记录
List<AfterServiceRecord> timeoutRecords = afterServiceRecordService.selectWorkerResendTimeoutRecords();
for (AfterServiceRecord record : timeoutRecords) {
try {
log.info("处理师傅重发补发超时售后记录:{}", record.getId());
// 自动设置为客户同意
record.setCustomerFinalCheck(1L);
record.setIsAutoProcessed(1);
record.setRefundApplyTime(new Date());
record.setAfterServiceStatus(1);
// 更新记录
afterServiceRecordService.updateGoodsAfterServiceRecord(record);
// 恢复确认中倒计时
afterServiceRecordService.resumeConfirmTimeout(record.getOrderDetailId());
log.info("师傅重发补发超时自动同意售后记录ID{},重发方式:{}",
record.getId(), record.getWorkerResendType());
} catch (Exception e) {
log.error("处理师傅重发补发超时异常售后记录ID{}", record.getId(), e);
}
}
log.info("师傅重发补发超时处理完成,共处理{}条记录", timeoutRecords.size());
}
/**
* 定时任务自动处理超时的师傅确认收货
* 查询退货发货时间超过6天且师傅未确认收货的售后记录
* 自动调用师傅确认收货方法进行退款处理
*/
@Scheduled(cron = "0 0 8,12,18 * * ?")
public void autoConfirmReceiveTask() {
try {
log.info("开始执行自动确认收货定时任务");
// 查询退货发货时间超过6天且师傅未确认收货的售后记录
List<AfterServiceRecord> timeoutRecords = afterServiceRecordService.selectReturnShipTimeoutRecords();
if (timeoutRecords == null || timeoutRecords.isEmpty()) {
log.info("没有找到需要自动确认收货的超时记录");
return;
}
log.info("找到{}条需要自动确认收货的超时记录", timeoutRecords.size());
int successCount = 0;
int failCount = 0;
// 循环处理每条超时记录
for (AfterServiceRecord record : timeoutRecords) {
try {
log.info("开始自动确认收货售后记录ID{},退货发货时间:{}",
record.getId(), record.getReturnShipTime());
// 创建参数对象
AfterServiceRecord param = new AfterServiceRecord();
param.setId(record.getId());
// 调用师傅确认收货方法
AjaxResult result = afterServiceRecordService.workerConfirmReceive(param);
// if (result.isSuccess()) {
// successCount++;
// // 标记为自动处理
// record.setIsAutoProcessed(1);
// record.setAutoProcessTime(new Date());
// record.setUpdateTime(new Date());
// afterServiceRecordService.updateAfterServiceRecord(record);
// log.info("自动确认收货成功售后记录ID{}", record.getId());
// } else {
// failCount++;
// log.error("自动确认收货失败售后记录ID{},错误信息:{}",
// record.getId(), result.getMsg());
// }
} catch (Exception e) {
failCount++;
log.error("自动确认收货异常售后记录ID{},错误:{}",
record.getId(), e.getMessage(), e);
}
}
log.info("自动确认收货定时任务执行完成,总记录数:{},成功:{},失败:{}",
timeoutRecords.size(), successCount, failCount);
} catch (Exception e) {
log.error("自动确认收货定时任务执行异常:{}", e.getMessage(), e);
}
}
}

View File

@ -28,4 +28,9 @@ public class AppOrderAssignRequest {
// 分配的商品及数量 // 分配的商品及数量
private List<AppGoodsRequest> goodsList; private List<AppGoodsRequest> goodsList;
//是否立即发货
private Integer isQuicklyDelivery;
private Integer generateServiceOrder;
} }

View File

@ -45,4 +45,14 @@ public class AppOrderRequest {
private Long goodsId; private Long goodsId;
private Long insuranceId; private Long insuranceId;
private Long serviceShopId;
private String orderImages;
/**
* 是否发货到服务店0=1=
*/
private Integer isDeliveryToStore;
} }

View File

@ -0,0 +1,35 @@
package com.ghy.order.request;
import lombok.Data;
import javax.validation.constraints.NotBlank;
/**
* 物流查询请求类
*
* @author clunt
*/
@Data
public class LogisticsQueryRequest {
/**
* 快递单号
*/
@NotBlank(message = "快递单号不能为空")
private String trackingNumber;
/**
* 快递公司编码可选如果不传会自动识别
*/
private String expressCode;
/**
* 快递公司名称可选如果不传会自动识别
*/
private String expressName;
/**
* 手机号阿里云快递查询API需要
*/
private String mobile;
}

View File

@ -24,6 +24,9 @@ public class SysOrderAssignRequest {
// 图片 // 图片
private String imageUrl; private String imageUrl;
// 下单图片
private String orderImages;
// 商品相关信息 // 商品相关信息
private Long goodsDeptCategoryId; private Long goodsDeptCategoryId;
// 服务地址ID // 服务地址ID

View File

@ -0,0 +1,36 @@
package com.ghy.order.request;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
/**
* 转单请求类
*
* @author clunt
*/
@Data
public class TransferOrderRequest {
/**
* 订单ID
*/
@NotNull(message = "订单ID不能为空")
private Long orderId;
/**
* 新师傅ID可以为null表示进入接单大厅
*/
private Long newWorkerId;
/**
* 转单金额新师傅承担可以为null表示无转单金额
*/
private BigDecimal transferAmount;
/**
* 转单原因
*/
private String transferReason;
}

View File

@ -61,4 +61,95 @@ public interface IAfterServiceRecordService {
* @return 结果 * @return 结果
*/ */
int deleteAfterServiceRecordById(String id); int deleteAfterServiceRecordById(String id);
/**
* 查询师傅反馈超时的售后记录
* 倒计时1客户发起后师傅24小时不操作任何反馈
*
* @return 超时的售后记录列表
*/
List<AfterServiceRecord> selectWorkerFeedbackTimeoutRecords();
/**
* 查询客户确认超时的售后记录
* 倒计时2师傅重做完成后客户36小时不操作
* 倒计时3师傅拒绝后客户36小时不操作
*
* @return 超时的售后记录列表
*/
List<AfterServiceRecord> selectCustomerConfirmTimeoutRecords();
/**
* 执行退款逻辑
* 用于定时器自动处理退款
*
* @param afterServiceRecord 售后记录
*/
void executeRefundLogic(AfterServiceRecord afterServiceRecord);
/**
* 恢复确认中倒计时
* @param orderDetailId 子单ID
*/
void resumeConfirmTimeout(Long orderDetailId);
/**
* 修改商品售后记录
*
* @param afterServiceRecord 售后记录
* @return 结果
*/
AjaxResult updateGoodsAfterServiceRecord(AfterServiceRecord afterServiceRecord) throws Exception;
/**
* 师傅重发/补发操作
* 师傅端点击重发补发按钮保存重发/补发方案
*
* @param afterServiceRecord 售后记录
* @return 操作结果
*/
AjaxResult workerResendPlan(AfterServiceRecord afterServiceRecord);
/**
* 退货操作
* 客户或师傅端进行退货操作保存退货信息
*
* @param afterServiceRecord 售后记录
* @return 操作结果
*/
AjaxResult returnGoods(AfterServiceRecord afterServiceRecord);
AfterServiceRecord unfinished(Long id);
/**
* 查询师傅重发补发超时的售后记录
* 快递/物流6天倒计时
* 非快递24小时倒计时
* @return 超时的售后记录列表
*/
List<AfterServiceRecord> selectWorkerResendTimeoutRecords();
/**
* 查询退货发货时间超过6天且师傅未确认收货的售后记录
* 用于定时任务自动处理师傅确认收货超时的情况
*
* @return 超时的售后记录列表
*/
List<AfterServiceRecord> selectReturnShipTimeoutRecords();
/**
* 师傅确认收货
* 师傅确认收到货物后根据同意处理方式决定是否执行退款
*
* @param afterServiceRecord 售后记录
* @return 操作结果
*/
AjaxResult workerConfirmReceive(AfterServiceRecord afterServiceRecord);
/**
* 恢复财务金额
* 当售后取消时将之前扣减的金额恢复到原财务账单
* @param afterServiceRecord 售后记录
*/
void restoreFinancialAmount(AfterServiceRecord afterServiceRecord);
} }

View File

@ -58,4 +58,6 @@ public interface IOrderAttachmentRecordService
* @return 结果 * @return 结果
*/ */
public int deleteOrderAttachmentRecordById(Long id); public int deleteOrderAttachmentRecordById(Long id);
int deleteOrderAttachmentRecordByOrderDetailId(Long orderDetailId);
} }

View File

@ -0,0 +1,55 @@
package com.ghy.order.service;
import com.ghy.order.domain.LogisticsInfo;
import com.ghy.order.request.LogisticsQueryRequest;
/**
* 物流服务接口
*
* @author clunt
*/
public interface LogisticsService {
/**
* 根据快递单号查询物流信息
*
* @param request 物流查询请求
* @return 物流信息
*/
LogisticsInfo queryLogistics(LogisticsQueryRequest request);
/**
* 根据快递单号查询物流信息
*
* @param trackingNumber 快递单号
* @return 物流信息
*/
LogisticsInfo queryLogistics(String trackingNumber);
/**
* 根据快递单号和快递公司编码查询物流信息
*
* @param trackingNumber 快递单号
* @param expressCode 快递公司编码
* @return 物流信息
*/
LogisticsInfo queryLogistics(String trackingNumber, String expressCode);
/**
* 根据快递单号快递公司编码和手机号查询物流信息
*
* @param trackingNumber 快递单号
* @param expressCode 快递公司编码
* @param mobile 手机号
* @return 物流信息
*/
LogisticsInfo queryLogistics(String trackingNumber, String expressCode, String mobile);
/**
* 根据订单ID查询物流信息
*
* @param orderId 订单ID
* @return 物流信息
*/
LogisticsInfo queryLogisticsByOrderId(Long orderId);
}

View File

@ -48,6 +48,13 @@ public interface OrderDetailService {
*/ */
List<OrderDetail> selectByOrderMasterId(Long orderMasterId); List<OrderDetail> selectByOrderMasterId(Long orderMasterId);
/**
* 根据主订单ID集合批量查询详细订单
* @param orderMasterIds 主订单ID集合
* @return 详细订单集合
*/
List<OrderDetail> selectByOrderMasterIds(List<Long> orderMasterIds);
/** /**
* @param ids 详细订单ids * @param ids 详细订单ids
* @return 删除结果 * @return 删除结果
@ -121,6 +128,14 @@ public interface OrderDetailService {
*/ */
Long countOrderDetailList(OrderDetail orderDetail); Long countOrderDetailList(OrderDetail orderDetail);
/**
* 查询符合条件的子单总数用于分页
*
* @param orderDetail 查询条件
* @return 总数
*/
Long selectOrderDetailCount(OrderDetail orderDetail);
/** /**
* 子单改价接口 * 子单改价接口
* *
@ -130,7 +145,7 @@ public interface OrderDetailService {
* @param remark 备注 * @param remark 备注
* @return 成功/失败 * @return 成功/失败
*/ */
int changePrice(Long orderDetailId, BigDecimal changeMoney, Integer type, String remark) throws Exception; int changePrice(Long orderDetailId, BigDecimal changeMoney, Integer type, String remark,String urls ,String fileNames,String reason) throws Exception;
int sureChange(Long financialChangeRecordId); int sureChange(Long financialChangeRecordId);
@ -203,4 +218,36 @@ public interface OrderDetailService {
// 订单详情数据统计返回 根据时间统计当前日期天 // 订单详情数据统计返回 根据时间统计当前日期天
OrderDetailStatisticsDTO orderStatisticsDisposeByNow(); OrderDetailStatisticsDTO orderStatisticsDisposeByNow();
/**
* 延期到货
* 如果是待确认状态则变为服务中其他状态不变
* 将confirm_start_time增加3天
* 延期次数最多2次
*
* @param orderMasterId 主订单ID
* @return 成功条数
*/
int delayOrder(Long orderMasterId);
/**
* 子订单退回
* 如果订单为服务类型(orderType=0)则退回到服务中状态
* 如果订单为商品类型(orderType=1)则退回到待排期状态
*
* @param orderDetailId 子订单ID
* @return 成功条数
*/
int returnOrder(Long orderDetailId);
int updateOrderDetailAddressById(OrderDetail orderDetail);
/**
* 保存子单师傅备注
*
* @param orderDetailId 子订单ID
* @param workerRemark 师傅备注
* @return 成功条数
*/
int saveDetailWorkerRemark(Long orderDetailId, String workerRemark);
} }

View File

@ -19,6 +19,14 @@ public interface OrderGoodsService {
List<OrderGoods> selectByOrderMasterId(Long orderMasterId); List<OrderGoods> selectByOrderMasterId(Long orderMasterId);
/**
* 根据主订单ID集合批量查询订单商品
*
* @param orderMasterIds 主订单ID集合
* @return 订单商品集合
*/
List<OrderGoods> selectByOrderMasterIds(List<Long> orderMasterIds);
List<OrderGoods> selectByOrderDetailId(Long orderDetailId); List<OrderGoods> selectByOrderDetailId(Long orderDetailId);
/** /**

View File

@ -7,6 +7,7 @@ import com.ghy.order.pojo.dto.OrderMasterStatisticsDTO;
import com.ghy.order.request.AppOrderRequest; import com.ghy.order.request.AppOrderRequest;
import com.ghy.order.request.OrderChangePriceReq; import com.ghy.order.request.OrderChangePriceReq;
import com.huifu.adapay.core.exception.BaseAdaPayException; import com.huifu.adapay.core.exception.BaseAdaPayException;
import com.ghy.order.domain.OrderDetail;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
@ -46,6 +47,14 @@ public interface OrderMasterService {
*/ */
Long countOrderMasterList(OrderMaster orderMaster); Long countOrderMasterList(OrderMaster orderMaster);
/**
* 查询符合条件的主单总数
*
* @param orderMaster 主订单入参
* @return 符合条件的主单总数
*/
Long selectOrderMasterCount(OrderMaster orderMaster);
/** /**
* @param orderMasterId 主订单id * @param orderMasterId 主订单id
* @return 主订单 * @return 主订单
@ -192,4 +201,23 @@ public interface OrderMasterService {
int updateOrderMasterAddressById(OrderMaster orderMaster); int updateOrderMasterAddressById(OrderMaster orderMaster);
int returnOrder(Long orderMasterId, String returnReason, String returnReasonDetail, String returnImages);
/**
* 保存主单师傅备注
*
* @param orderMasterId 主订单ID
* @param workerRemark 师傅备注
* @return 成功条数
*/
int saveMasterWorkerRemark(Long orderMasterId, String workerRemark);
/**
* 根据商品主单ID查询服务主单
*
* @param goodsOrderMasterId 商品主单ID
* @return 服务主单
*/
OrderMaster selectByGoodsOrderMasterId(Long goodsOrderMasterId);
} }

View File

@ -0,0 +1,303 @@
package com.ghy.order.service.impl;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.ghy.order.domain.LogisticsInfo;
import com.ghy.order.domain.OrderMaster;
import com.ghy.order.request.LogisticsQueryRequest;
import com.ghy.order.service.LogisticsService;
import com.ghy.order.service.OrderMasterService;
import com.ghy.order.utils.HttpUtils;
import com.ghy.order.utils.LogisticsUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpResponse;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
/**
* 物流服务实现类
*
* @author clunt
*/
@Slf4j
@Service
public class LogisticsServiceImpl implements LogisticsService {
@Autowired
private OrderMasterService orderMasterService;
/**
* 阿里云快递查询 API配置
*/
@Value("${logistics.aliyun.appCode:}")
private String appCode;
@Value("${logistics.aliyun.host:https://kzexpress.market.alicloudapi.com}")
private String host;
@Value("${logistics.aliyun.path:/api-mall/api/express/query}")
private String path;
@Override
public LogisticsInfo queryLogistics(LogisticsQueryRequest request) {
return queryLogistics(request.getTrackingNumber(), request.getExpressCode(), request.getMobile());
}
@Override
public LogisticsInfo queryLogistics(String trackingNumber) {
return queryLogistics(trackingNumber, null, null);
}
@Override
public LogisticsInfo queryLogistics(String trackingNumber, String expressCode) {
return queryLogistics(trackingNumber, expressCode, null);
}
/**
* 根据快递单号查询物流信息阿里云API
*
* @param trackingNumber 快递单号
* @param expressCode 快递公司编码
* @param mobile 手机号
* @return 物流信息
*/
public LogisticsInfo queryLogistics(String trackingNumber, String expressCode, String mobile) {
LogisticsInfo logisticsInfo = new LogisticsInfo();
logisticsInfo.setTrackingNumber(trackingNumber);
logisticsInfo.setQueryTime(new Date());
try {
// 验证快递单号格式
if (!LogisticsUtils.isValidTrackingNumber(trackingNumber)) {
logisticsInfo.setSuccess(false);
logisticsInfo.setErrorMsg("快递单号格式不正确");
return logisticsInfo;
}
// 调用阿里云快递查询 API
JSONObject result = callAliyunAPI(trackingNumber, mobile);
if (result.getBool("success", false)) {
// 查询成功
logisticsInfo.setSuccess(true);
// 解析快递公司信息
JSONObject data = result.getJSONObject("data");
if (data != null) {
logisticsInfo.setExpressCode(data.getStr("cpCode"));
logisticsInfo.setExpressName(data.getStr("logisticsCompanyName"));
logisticsInfo.setStatus(getAliyunStatus(data.getStr("logisticsStatusDesc")));
logisticsInfo.setStatusDesc(data.getStr("logisticsStatusDesc"));
// 解析物流轨迹
List<LogisticsInfo.LogisticsTrace> traces = new ArrayList<>();
JSONArray tracesArray = data.getJSONArray("logisticsTraceDetailList");
if (tracesArray != null) {
for (int i = 0; i < tracesArray.size(); i++) {
JSONObject trace = tracesArray.getJSONObject(i);
LogisticsInfo.LogisticsTrace logisticsTrace = new LogisticsInfo.LogisticsTrace();
// 处理时间戳
Long timeStamp = trace.getLong("time");
if (timeStamp != null) {
logisticsTrace.setTime(new Date(timeStamp));
}
logisticsTrace.setLocation(trace.getStr("areaName"));
logisticsTrace.setDescription(trace.getStr("desc"));
logisticsTrace.setStatus(trace.getStr("logisticsStatus"));
traces.add(logisticsTrace);
}
}
logisticsInfo.setTraces(traces);
}
} else {
// 查询失败
logisticsInfo.setSuccess(false);
logisticsInfo.setErrorMsg(result.getStr("msg", "查询失败"));
}
} catch (Exception e) {
log.error("查询物流信息失败,快递单号:{},错误:{}", trackingNumber, e.getMessage(), e);
logisticsInfo.setSuccess(false);
logisticsInfo.setErrorMsg("查询物流信息失败:" + e.getMessage());
}
return logisticsInfo;
}
@Override
public LogisticsInfo queryLogisticsByOrderId(Long orderId) {
OrderMaster orderMaster = orderMasterService.selectById(orderId);
if (orderMaster == null || StrUtil.isBlank(orderMaster.getTrackingNumber())) {
LogisticsInfo logisticsInfo = new LogisticsInfo();
logisticsInfo.setSuccess(false);
logisticsInfo.setErrorMsg("订单不存在或没有快递单号");
return logisticsInfo;
}
return queryLogistics(orderMaster.getTrackingNumber());
}
/**
* 调用阿里云快递查询 API
*
* @param trackingNumber 快递单号
* @param mobile 手机号
* @return API返回结果
*/
private JSONObject callAliyunAPI(String trackingNumber, String mobile) {
try {
String method = "GET";
// 构建请求头
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", "APPCODE " + appCode);
// 构建查询参数
Map<String, String> querys = new HashMap<>();
querys.put("expressNo", trackingNumber);
if (StrUtil.isNotBlank(mobile)) {
querys.put("mobile", mobile);
}
log.info("阿里云快递查询API请求参数: expressNo={}, mobile={}", trackingNumber, mobile);
// 调用API
HttpResponse response = HttpUtils.doGet(host, path, method, headers, querys);
String result = EntityUtils.toString(response.getEntity());
log.info("阿里云快递查询API响应结果: {}", result);
// 检查响应状态码
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode != 200) {
log.error("阿里云API返回错误状态码: {}", statusCode);
JSONObject errorResult = new JSONObject();
errorResult.set("success", false);
errorResult.set("message", "API返回错误状态码: " + statusCode);
return errorResult;
}
// 检查响应内容是否为空
if (StrUtil.isBlank(result)) {
log.error("阿里云API返回空响应");
JSONObject errorResult = new JSONObject();
errorResult.set("success", false);
errorResult.set("message", "API返回空响应");
return errorResult;
}
// 检查响应内容是否以{开头JSON格式
if (!result.trim().startsWith("{")) {
log.error("阿里云API返回非JSON格式响应: {}", result.substring(0, Math.min(100, result.length())));
JSONObject errorResult = new JSONObject();
errorResult.set("success", false);
errorResult.set("message", "API返回非JSON格式响应");
return errorResult;
}
// 尝试解析JSON
try {
return JSONUtil.parseObj(result);
} catch (Exception jsonException) {
log.error("解析阿里云API响应JSON失败: {}", jsonException.getMessage());
JSONObject errorResult = new JSONObject();
errorResult.set("success", false);
errorResult.set("message", "解析API响应失败: " + jsonException.getMessage());
return errorResult;
}
} catch (Exception e) {
log.error("调用阿里云快递查询API失败: {}", e.getMessage(), e);
JSONObject errorResult = new JSONObject();
errorResult.set("success", false);
errorResult.set("message", "API调用失败: " + e.getMessage());
return errorResult;
}
}
/**
* 将阿里云状态转换为系统状态码
*
* @param aliyunStatus 阿里云状态
* @return 系统状态码
*/
private Integer getAliyunStatus(String aliyunStatus) {
if (StrUtil.isBlank(aliyunStatus)) {
return 0;
}
switch (aliyunStatus) {
case "在途":
case "运输中":
case "TRANSPORT":
return 0;
case "揽收":
case "已揽收":
case "ACCEPT":
return 1;
case "疑难":
case "异常":
case "EXCEPTION":
return 2;
case "签收":
case "已签收":
case "SIGN":
return 3;
case "退签":
case "已退签":
case "REJECT":
return 4;
case "派件":
case "派送中":
case "DELIVERING":
return 5;
case "退回":
case "已退回":
case "RETURN":
return 6;
case "到达驿站":
case "STA_INBOUND":
return 5; // 到达驿站也算派件状态
default:
return 0;
}
}
/**
* 获取状态描述
*
* @param status 状态码
* @return 状态描述
*/
private String getStatusDesc(Integer status) {
switch (status) {
case 0:
return "在途";
case 1:
return "揽收";
case 2:
return "疑难";
case 3:
return "签收";
case 4:
return "退签";
case 5:
return "派件";
case 6:
return "退回";
default:
return "未知";
}
}
}

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