ai-engineering-init 1.14.0 → 1.14.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/hooks/skill-forced-eval.js +1 -0
- package/.claude/hooks/stop.js +1 -2
- package/.claude/skills/auto-test/SKILL.md +182 -10
- package/.claude/skills/code-patterns/SKILL.md +119 -0
- package/.claude/skills/codex-code-review/SKILL.md +39 -0
- package/.claude/skills/leniu-code-patterns/SKILL.md +179 -2
- package/.claude/skills/leniu-crud-development/SKILL.md +26 -7
- package/.claude/skills/leniu-java-mybatis/SKILL.md +25 -16
- package/.claude/skills/leniu-report-scenario/SKILL.md +508 -0
- package/.claude/skills/leniu-report-scenario/references/customization.md +356 -0
- package/.claude/skills/leniu-report-scenario/references/data-permission.md +182 -0
- package/.claude/skills/leniu-report-scenario/references/report-tables.md +162 -0
- package/.codex/skills/code-patterns/SKILL.md +119 -0
- package/.codex/skills/leniu-code-patterns/SKILL.md +179 -2
- package/.codex/skills/leniu-crud-development/SKILL.md +26 -7
- package/.codex/skills/leniu-java-mybatis/SKILL.md +25 -16
- package/.codex/skills/leniu-report-scenario/SKILL.md +508 -0
- package/.codex/skills/leniu-report-scenario/references/customization.md +356 -0
- package/.codex/skills/leniu-report-scenario/references/data-permission.md +182 -0
- package/.codex/skills/leniu-report-scenario/references/report-tables.md +162 -0
- package/.cursor/hooks/cursor-skill-eval.js +4 -29
- package/.cursor/rules/skill-activation.mdc +1 -7
- package/.cursor/skills/code-patterns/SKILL.md +119 -0
- package/.cursor/skills/leniu-code-patterns/SKILL.md +179 -2
- package/.cursor/skills/leniu-crud-development/SKILL.md +26 -7
- package/.cursor/skills/leniu-java-mybatis/SKILL.md +25 -16
- package/.cursor/skills/leniu-report-scenario/SKILL.md +508 -0
- package/.cursor/skills/leniu-report-scenario/references/customization.md +356 -0
- package/.cursor/skills/leniu-report-scenario/references/data-permission.md +182 -0
- package/.cursor/skills/leniu-report-scenario/references/report-tables.md +162 -0
- package/CLAUDE.md +0 -28
- package/bin/index.js +145 -52
- package/package.json +1 -1
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
# 汇总表定制开发指南
|
|
2
|
+
|
|
3
|
+
> 本文件按需加载:当需要新建汇总表(实现 MQ 消费 + fix 重算)时读取。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 一、报表系统架构
|
|
8
|
+
|
|
9
|
+
### 两阶段消费模型
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
MQ 消息到达(下单/退款)
|
|
13
|
+
↓
|
|
14
|
+
第一阶段(ORDER < 10,同步写基础表)
|
|
15
|
+
├── ORDER=1 ReportOrderInfoService → report_order_info
|
|
16
|
+
├── ORDER=3 ReportOrderDetailService → report_order_detail
|
|
17
|
+
└── ORDER=5 ReportRefundService → report_refund / report_refund_detail
|
|
18
|
+
↓
|
|
19
|
+
第二阶段(ORDER >= 10,汇总表处理)
|
|
20
|
+
├── ORDER=10 ReportSumOrganizationService → 组织汇总
|
|
21
|
+
├── ORDER=11 ReportSumCanteenService → 食堂汇总
|
|
22
|
+
├── ORDER=12 ReportSumTypeService → 用户类别汇总
|
|
23
|
+
├── ORDER=13 ReportSumPayService → 支付汇总
|
|
24
|
+
├── ORDER=14 ReportSumMealtimeService → 餐次汇总
|
|
25
|
+
├── ORDER=30 ReportSumDishesService → 菜品销售汇总
|
|
26
|
+
├── ORDER=50 ReportAnalysisCustService → 用户分析
|
|
27
|
+
└── ORDER=51 ReportAnalysisDishesSaleService → 菜品销售分析
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### v5.29 vs 标准版的第二阶段差异
|
|
31
|
+
|
|
32
|
+
| 版本 | 第二阶段模式 | 核心方法 |
|
|
33
|
+
|------|-------------|---------|
|
|
34
|
+
| v5.29 | `batchConsume()` 增量累加 | 分组 → 查存量 → 累加/新建 |
|
|
35
|
+
| 标准版 | `fix()` 按日重算 | 先删后插,从基础表重新聚合 |
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 二、ReportOrderConsumeService 接口
|
|
40
|
+
|
|
41
|
+
```java
|
|
42
|
+
public interface ReportOrderConsumeService extends Ordered {
|
|
43
|
+
|
|
44
|
+
// 单笔消费(第一阶段基础表使用)
|
|
45
|
+
void consume(OrderChangePO payload, ReportOrderInfoDTO baseInfo);
|
|
46
|
+
|
|
47
|
+
// 批量消费(第二阶段汇总表使用)
|
|
48
|
+
default void batchConsume(List<ReportOrderConsumeDTO> consumeList) { }
|
|
49
|
+
|
|
50
|
+
// 批量菜品消费
|
|
51
|
+
default void batchDishesConsume(List<ReportOrderDishesConsumeDTO> consumeList) { }
|
|
52
|
+
|
|
53
|
+
// 数据修复:删除时间段内数据并重新聚合
|
|
54
|
+
void fix(ReportBaseParam param);
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 三、实现汇总 Service(v5.29 batchConsume 模式)
|
|
61
|
+
|
|
62
|
+
```java
|
|
63
|
+
@Service
|
|
64
|
+
@Slf4j
|
|
65
|
+
public class ReportSumXxxService implements ReportOrderConsumeService {
|
|
66
|
+
|
|
67
|
+
@Autowired
|
|
68
|
+
private ReportSumXxxMapper reportSumXxxMapper;
|
|
69
|
+
|
|
70
|
+
@Override
|
|
71
|
+
public int getOrder() { return 15; } // 10-29普通, 30+菜品, 50+分析
|
|
72
|
+
|
|
73
|
+
@Override
|
|
74
|
+
public void consume(OrderChangePO payload, ReportOrderInfoDTO baseInfo) {
|
|
75
|
+
// 汇总表通常留空
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
@Override
|
|
79
|
+
public void batchConsume(List<ReportOrderConsumeDTO> list) {
|
|
80
|
+
// 1. 按维度分组(使用 KEY_SPLIT = "~~~~")
|
|
81
|
+
Map<String, List<ReportOrderConsumeDTO>> grouped = list.stream()
|
|
82
|
+
.collect(Collectors.groupingBy(o ->
|
|
83
|
+
o.getPayTime().toLocalDate() + CommonConstants.KEY_SPLIT +
|
|
84
|
+
o.getCanteenId() + CommonConstants.KEY_SPLIT +
|
|
85
|
+
o.getStallId() + CommonConstants.KEY_SPLIT +
|
|
86
|
+
o.getMealtimeType()
|
|
87
|
+
));
|
|
88
|
+
|
|
89
|
+
List<ReportSumXxx> insertList = new ArrayList<>();
|
|
90
|
+
List<ReportSumXxx> updateList = new ArrayList<>();
|
|
91
|
+
|
|
92
|
+
for (Map.Entry<String, List<ReportOrderConsumeDTO>> entry : grouped.entrySet()) {
|
|
93
|
+
String[] keys = entry.getKey().split(CommonConstants.KEY_SPLIT, -1);
|
|
94
|
+
LocalDate statisticDate = SafeTypeConvertUtil.parseLocalDateSafe(keys[0]);
|
|
95
|
+
Long canteenId = SafeTypeConvertUtil.parseLongSafe(keys[1]);
|
|
96
|
+
Long stallId = SafeTypeConvertUtil.parseLongSafe(keys[2]);
|
|
97
|
+
Integer mealtimeType = SafeTypeConvertUtil.parseIntSafe(keys[3]);
|
|
98
|
+
|
|
99
|
+
// 2. 查询是否存在
|
|
100
|
+
ReportSumXxx existing = reportSumXxxMapper.selectOne(
|
|
101
|
+
Wrappers.<ReportSumXxx>lambdaQuery()
|
|
102
|
+
.eq(ReportSumXxx::getStatisticDate, statisticDate)
|
|
103
|
+
.eq(ReportSumXxx::getCanteenId, canteenId)
|
|
104
|
+
.eq(ReportSumXxx::getStallId, stallId)
|
|
105
|
+
.eq(ReportSumXxx::getMealtimeType, mealtimeType)
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
ReportSumXxx record;
|
|
109
|
+
if (existing == null) {
|
|
110
|
+
record = new ReportSumXxx();
|
|
111
|
+
record.setId(Id.next());
|
|
112
|
+
record.setStatisticDate(statisticDate);
|
|
113
|
+
record.setCanteenId(canteenId);
|
|
114
|
+
record.setStallId(stallId);
|
|
115
|
+
record.setMealtimeType(mealtimeType);
|
|
116
|
+
insertList.add(record);
|
|
117
|
+
} else {
|
|
118
|
+
record = existing;
|
|
119
|
+
updateList.add(record);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// 3. 累加金额(参考 ReportSumCanteen.generateSummary)
|
|
123
|
+
for (ReportOrderConsumeDTO dto : entry.getValue()) {
|
|
124
|
+
record.setConsumeNum(dto.getCancelFlag() == 1
|
|
125
|
+
? record.getConsumeNum() - 1
|
|
126
|
+
: record.getConsumeNum() + 1);
|
|
127
|
+
record.setRealAmount(record.getRealAmount().add(dto.getRealAmount()));
|
|
128
|
+
record.setRefundAmount(record.getRefundAmount().add(dto.getRealRefundAmount()));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 4. 批量写入
|
|
133
|
+
if (CollUtil.isNotEmpty(insertList)) {
|
|
134
|
+
reportSumXxxMapper.insert(insertList);
|
|
135
|
+
}
|
|
136
|
+
if (CollUtil.isNotEmpty(updateList)) {
|
|
137
|
+
reportSumXxxMapper.updateById(updateList);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
@Override
|
|
142
|
+
public void fix(ReportBaseParam param) {
|
|
143
|
+
// 先删后插
|
|
144
|
+
reportSumXxxMapper.delete(Wrappers.<ReportSumXxx>lambdaQuery()
|
|
145
|
+
.between(ReportSumXxx::getStatisticDate,
|
|
146
|
+
param.getStartDate(), param.getEndDate()));
|
|
147
|
+
reportSumXxxMapper.initFix(param.getStartDate(), param.getEndDate());
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## 四、实现汇总 Service(标准版 fix 模式)
|
|
155
|
+
|
|
156
|
+
```java
|
|
157
|
+
@Service
|
|
158
|
+
@Slf4j
|
|
159
|
+
public class ReportSumXxxService implements ReportOrderConsumeService {
|
|
160
|
+
|
|
161
|
+
@Override
|
|
162
|
+
public int getOrder() { return 15; }
|
|
163
|
+
|
|
164
|
+
@Override
|
|
165
|
+
public void consume(OrderChangePO payload, ReportOrderInfoDTO baseInfo) {
|
|
166
|
+
// 标准版留空,由 fix() 统一处理
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
@Override
|
|
170
|
+
public void fix(ReportBaseParam param) {
|
|
171
|
+
LocalDateTime start = param.getStartPayTime();
|
|
172
|
+
LocalDateTime end = param.getEndPayTime();
|
|
173
|
+
reportSumXxxMapper.delete(Wrappers.<ReportSumXxx>lambdaQuery()
|
|
174
|
+
.between(ReportSumXxx::getStatisticDate,
|
|
175
|
+
start.toLocalDate(), end.toLocalDate()));
|
|
176
|
+
reportSumXxxMapper.initFix(start, end);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## 五、fix SQL 模板(initFix)
|
|
184
|
+
|
|
185
|
+
```xml
|
|
186
|
+
<insert id="initFix">
|
|
187
|
+
INSERT INTO report_sum_xxx (id, statistic_date, canteen_id, canteen_name,
|
|
188
|
+
stall_id, stall_name, order_count, consume_amount, refund_amount, net_amount,
|
|
189
|
+
crby, crtime, upby, uptime, del_flag)
|
|
190
|
+
SELECT
|
|
191
|
+
#{id},
|
|
192
|
+
DATE(a.pay_time),
|
|
193
|
+
a.canteen_id,
|
|
194
|
+
a.canteen_name,
|
|
195
|
+
a.stall_id,
|
|
196
|
+
a.stall_name,
|
|
197
|
+
COUNT(*),
|
|
198
|
+
SUM(CASE WHEN a.consume_type = 1 THEN a.real_amount ELSE 0 END),
|
|
199
|
+
SUM(CASE WHEN a.consume_type = 2 THEN ABS(a.real_refund_amount) ELSE 0 END),
|
|
200
|
+
SUM(a.real_amount) + SUM(IFNULL(a.real_refund_amount, 0)),
|
|
201
|
+
'system', NOW(), 'system', NOW(), 2
|
|
202
|
+
FROM report_order_info a
|
|
203
|
+
WHERE a.pay_time BETWEEN #{startTime} AND #{endTime}
|
|
204
|
+
GROUP BY DATE(a.pay_time), a.canteen_id, a.canteen_name, a.stall_id, a.stall_name
|
|
205
|
+
</insert>
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**标准版 initFix**(无 consumeType,退款用 refundAmount):
|
|
209
|
+
```xml
|
|
210
|
+
<insert id="initFix">
|
|
211
|
+
INSERT INTO report_sum_xxx (...)
|
|
212
|
+
SELECT
|
|
213
|
+
#{id}, DATE(a.pay_time), a.canteen_id, a.canteen_name,
|
|
214
|
+
COUNT(*),
|
|
215
|
+
SUM(a.real_amount),
|
|
216
|
+
SUM(IFNULL(a.refund_amount, 0)),
|
|
217
|
+
SUM(a.real_amount - IFNULL(a.refund_amount, 0))
|
|
218
|
+
FROM report_order_info a
|
|
219
|
+
WHERE a.pay_time BETWEEN #{startTime} AND #{endTime}
|
|
220
|
+
GROUP BY DATE(a.pay_time), a.canteen_id, a.canteen_name
|
|
221
|
+
</insert>
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## 六、消费调度器(ReportConsumerService)
|
|
227
|
+
|
|
228
|
+
```
|
|
229
|
+
POST /summary/fix/order → ReportFixController
|
|
230
|
+
├─ 校验日期范围(≤31天)
|
|
231
|
+
└─ Executors.doInTenant(tenantId, () → ReportFixService.fix(param))
|
|
232
|
+
→ 遍历所有 ReportOrderConsumeService 实现类
|
|
233
|
+
→ 按 ORDER 排序,逐个调用 fix()
|
|
234
|
+
|
|
235
|
+
MQ 消费触发 → ReportConsumerService.consumeOrderReport()
|
|
236
|
+
├─ 获取所有正常商户
|
|
237
|
+
├─ 对每个商户获取 Redisson 分布式锁
|
|
238
|
+
├─ 分片查询待消费数据(每批 1000 条)
|
|
239
|
+
└─ 按 ORDER 排序,调用 batchConsume()
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## 七、汇总 Entity 模板
|
|
245
|
+
|
|
246
|
+
```java
|
|
247
|
+
@Data
|
|
248
|
+
@TableName("report_sum_xxx")
|
|
249
|
+
@ApiModel("XXX汇总表")
|
|
250
|
+
public class ReportSumXxx {
|
|
251
|
+
|
|
252
|
+
@TableId
|
|
253
|
+
private Long id;
|
|
254
|
+
|
|
255
|
+
// 维度字段
|
|
256
|
+
private LocalDate statisticDate;
|
|
257
|
+
private Long areaId, canteenId, stallId, orgId;
|
|
258
|
+
private String areaName, canteenName, stallName, orgFullId, orgFullName;
|
|
259
|
+
private Integer mealtimeType;
|
|
260
|
+
|
|
261
|
+
// 统计字段(单位:分,初始值 ZERO)
|
|
262
|
+
private Integer custNum = 0;
|
|
263
|
+
private Integer consumeNum = 0;
|
|
264
|
+
private BigDecimal payableAmount = BigDecimal.ZERO;
|
|
265
|
+
private BigDecimal realAmount = BigDecimal.ZERO;
|
|
266
|
+
private BigDecimal refundAmount = BigDecimal.ZERO;
|
|
267
|
+
private BigDecimal totalAmount = BigDecimal.ZERO;
|
|
268
|
+
|
|
269
|
+
// 审计字段
|
|
270
|
+
private String crby, upby;
|
|
271
|
+
private LocalDateTime crtime, uptime;
|
|
272
|
+
private Integer delFlag;
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## 八、建表模板
|
|
279
|
+
|
|
280
|
+
```sql
|
|
281
|
+
CREATE TABLE report_sum_xxx (
|
|
282
|
+
id BIGINT NOT NULL COMMENT '主键(雪花ID)',
|
|
283
|
+
statistic_date DATE COMMENT '统计日期',
|
|
284
|
+
area_id BIGINT COMMENT '区域ID',
|
|
285
|
+
area_name VARCHAR(128) COMMENT '区域名称',
|
|
286
|
+
canteen_id BIGINT COMMENT '食堂ID',
|
|
287
|
+
canteen_name VARCHAR(128) COMMENT '食堂名称',
|
|
288
|
+
stall_id BIGINT COMMENT '档口ID',
|
|
289
|
+
stall_name VARCHAR(128) COMMENT '档口名称',
|
|
290
|
+
org_id BIGINT COMMENT '组织ID',
|
|
291
|
+
org_full_name VARCHAR(512) COMMENT '组织全名',
|
|
292
|
+
mealtime_type INT COMMENT '餐次类型',
|
|
293
|
+
cust_num INT DEFAULT 0 COMMENT '消费人数',
|
|
294
|
+
consume_num INT DEFAULT 0 COMMENT '消费次数',
|
|
295
|
+
payable_amount DECIMAL(18,2) DEFAULT 0 COMMENT '应付金额(分)',
|
|
296
|
+
real_amount DECIMAL(18,2) DEFAULT 0 COMMENT '实付金额(分)',
|
|
297
|
+
refund_amount DECIMAL(18,2) DEFAULT 0 COMMENT '退款金额(分)',
|
|
298
|
+
total_amount DECIMAL(18,2) DEFAULT 0 COMMENT '合计金额(分)',
|
|
299
|
+
crby VARCHAR(64) COMMENT '创建人',
|
|
300
|
+
crtime DATETIME COMMENT '创建时间',
|
|
301
|
+
upby VARCHAR(64) COMMENT '更新人',
|
|
302
|
+
uptime DATETIME COMMENT '更新时间',
|
|
303
|
+
del_flag INT DEFAULT 2 COMMENT '删除标识(1-删除,2-正常)',
|
|
304
|
+
PRIMARY KEY (id),
|
|
305
|
+
INDEX idx_statistic_date (statistic_date),
|
|
306
|
+
INDEX idx_canteen (canteen_id, stall_id)
|
|
307
|
+
) COMMENT='XXX汇总表';
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
## 九、账户流水汇总
|
|
313
|
+
|
|
314
|
+
实现 `ReportAccountConsumeService` 接口(类似订单版),数据源为 `report_account_flow`。
|
|
315
|
+
|
|
316
|
+
```sql
|
|
317
|
+
SELECT d.wallet_id,
|
|
318
|
+
SUM(CASE WHEN d.flow_type = 110 THEN ABS(d.amount) ELSE 0 END) AS consumeAmount,
|
|
319
|
+
SUM(CASE WHEN d.flow_type = 130 THEN d.amount ELSE 0 END) AS refundAmount
|
|
320
|
+
FROM report_account_flow f
|
|
321
|
+
JOIN report_account_flow_detail d ON f.flow_id = d.flow_id
|
|
322
|
+
WHERE f.pay_time BETWEEN #{startTime} AND #{endTime}
|
|
323
|
+
GROUP BY d.wallet_id
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
## 十、开发检查清单
|
|
329
|
+
|
|
330
|
+
### 建表
|
|
331
|
+
- [ ] 维度字段 + 金额汇总字段 + 审计字段(crby/crtime/upby/uptime/del_flag),无 tenant_id
|
|
332
|
+
|
|
333
|
+
### 实现
|
|
334
|
+
- [ ] 实现 `ReportOrderConsumeService`,设置 `getOrder()` 值
|
|
335
|
+
- [ ] v5.29:实现 `batchConsume()`(分组 → 查存量 → 累加/新建)
|
|
336
|
+
- [ ] 标准版:实现 `fix()`(先删后插)
|
|
337
|
+
- [ ] 两个版本都实现 `fix()` 方法
|
|
338
|
+
|
|
339
|
+
### 退款
|
|
340
|
+
- [ ] v5.29:`consumeType=2` 退款金额为负数,直接 SUM
|
|
341
|
+
- [ ] 标准版:`report_refund` 独立表,退款金额为正数
|
|
342
|
+
|
|
343
|
+
### SQL 合规(only_full_group_by)
|
|
344
|
+
- [ ] SELECT 非聚合字段全部在 GROUP BY 中
|
|
345
|
+
- [ ] GROUP BY 表达式与 SELECT 完全一致
|
|
346
|
+
|
|
347
|
+
### 关键代码位置(标准版 core-report)
|
|
348
|
+
|
|
349
|
+
| 类型 | 路径前缀 `core-report/.../statistics/` |
|
|
350
|
+
|------|------|
|
|
351
|
+
| MQ 监听器 | `config/mq/ReportOrderMQListener.java` |
|
|
352
|
+
| 消费调度 | `config/mq/service/ReportConsumerService.java` |
|
|
353
|
+
| ConsumeService 接口 | `config/mq/ReportOrderConsumeService.java` |
|
|
354
|
+
| 食堂汇总 | `order/summary/service/ReportSumCanteenService.java` |
|
|
355
|
+
| 餐次汇总 | `order/summary/service/ReportSumMealtimeService.java` |
|
|
356
|
+
| Fix Controller | `order/fix/controller/ReportFixController.java` |
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# 报表数据权限集成
|
|
2
|
+
|
|
3
|
+
> 本文件按需加载:当报表需要数据权限过滤时读取。默认不加载。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 一、权限模型
|
|
8
|
+
|
|
9
|
+
### ReportDataPermissionParam
|
|
10
|
+
|
|
11
|
+
```java
|
|
12
|
+
@Data
|
|
13
|
+
public class ReportDataPermissionParam {
|
|
14
|
+
private List<Long> areaIdList; // 区域权限
|
|
15
|
+
private List<Long> orgIdList; // 机构权限
|
|
16
|
+
private List<Long> canteenIdList; // 食堂权限
|
|
17
|
+
private List<Long> stallIdList; // 档口权限
|
|
18
|
+
private Integer roleType; // 权限类型
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### 权限类型
|
|
23
|
+
|
|
24
|
+
| roleType | 含义 | 过滤维度 |
|
|
25
|
+
|----------|------|---------|
|
|
26
|
+
| -1 | 超级管理员 | 不过滤 |
|
|
27
|
+
| 1 | 区域权限 | areaIdList |
|
|
28
|
+
| 2 | 机构权限 | orgIdList |
|
|
29
|
+
| 3 | 食堂权限 | canteenIdList |
|
|
30
|
+
| 4 | 档口权限 | stallIdList |
|
|
31
|
+
| 5 | 食堂+档口权限 | canteenIdList + stallIdList |
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 二、Service 层集成
|
|
36
|
+
|
|
37
|
+
```java
|
|
38
|
+
@Service
|
|
39
|
+
@Slf4j
|
|
40
|
+
public class XxxReportService {
|
|
41
|
+
|
|
42
|
+
@Autowired
|
|
43
|
+
private MgrAuthV2Api mgrAuthApi;
|
|
44
|
+
@Autowired
|
|
45
|
+
private ReportDataPermissionService reportDataPermissionService;
|
|
46
|
+
@Autowired
|
|
47
|
+
private AsyncTaskExecutor asyncTaskExecutor;
|
|
48
|
+
|
|
49
|
+
public ReportBaseTotalVO<XxxVO> pageWithTotal(XxxParam param) {
|
|
50
|
+
// 1. 获取用户权限
|
|
51
|
+
MgrUserAuthPO authPO = mgrAuthApi.getUserAuthPO();
|
|
52
|
+
ReportDataPermissionParam permission =
|
|
53
|
+
reportDataPermissionService.getDataPermission(authPO);
|
|
54
|
+
|
|
55
|
+
// 2. 并行查询(携带权限参数)
|
|
56
|
+
CompletableFuture<Long> countF = CompletableFuture.supplyAsync(
|
|
57
|
+
() -> mapper.listSummary_COUNT(param, authPO, permission), asyncTaskExecutor);
|
|
58
|
+
CompletableFuture<List<XxxVO>> listF = CompletableFuture.supplyAsync(() -> {
|
|
59
|
+
PageMethod.startPage(param.getPage());
|
|
60
|
+
return mapper.listSummaryPage(param, authPO, permission);
|
|
61
|
+
}, asyncTaskExecutor);
|
|
62
|
+
CompletableFuture<XxxVO> totalF = CompletableFuture.supplyAsync(
|
|
63
|
+
() -> mapper.getSummaryTotal(param, authPO, permission), asyncTaskExecutor);
|
|
64
|
+
CompletableFuture.allOf(countF, listF, totalF).join();
|
|
65
|
+
|
|
66
|
+
PageVO<XxxVO> pageVO = PageVO.of(listF.join());
|
|
67
|
+
pageVO.setTotal(countF.join());
|
|
68
|
+
return new ReportBaseTotalVO<XxxVO>()
|
|
69
|
+
.setResultPage(pageVO)
|
|
70
|
+
.setTotalLine(totalF.join());
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## 三、Mapper 签名
|
|
78
|
+
|
|
79
|
+
```java
|
|
80
|
+
public interface XxxReportMapper extends BaseMapper<ReportSumXxx> {
|
|
81
|
+
|
|
82
|
+
List<XxxVO> listSummaryPage(
|
|
83
|
+
@Param("param") XxxParam param,
|
|
84
|
+
@Param("authPO") MgrUserAuthPO authPO,
|
|
85
|
+
@Param("dataPermission") ReportDataPermissionParam dataPermission
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
XxxVO getSummaryTotal(
|
|
89
|
+
@Param("param") XxxParam param,
|
|
90
|
+
@Param("authPO") MgrUserAuthPO authPO,
|
|
91
|
+
@Param("dataPermission") ReportDataPermissionParam dataPermission
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
Long listSummary_COUNT(
|
|
95
|
+
@Param("param") XxxParam param,
|
|
96
|
+
@Param("authPO") MgrUserAuthPO authPO,
|
|
97
|
+
@Param("dataPermission") ReportDataPermissionParam dataPermission
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## 四、XML 权限 SQL
|
|
105
|
+
|
|
106
|
+
### 方式一:引用公共权限片段
|
|
107
|
+
|
|
108
|
+
```xml
|
|
109
|
+
<sql id="baseWhere">
|
|
110
|
+
WHERE a.del_flag = 2
|
|
111
|
+
<if test="param.startDate != null">
|
|
112
|
+
AND a.statistic_date >= #{param.startDate}
|
|
113
|
+
</if>
|
|
114
|
+
<if test="param.endDate != null">
|
|
115
|
+
AND a.statistic_date <= #{param.endDate}
|
|
116
|
+
</if>
|
|
117
|
+
<!-- 引入数据权限 -->
|
|
118
|
+
<include refid="net.xnzn.core.report.statistics.common.mapper.ReportDataPermissionMapper.dataPermission"/>
|
|
119
|
+
</sql>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### 方式二:内联权限判断
|
|
123
|
+
|
|
124
|
+
```xml
|
|
125
|
+
<!-- 超级管理员不过滤 -->
|
|
126
|
+
<if test="'-1'.toString() != authPO.roleType.toString()">
|
|
127
|
+
AND EXISTS (
|
|
128
|
+
SELECT null FROM mgr_role_org it1
|
|
129
|
+
WHERE a.org_id = it1.org_id
|
|
130
|
+
AND it1.role_id = #{authPO.roleId}
|
|
131
|
+
)
|
|
132
|
+
</if>
|
|
133
|
+
|
|
134
|
+
<!-- 食堂权限过滤 -->
|
|
135
|
+
<if test="dataPermission.canteenIdList != null and dataPermission.canteenIdList.size() > 0">
|
|
136
|
+
AND a.canteen_id IN
|
|
137
|
+
<foreach collection="dataPermission.canteenIdList" item="id" open="(" separator="," close=")">
|
|
138
|
+
#{id}
|
|
139
|
+
</foreach>
|
|
140
|
+
</if>
|
|
141
|
+
|
|
142
|
+
<!-- 档口权限过滤 -->
|
|
143
|
+
<if test="dataPermission.stallIdList != null and dataPermission.stallIdList.size() > 0">
|
|
144
|
+
AND a.stall_id IN
|
|
145
|
+
<foreach collection="dataPermission.stallIdList" item="id" open="(" separator="," close=")">
|
|
146
|
+
#{id}
|
|
147
|
+
</foreach>
|
|
148
|
+
</if>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## 五、Controller 层(带权限的导出)
|
|
154
|
+
|
|
155
|
+
```java
|
|
156
|
+
@PostMapping("/export")
|
|
157
|
+
@ApiOperation("导出(含权限)")
|
|
158
|
+
public void export(@RequestBody LeRequest<XxxParam> request) {
|
|
159
|
+
XxxParam param = request.getContent();
|
|
160
|
+
|
|
161
|
+
// 获取权限(导出也需要权限过滤)
|
|
162
|
+
MgrUserAuthPO authPO = mgrAuthApi.getUserAuthPO();
|
|
163
|
+
ReportDataPermissionParam permission =
|
|
164
|
+
reportDataPermissionService.getDataPermission(authPO);
|
|
165
|
+
|
|
166
|
+
XxxVO totalLine = mapper.getSummaryTotal(param, authPO, permission);
|
|
167
|
+
|
|
168
|
+
exportApi.startExcelExportTaskByPage(
|
|
169
|
+
I18n.getMessage("report.xxx.title"),
|
|
170
|
+
I18n.getMessage(ReportConstant.REPORT_TITLE_DETAILS),
|
|
171
|
+
XxxVO.class,
|
|
172
|
+
param.getExportCols(),
|
|
173
|
+
param.getPage(),
|
|
174
|
+
totalLine,
|
|
175
|
+
() -> {
|
|
176
|
+
PageMethod.startPage(param.getPage());
|
|
177
|
+
List<XxxVO> list = mapper.listSummaryPage(param, authPO, permission);
|
|
178
|
+
return PageVO.of(list);
|
|
179
|
+
}
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
```
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# 报表基础表完整字段说明
|
|
2
|
+
|
|
3
|
+
> 本文件按需加载:当报表基于 report_order_info / report_account_flow 开发时读取。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## report_order_info(报表订单主表)
|
|
8
|
+
|
|
9
|
+
### v5.29 版本(sys-canteen 模块)
|
|
10
|
+
|
|
11
|
+
> 正向下单和逆向退款**都存在这张表中**,通过 `consumeType` 区分。
|
|
12
|
+
|
|
13
|
+
| 字段 | 类型 | 说明 |
|
|
14
|
+
|------|------|------|
|
|
15
|
+
| `orderId` | Long | 主键。正向=订单ID,退款=退款单ID |
|
|
16
|
+
| `relationOrderId` | Long | 关联订单ID(退款记录指向原始订单) |
|
|
17
|
+
| `consumeType` | Integer | **1=消费,2=退款** |
|
|
18
|
+
| `orderRefundState` | Integer | **1=未退单,2=已退单(全退),3=部分退单** |
|
|
19
|
+
| `payableAmount` | BigDecimal | 应付金额(分) |
|
|
20
|
+
| `realAmount` | BigDecimal | 实付金额(分) |
|
|
21
|
+
| `refundAmount` | BigDecimal | 累计退款金额(分) |
|
|
22
|
+
| `realRefundAmount` | BigDecimal | 实际退款金额(分,退款记录为负数) |
|
|
23
|
+
| `walletAmount` | BigDecimal | 个人钱包支付金额(分) |
|
|
24
|
+
| `subsidyAmount` | BigDecimal | 补贴钱包支付金额(分) |
|
|
25
|
+
| `redEnvelopeAmount` | BigDecimal | 红包支付金额(分) |
|
|
26
|
+
| `accPayAmount` | BigDecimal | 账户支付金额(分) |
|
|
27
|
+
| `outPayAmount` | BigDecimal | 外部支付金额(分) |
|
|
28
|
+
| `payTime` | LocalDateTime | 支付时间(退款=审核时间) |
|
|
29
|
+
| `orderTime` | LocalDateTime | 下单时间 |
|
|
30
|
+
| `orderDate` | LocalDate | 就餐日期 |
|
|
31
|
+
| `status` | Integer | 消费状态:0=未消费,1=已消费 |
|
|
32
|
+
| `mealtimeType` | Integer | 餐次类型(1早/2午/3下午茶/4晚/5夜宵/-1其他) |
|
|
33
|
+
| `orderType` | Integer | 订单类型(1当餐/2预订/3报餐/4扫码/5餐桌/6自助/11商城/12超市/21补扣/22外部) |
|
|
34
|
+
| `canteenId/canteenName` | Long/String | 食堂 |
|
|
35
|
+
| `stallId/stallName` | Long/String | 档口 |
|
|
36
|
+
| `areaId/areaName` | Long/String | 区域 |
|
|
37
|
+
| `custId/custName/custNum` | - | 用户信息 |
|
|
38
|
+
| `orgId/orgFullId/orgName/orgFullName` | - | 组织信息 |
|
|
39
|
+
| `payType` | Integer | 支付方式 |
|
|
40
|
+
| `payChannel` | Integer | 支付渠道 |
|
|
41
|
+
| `nuClearMode` | Integer | 核身方式(1刷卡/2刷脸/3扫码) |
|
|
42
|
+
| `sourceType` | Integer | 来源类型 |
|
|
43
|
+
| `ifOnline` | Integer | 是否在线订单(1是/2否) |
|
|
44
|
+
| `psnType/psnTypeName` | Integer/String | 用户类别 |
|
|
45
|
+
| `personType` | Integer | 人员归类(1职工/2患者/3陪护/4其他,医院版) |
|
|
46
|
+
|
|
47
|
+
### 标准版(core-report 模块)
|
|
48
|
+
|
|
49
|
+
> 仅存正向订单,**无 consumeType 字段**。退款数据在独立 `report_refund` 表。
|
|
50
|
+
|
|
51
|
+
与 v5.29 版本相比,额外字段:
|
|
52
|
+
- `discountsAmount`(优惠金额,取负数)
|
|
53
|
+
- `deliveryAmount/packingAmount`(配送费/包装费)
|
|
54
|
+
- `ageType/holidayType`(年龄段/节假日类型)
|
|
55
|
+
- `machineSn`(设备SN)
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## report_order_detail(菜品明细表)
|
|
60
|
+
|
|
61
|
+
| 字段 | 类型 | 说明 |
|
|
62
|
+
|------|------|------|
|
|
63
|
+
| `detailId` | Long | 明细主键 |
|
|
64
|
+
| `orderId` | Long | 关联订单ID |
|
|
65
|
+
| `goodsDishesId/goodsDishesName` | Long/String | 菜品信息 |
|
|
66
|
+
| `salePrice` | BigDecimal | 售卖价格(分) |
|
|
67
|
+
| `price` | BigDecimal | 最终价格(分) |
|
|
68
|
+
| `quantity` | Integer | 数量/重量 |
|
|
69
|
+
| `salesMode` | Integer | 销售方式(1按份/2称重) |
|
|
70
|
+
| `totalAmount` | BigDecimal | 总金额(分) |
|
|
71
|
+
| `realAmount` | BigDecimal | 实付金额(分) |
|
|
72
|
+
| `detailState` | Integer | **1=正常,2=已退菜(全退),3=部分退菜** |
|
|
73
|
+
| `goodsRefundNum` | Integer | 已退数量 |
|
|
74
|
+
| `refundAmount` | BigDecimal | 退款金额(分) |
|
|
75
|
+
| `detailType` | Integer | 类别(1菜品/2套餐/3商品/4按键/5补扣/6报餐) |
|
|
76
|
+
| `costPrice` | BigDecimal | 成本价格 |
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## report_refund / report_refund_detail(标准版特有)
|
|
81
|
+
|
|
82
|
+
**report_refund**(退款主表,金额为**正数**):
|
|
83
|
+
|
|
84
|
+
| 字段 | 类型 | 说明 |
|
|
85
|
+
|------|------|------|
|
|
86
|
+
| `orderRefundId` | Long | 退款单主键 |
|
|
87
|
+
| `orderId` | Long | 原始订单ID |
|
|
88
|
+
| `realRefundAmount` | BigDecimal | 退款金额(**正数**) |
|
|
89
|
+
| `applyType` | Integer | 申请类型(1退单/2纠错) |
|
|
90
|
+
| `checkTime` | LocalDateTime | 审核时间 |
|
|
91
|
+
|
|
92
|
+
**report_refund_detail**:`orderRefundId`, `detailId`, `realQuantity`, `realRefundAmount`(**正数**)
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## report_account_flow(账户流水主表)
|
|
97
|
+
|
|
98
|
+
| 字段 | 类型 | 说明 |
|
|
99
|
+
|------|------|------|
|
|
100
|
+
| `flowId` | Long | 主键(雪花ID) |
|
|
101
|
+
| `custId` | Long | 人员ID |
|
|
102
|
+
| `flowType` | Integer | 交易类型(AccTradeTypeEnum) |
|
|
103
|
+
| `flowRealAmount` | BigDecimal | 实际交易金额 |
|
|
104
|
+
| `flowAmount` | BigDecimal | 交易金额 |
|
|
105
|
+
| `accTotalBal` | BigDecimal | 可用余额之和(不含冻结) |
|
|
106
|
+
| `accAllBal` | BigDecimal | 总余额(含冻结) |
|
|
107
|
+
| `payTime` | LocalDateTime | 支付时间 |
|
|
108
|
+
| `status` | Integer | 消费状态:0=未消费,1=已消费 |
|
|
109
|
+
| `manageCost` | BigDecimal | 管理费 |
|
|
110
|
+
|
|
111
|
+
> `custName`、`mobile` 使用 SM4 加密存储。
|
|
112
|
+
|
|
113
|
+
## report_account_flow_detail(流水钱包明细)
|
|
114
|
+
|
|
115
|
+
| 字段 | 类型 | 说明 |
|
|
116
|
+
|------|------|------|
|
|
117
|
+
| `flowId` | Long | 关联主流水 |
|
|
118
|
+
| `walletId` | Long | 钱包类型(1个人/2补贴/4红包) |
|
|
119
|
+
| `amount` | BigDecimal | 金额(转出取负值) |
|
|
120
|
+
| `walletBal` | BigDecimal | 钱包可用余额 |
|
|
121
|
+
| `frozenBalance` | BigDecimal | 冻结余额 |
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## 枚举速查
|
|
126
|
+
|
|
127
|
+
### AccWalletIdEnum(钱包类型)
|
|
128
|
+
|
|
129
|
+
| key | 枚举 | 含义 |
|
|
130
|
+
|-----|------|------|
|
|
131
|
+
| 1 | WALLET | 个人钱包 |
|
|
132
|
+
| 2 | SUBSIDY | 补贴钱包 |
|
|
133
|
+
| 4 | LUCK_MONEY | 红包 |
|
|
134
|
+
|
|
135
|
+
### AccTradeTypeEnum(交易类型)
|
|
136
|
+
|
|
137
|
+
| key | 枚举 | 金额方向 |
|
|
138
|
+
|-----|------|---------|
|
|
139
|
+
| 10/11 | RECHARGE/RECHARGE_GIFT | 正 |
|
|
140
|
+
| 12/40/50 | 撤销赠送/撤销充值/撤销补贴 | **负** |
|
|
141
|
+
| 20 | SUBSIDY 补贴 | 正 |
|
|
142
|
+
| 30 | WITHDRAW 提现 | **负** |
|
|
143
|
+
| 60/80/100 | 转出/冻结/补贴清空 | **负** |
|
|
144
|
+
| 70/90 | 转入/解冻 | 正 |
|
|
145
|
+
| 110/120 | CONSUME/CONSUME_REPAIR | **负** |
|
|
146
|
+
| 130 | CONSUME_REFUND 退款 | 正 |
|
|
147
|
+
| 140/141/142 | 红包/撤销红包/红包清空 | 正/**负**/**负** |
|
|
148
|
+
|
|
149
|
+
### 账户流水常用过滤
|
|
150
|
+
|
|
151
|
+
```sql
|
|
152
|
+
-- 净消费额:消费为负,退款为正 → SUM 即为净消费额(负值)
|
|
153
|
+
WHERE wallet_id = #{walletId} AND flow_type IN (110, 130)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## 其他基础表
|
|
159
|
+
|
|
160
|
+
- **report_order_pay**:`orderId`, `payType/payChannel`, `payAmount/refundAmount`
|
|
161
|
+
- **report_order_discount**:`orderId`, `changeAmount`, `changeType`(1上浮/2优惠), `changeDetailType`
|
|
162
|
+
- **report_account_summary**(日结表):联合主键 `statisticDate + custId`,期末余额 = 期初 + 充值 - 撤销 + 补贴 - 消费 + 退款 - 提现
|