Compare commits
235 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
d957a0c2a3 | |
|
|
dd51d48a45 | |
|
|
5a172360bc | |
|
|
28c5d34acf | |
|
|
e66d6b56e9 | |
|
|
232509dc14 | |
|
|
477f252fd1 | |
|
|
4c801f007a | |
|
|
5d5f67ef07 | |
|
|
027e23e538 | |
|
|
f88d3a1673 | |
|
|
5c17e811a9 | |
|
|
ae843ab767 | |
|
|
d2db2a5547 | |
|
|
f1df93ec25 | |
|
|
34d71c1fd0 | |
|
|
bc9d50bf0c | |
|
|
e1e41d98bc | |
|
|
a36d9374dd | |
|
|
174f6b4a82 | |
|
|
aa7b8c7a5d | |
|
|
77008d11da | |
|
|
f66ee72314 | |
|
|
40ba4652be | |
|
|
1485037c82 | |
|
|
cb78526d0e | |
|
|
bc96277f86 | |
|
|
c733349a9e | |
|
|
e7bb47f023 | |
|
|
a805c562c9 | |
|
|
8bbacc653f | |
|
|
e0060c0432 | |
|
|
72748b7113 | |
|
|
fe13c91ab2 | |
|
|
16891e331d | |
|
|
9ebbc056f3 | |
|
|
43e51e7557 | |
|
|
642038eef5 | |
|
|
5489aaa620 | |
|
|
9bb52b5ffb | |
|
|
bba67251af | |
|
|
4388cbb3f6 | |
|
|
e0226437ad | |
|
|
c57c585b37 | |
|
|
eced56a427 | |
|
|
8148a59e83 | |
|
|
c84c39a28f | |
|
|
47f717fcb2 | |
|
|
5c2049c3ea | |
|
|
82ecd1a144 | |
|
|
9acd98d9b5 | |
|
|
9db20efd88 | |
|
|
61dcb7b25c | |
|
|
c8825f1c16 | |
|
|
c3f89a3a4f | |
|
|
e9771921db | |
|
|
9ebbcfacf7 | |
|
|
4783b4b077 | |
|
|
5cc6d5736d | |
|
|
87bcdda6f8 | |
|
|
624921d776 | |
|
|
75ffa0f6ee | |
|
|
1a8283e441 | |
|
|
cb3e5d98a8 | |
|
|
731d340abf | |
|
|
e9ab52cb7e | |
|
|
7878cc61c1 | |
|
|
c55ac047c0 | |
|
|
a64f1728ac | |
|
|
a75d05bc9a | |
|
|
6532c9d460 | |
|
|
3eb0bd846f | |
|
|
7d2b4f7fdb | |
|
|
7a2f71624b | |
|
|
190d2a2bed | |
|
|
f12121c457 | |
|
|
c7953da901 | |
|
|
464fab9fa1 | |
|
|
ef8fc2c492 | |
|
|
9090b61095 | |
|
|
08e5353b4e | |
|
|
a2a009a1e2 | |
|
|
49394a5172 | |
|
|
57d5161b2c | |
|
|
270ad85a10 | |
|
|
65ca7bf9aa | |
|
|
356d2beaa5 | |
|
|
d2260ade7b | |
|
|
9c519ce980 | |
|
|
620af65906 | |
|
|
353e302df9 | |
|
|
0a37c352fb | |
|
|
1a5b094388 | |
|
|
c8811b0e60 | |
|
|
510e696030 | |
|
|
8c859e1866 | |
|
|
91cb050b47 | |
|
|
1fe53780b9 | |
|
|
671a9f225e | |
|
|
ae2f59d183 | |
|
|
d59b15c569 | |
|
|
fa7ff1971f | |
|
|
08c7cb32a9 | |
|
|
f1d55564c3 | |
|
|
3cf9951bb3 | |
|
|
ce8015b836 | |
|
|
9a3245380b | |
|
|
02528c930f | |
|
|
3dc68ceaf8 | |
|
|
ad6473f446 | |
|
|
61e9859181 | |
|
|
3fbd432b9c | |
|
|
7643b5d6d8 | |
|
|
4ef6b6de2c | |
|
|
7a73c9b099 | |
|
|
40f5c1472e | |
|
|
8870875647 | |
|
|
5fe9d79a94 | |
|
|
ee36d363a7 | |
|
|
f9f36c16d9 | |
|
|
7f959107da | |
|
|
47f5c7b039 | |
|
|
704454a759 | |
|
|
a82b513755 | |
|
|
c726bf91d4 | |
|
|
ea0233425e | |
|
|
708d2f12f5 | |
|
|
037b1b5b5e | |
|
|
b2ddfb1502 | |
|
|
bd78980529 | |
|
|
fa616c8de6 | |
|
|
7afe847942 | |
|
|
d3dcb97911 | |
|
|
10835cd1a7 | |
|
|
e2ec85c7ae | |
|
|
7a2a155938 | |
|
|
dfcf26d976 | |
|
|
cedc78e76e | |
|
|
e38126d341 | |
|
|
547fcfc37b | |
|
|
e8af400536 | |
|
|
3524b20075 | |
|
|
affce5e1b4 | |
|
|
f1328d4db8 | |
|
|
75c06e6457 | |
|
|
448b134f11 | |
|
|
5b8fe3cd58 | |
|
|
f68eb53363 | |
|
|
9a722c8da2 | |
|
|
18b6d5325d | |
|
|
8766726189 | |
|
|
dfc1aaca91 | |
|
|
ca4530ce84 | |
|
|
6c500a0349 | |
|
|
d7f8ffa20d | |
|
|
8e09f2aaf6 | |
|
|
36767a00bf | |
|
|
ecd9a76eeb | |
|
|
4f984ab2e7 | |
|
|
1d91eb9976 | |
|
|
c747158393 | |
|
|
cb16333e61 | |
|
|
964d1cb519 | |
|
|
cc032f44c0 | |
|
|
71bb414f23 | |
|
|
ef23b61265 | |
|
|
6dd60354a6 | |
|
|
582d478186 | |
|
|
a14f69f77c | |
|
|
5a4945955d | |
|
|
99be99391f | |
|
|
60952f6b47 | |
|
|
60c02a28ed | |
|
|
05ac0b5acb | |
|
|
087ea91ecf | |
|
|
54a7b65f8f | |
|
|
687f691ff9 | |
|
|
78f43c0c16 | |
|
|
880866b1b6 | |
|
|
c07f80e18f | |
|
|
1abb02f464 | |
|
|
fc38c49162 | |
|
|
c857e551fb | |
|
|
a22d60f23a | |
|
|
806e5e700e | |
|
|
f8a987a0f6 | |
|
|
be3311daee | |
|
|
0ff2a6b703 | |
|
|
dbeace3191 | |
|
|
96cbd47cb0 | |
|
|
bcffad126c | |
|
|
6dbdb33a2b | |
|
|
fecb22b5ad | |
|
|
e661871090 | |
|
|
38c26e399f | |
|
|
e1696d937b | |
|
|
2099610e59 | |
|
|
2e2494c9bb | |
|
|
ed034cb2c7 | |
|
|
d40822487f | |
|
|
3cd9560732 | |
|
|
479e619d39 | |
|
|
bd9883c0d0 | |
|
|
6aec17ed20 | |
|
|
5aea1c4731 | |
|
|
f385aaabf3 | |
|
|
0a86d01406 | |
|
|
ccb7cc34b5 | |
|
|
37722ef2fa | |
|
|
d1ce4b76a2 | |
|
|
95952ea425 | |
|
|
1a2f919f79 | |
|
|
3c958a2923 | |
|
|
ec4fdda392 | |
|
|
73710b0a4c | |
|
|
e6a3d6fc2d | |
|
|
e7e8583806 | |
|
|
259b99c3f0 | |
|
|
9df61f6a85 | |
|
|
21c4d8e7dc | |
|
|
174b6031e9 | |
|
|
37471713b7 | |
|
|
4c8a00d081 | |
|
|
f9080bb812 | |
|
|
cf10288761 | |
|
|
1be8d59c6f | |
|
|
930a9e3de8 | |
|
|
2e7bcaba70 | |
|
|
d6028c3e1e | |
|
|
ff67462b73 | |
|
|
fab703b7af | |
|
|
2655d8ffdb | |
|
|
ab7e7eaca7 | |
|
|
c92ea38c60 | |
|
|
977994aeee |
|
|
@ -34,3 +34,4 @@ build/
|
||||||
.vscode/
|
.vscode/
|
||||||
/logs/
|
/logs/
|
||||||
/ghy-admin/src/test/
|
/ghy-admin/src/test/
|
||||||
|
ghy-shop/ADDRESS_QUERY_EXAMPLE.md
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,214 @@
|
||||||
|
<!-- @author Yifei.Kuang -->
|
||||||
|
# 房间 / 群 / 群员业务需求说明文档
|
||||||
|
|
||||||
|
## 一、项目背景与目标
|
||||||
|
|
||||||
|
- **背景**
|
||||||
|
在现有「师傅 / 商城 / 分销」体系上,引入「房间(圈子)+ 群 + 分销」的新结构,实现多房间、多群、多身份的精细化运营与监管。
|
||||||
|
|
||||||
|
- **目标**
|
||||||
|
- 建立清晰的层级结构:**平台 → 房间 → 群 → 群员/分销**。
|
||||||
|
- 统一分销身份规则,保证订单与佣金归属准确。
|
||||||
|
- 支持群主、房间主、下级分销、运营商等多角色协同。
|
||||||
|
- 为后续房间收费、群收费、群员收费等模式预留空间。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、核心概念定义
|
||||||
|
|
||||||
|
### 2.1 平台
|
||||||
|
|
||||||
|
- 系统最上层运营主体,负责全局参数(平台扣点、结算规则等)。
|
||||||
|
|
||||||
|
### 2.2 房间(圈子)
|
||||||
|
|
||||||
|
- **定义**:平台下的独立运营空间,一个平台可创建多个房间。
|
||||||
|
- **特点**
|
||||||
|
- 房间名称:限制在 4–6 个字。
|
||||||
|
- 房间标识图片、品牌注解由房间主配置。
|
||||||
|
- 顶部功能栏:默认为「师傅库 / 商城 / 会话厅」,文字可改,总长度有限制但功能不变。
|
||||||
|
- 房间可配置自身的功能权限(是否开放商城、是否允许群选品等)以及扣点、收费策略。
|
||||||
|
- **关系**
|
||||||
|
- 「微社圈」是整体产品名,“切换圈子”即切换不同房间。
|
||||||
|
|
||||||
|
### 2.3 群(分销群)
|
||||||
|
|
||||||
|
- **定义**:隶属于某房间的运营群,每个房间下可以创建多个群。
|
||||||
|
- **建群要求**
|
||||||
|
- 填写城市、区域、主营品类、预期群规模等信息。
|
||||||
|
- 建群申请需由房间主及后台审核(形式类似现有分销申请)。
|
||||||
|
- **职责**
|
||||||
|
- 每个群经营自己的商品与客户。
|
||||||
|
- 群商城中仅群主有选品权限,下级分销不能选品。
|
||||||
|
|
||||||
|
### 2.4 群主
|
||||||
|
|
||||||
|
- 该群的最高级分销者。
|
||||||
|
- 新建群后自动成为本群顶级分销(无上级分销)。
|
||||||
|
- 负责本群的选品、群成员管理、群内规则等。
|
||||||
|
|
||||||
|
### 2.5 群员 / 分销人员
|
||||||
|
|
||||||
|
- 所有群员可根据既有三级分销规则绑定上级关系。
|
||||||
|
- **分销身份规则**
|
||||||
|
- 每个群员在「平台 + 房间 + 群」这一组合下有一个独立分销身份。
|
||||||
|
- 同一微信号在不同房间、不同群中,对系统而言是多个独立身份。
|
||||||
|
- **影响**
|
||||||
|
- 每个身份对应一条独立的分销线。
|
||||||
|
- 订单结算与分账都按所在群对应的分销线进行。
|
||||||
|
|
||||||
|
### 2.6 系统机器人 / 监管号
|
||||||
|
|
||||||
|
- 平台可在任意群中驻入一个“系统机器人”账号:
|
||||||
|
- 拥有类似群主的管理能力及后台监管能力。
|
||||||
|
- 用于监管群运营是否健康、是否违规。
|
||||||
|
|
||||||
|
### 2.7 运营商(按微信号)
|
||||||
|
|
||||||
|
- 以微信号为主体的运营视角。
|
||||||
|
- 同一微信号在多个房间、多个群中有多条分销线,但在运营统计中可统一聚合。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、分销身份与订单归属规则
|
||||||
|
|
||||||
|
### 3.1 唯一身份线
|
||||||
|
|
||||||
|
- 每条分销身份线由四段组合定义:
|
||||||
|
**平台ID + 房间ID + 群ID + 用户ID**
|
||||||
|
- 每一条组合都被视为“系统中的一个人、一条分销线”。
|
||||||
|
|
||||||
|
### 3.2 多群多房间身份
|
||||||
|
|
||||||
|
- 同一微信号在不同房间/群中创建或加入时:
|
||||||
|
- 系统会为其生成新的分销身份线。
|
||||||
|
- 不同房间、群的身份互不影响。
|
||||||
|
|
||||||
|
### 3.3 分销绑定
|
||||||
|
|
||||||
|
- 在某群中被拉入的新成员:
|
||||||
|
- 按现有三级分销规则建立上下级关系。
|
||||||
|
- 上下级关系限定在本群对应的分销线中。
|
||||||
|
|
||||||
|
### 3.4 订单与分销归属
|
||||||
|
|
||||||
|
- 用户从某个群入口进入商城并下单:
|
||||||
|
- 所有分销与分账都按该群的分销线计算。
|
||||||
|
- 即使服务商在其他群也有身份,本订单仍只归属于当前群对应的身份线。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、房间与群的前台结构
|
||||||
|
|
||||||
|
### 4.1 房间首页结构
|
||||||
|
|
||||||
|
- 房间头部展示:
|
||||||
|
- 房间头像/标识图;
|
||||||
|
- 品牌名称(4–6 字);
|
||||||
|
- 简短品牌注解。
|
||||||
|
- 顶部功能栏:
|
||||||
|
- 默认入口为:「师傅库 / 商城 / 会话厅」。
|
||||||
|
- 房间主可修改这三个名称(总长度有限制),但功能含义不变。
|
||||||
|
- 顶部入口与底部导航对应,为功能快捷入口。
|
||||||
|
|
||||||
|
### 4.2 师傅库
|
||||||
|
|
||||||
|
- 展示本房间下已入驻的师傅与服务店铺。
|
||||||
|
- 支持按街道、服务品类筛选。
|
||||||
|
- 师傅资料在现有认证信息基础上增加头像和定位信息。
|
||||||
|
- 用户可在师傅详情中:
|
||||||
|
- 直接发起“发布订单”给该师傅;
|
||||||
|
- 或将已有未接订单指派给该师傅。
|
||||||
|
|
||||||
|
### 4.3 商城
|
||||||
|
|
||||||
|
- 房间/群共用一个商城体系:
|
||||||
|
- 房间级定义基础类目与商品池。
|
||||||
|
- 群主在此基础上为本群做选品配置。
|
||||||
|
- 首页默认展示热门服务类目,可左划展示其他商品类目。
|
||||||
|
- 列表支持按价格、评分、销量筛选。
|
||||||
|
- 商品销量按整条上架商品做统一统计。
|
||||||
|
|
||||||
|
### 4.4 会话厅
|
||||||
|
|
||||||
|
- 作为房间的首页使用:
|
||||||
|
- 展示全部群、自建群、已加入群、单聊会话等。
|
||||||
|
- 顶部菜单包含:申请房间、申请建群、搜索群、房间名称设置等入口。
|
||||||
|
- 与群聊页面不同,主要承载群列表与会话入口。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、订单与发布相关需求(概要)
|
||||||
|
|
||||||
|
### 5.1 师傅端状态汇总
|
||||||
|
|
||||||
|
- 师傅端首页顶部使用黄条展示:
|
||||||
|
- 新订单数量;
|
||||||
|
- 未约订单数量;
|
||||||
|
- 待上门/待发货数量。
|
||||||
|
- 点击不同统计可跳转到对应订单列表。
|
||||||
|
|
||||||
|
### 5.2 发单入口
|
||||||
|
|
||||||
|
- 页面底部设置蓝色「+」按钮作为发布主单入口。
|
||||||
|
- 支持:
|
||||||
|
- 服务类/商品类发单;
|
||||||
|
- 选择到第 4 级类目,可多选;
|
||||||
|
- 将常用配置保存为模板。
|
||||||
|
- 支持代填发布,由专业师傅代填问题内容,客户补充地址、电话后提交。
|
||||||
|
|
||||||
|
### 5.3 保险规则(概念级)
|
||||||
|
|
||||||
|
- 发单时默认勾选“启用保险核查服务费”。
|
||||||
|
- 发单方需选择保险费用由谁承担:
|
||||||
|
- 服务费由发单方支付;
|
||||||
|
- 服务费在订单中扣除。
|
||||||
|
- 到付场景下,已产生的保险费用仍需保留,并可在后续订单中抵扣。
|
||||||
|
|
||||||
|
### 5.4 报价/加价模式(概念级)
|
||||||
|
|
||||||
|
- 在满足一定条件(报价模式或允许商家报价)时,显示“加价/报价”入口。
|
||||||
|
- 支持大厅主单、未约/未排/监控单、已接单主单进行报价。
|
||||||
|
- 客户最多可选择 3 家(特例 4 家)报价,确认后进入支付流程。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、「我的」与运营统计(概念级)
|
||||||
|
|
||||||
|
- “我的”页面延用现有结构:
|
||||||
|
- 展示分销中心、订单、选品等信息。
|
||||||
|
- 运营统计以微信号为主体:
|
||||||
|
- 汇总展示多房间、多群的运营数据;
|
||||||
|
- 可按房间、群维度下钻查看。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 七、商品与服务上架(概念级)
|
||||||
|
|
||||||
|
- 服务上架沿用当前规则。
|
||||||
|
- 商品上架需区分于服务:
|
||||||
|
- 针对库存、规格、展示方式做单独设计;
|
||||||
|
- 与服务上架在前台展示上明显区分。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 八、后台与安全监管(概念级)
|
||||||
|
|
||||||
|
- 房间级管理:
|
||||||
|
- 房间创建与审核;
|
||||||
|
- 房间启用/禁用;
|
||||||
|
- 房间收费与扣点配置。
|
||||||
|
- 群级管理:
|
||||||
|
- 建群审核;
|
||||||
|
- 群启用/禁用;
|
||||||
|
- 群主调整;
|
||||||
|
- 系统机器人驻入/移除。
|
||||||
|
- 操作审计:
|
||||||
|
- 记录房间、群创建和重要参数修改,便于运营追溯。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**文档定位**:业务需求说明(偏产品视角)
|
||||||
|
**用途**:为后续详细设计与开发提供业务背景和规则依据
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -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 纯发单群模式
|
||||||
|
|
||||||
|
- 对于“只发单、不聊天”的群:
|
||||||
|
- 群聊可以不保持实时连接;
|
||||||
|
- 用户仅在手动刷新时看到新订单;
|
||||||
|
- 群形态接近“单纯接单页面”,以降低系统实时成本。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 九、「我的」与运营视角
|
||||||
|
|
||||||
|
- “我的”页面沿用现有商城结构:
|
||||||
|
- 包含分销中心、订单、选品等模块。
|
||||||
|
- 统计以微信号为主视角:
|
||||||
|
- 汇总展示该用户在不同房间、群中的运营情况;
|
||||||
|
- 可按房间、群进行明细下钻。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 十、后台与审计(功能级)
|
||||||
|
|
||||||
|
- 房间管理:
|
||||||
|
- 房间申请审批;
|
||||||
|
- 房间启用/禁用;
|
||||||
|
- 房间级扣点/收费配置。
|
||||||
|
- 群管理:
|
||||||
|
- 建群申请审核;
|
||||||
|
- 群启用/禁用;
|
||||||
|
- 群主调整;
|
||||||
|
- 系统机器人驻入/移除。
|
||||||
|
- 审计日志:
|
||||||
|
- 创建/修改房间、群;
|
||||||
|
- 修改扣点与重要配置;
|
||||||
|
- 关键权限调整(如群主、管理员变更)等操作均需记录。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**文档定位**:开发可读的功能说明(不含字段/接口细节)
|
||||||
|
**用途**:指导后续接口设计、表结构设计与前后端实现拆分
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -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年
|
||||||
|
|
||||||
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
@ -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)){
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 商品类别表
|
* 商品类别表
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 + '\'' +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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";
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,4 +36,6 @@ public class OrderListRequest {
|
||||||
private Integer timeout;
|
private Integer timeout;
|
||||||
|
|
||||||
private Boolean needImgs = true;
|
private Boolean needImgs = true;
|
||||||
|
|
||||||
|
private Integer orderType;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,4 +13,6 @@ public class OrderStatisticsRequest {
|
||||||
private Long workerId;
|
private Long workerId;
|
||||||
|
|
||||||
private Long deptId;
|
private Long deptId;
|
||||||
|
|
||||||
|
private Integer orderType;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
@ -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_"]) {
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
<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>
|
||||||
|
|
@ -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 "";
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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">×</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">
|
||||||
|
|
|
||||||
|
|
@ -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');
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,10 @@
|
||||||
title: '师傅者id',
|
title: '师傅者id',
|
||||||
visible: false
|
visible: false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
field: 'workerPhone',
|
||||||
|
title: '手机号'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
field: 'name',
|
field: 'name',
|
||||||
title: '真实姓名'
|
title: '真实姓名'
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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())
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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; // 倒计时小时数
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -42,4 +42,7 @@ public class OrderAttachmentRecord {
|
||||||
*/
|
*/
|
||||||
private String paymentId;
|
private String paymentId;
|
||||||
|
|
||||||
|
|
||||||
|
private Long financialChangeRecordId;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量删除细单表信息
|
* 批量删除细单表信息
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -28,4 +28,9 @@ public class AppOrderAssignRequest {
|
||||||
|
|
||||||
// 分配的商品及数量
|
// 分配的商品及数量
|
||||||
private List<AppGoodsRequest> goodsList;
|
private List<AppGoodsRequest> goodsList;
|
||||||
|
|
||||||
|
//是否立即发货
|
||||||
|
private Integer isQuicklyDelivery;
|
||||||
|
|
||||||
|
private Integer generateServiceOrder;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -24,6 +24,9 @@ public class SysOrderAssignRequest {
|
||||||
// 图片
|
// 图片
|
||||||
private String imageUrl;
|
private String imageUrl;
|
||||||
|
|
||||||
|
// 下单图片
|
||||||
|
private String orderImages;
|
||||||
|
|
||||||
// 商品相关信息
|
// 商品相关信息
|
||||||
private Long goodsDeptCategoryId;
|
private Long goodsDeptCategoryId;
|
||||||
// 服务地址ID
|
// 服务地址ID
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -58,4 +58,6 @@ public interface IOrderAttachmentRecordService
|
||||||
* @return 结果
|
* @return 结果
|
||||||
*/
|
*/
|
||||||
public int deleteOrderAttachmentRecordById(Long id);
|
public int deleteOrderAttachmentRecordById(Long id);
|
||||||
|
|
||||||
|
int deleteOrderAttachmentRecordByOrderDetailId(Long orderDetailId);
|
||||||
}
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -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
Loading…
Reference in New Issue