leniu-dev 2.0.0 → 2.0.1

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.
Files changed (55) hide show
  1. package/.claude/mysql-config.json +34 -0
  2. package/.claude/skills/jenkins-deploy/SKILL.md +21 -5
  3. package/.claude/skills/jenkins-deploy/assets/jk_build.py +29 -14
  4. package/.claude/skills/leniu-java-amount-handling/SKILL.md +461 -0
  5. package/.claude/skills/leniu-java-export/SKILL.md +570 -0
  6. package/.claude/skills/leniu-java-report-query-param/SKILL.md +291 -0
  7. package/.claude/skills/leniu-java-total-line/SKILL.md +196 -0
  8. package/.claude/skills/leniu-marketing-price-rule-customizer/SKILL.md +301 -0
  9. package/.claude/skills/leniu-marketing-recharge-rule-customizer/SKILL.md +285 -0
  10. package/.claude/skills/leniu-mealtime/SKILL.md +215 -0
  11. package/.claude/skills/leniu-report-customization/SKILL.md +415 -0
  12. package/.claude/skills/leniu-report-customization/references/table-fields.md +93 -0
  13. package/.claude/skills/leniu-report-standard-customization/SKILL.md +391 -0
  14. package/.claude/skills/leniu-report-standard-customization/references/analysis-module.md +64 -0
  15. package/.claude/skills/leniu-report-standard-customization/references/table-fields.md +113 -0
  16. package/.claude/skills/loki-log-query/SKILL.md +25 -55
  17. package/.claude/skills/loki-log-query/environments.json +45 -0
  18. package/.claude/skills/mysql-debug/SKILL.md +6 -12
  19. package/.codex/skills/jenkins-deploy/SKILL.md +21 -5
  20. package/.codex/skills/jenkins-deploy/assets/env_param.template.json +51 -0
  21. package/.codex/skills/jenkins-deploy/assets/jk_build.py +415 -0
  22. package/.codex/skills/leniu-java-export/SKILL.md +570 -0
  23. package/.codex/skills/leniu-java-report-query-param/SKILL.md +291 -0
  24. package/.codex/skills/leniu-java-total-line/SKILL.md +196 -0
  25. package/.codex/skills/leniu-marketing-price-rule-customizer/SKILL.md +301 -0
  26. package/.codex/skills/leniu-marketing-recharge-rule-customizer/SKILL.md +285 -0
  27. package/.codex/skills/leniu-mealtime/SKILL.md +215 -0
  28. package/.codex/skills/leniu-report-customization/SKILL.md +415 -0
  29. package/.codex/skills/leniu-report-customization/references/table-fields.md +93 -0
  30. package/.codex/skills/leniu-report-standard-customization/SKILL.md +391 -0
  31. package/.codex/skills/leniu-report-standard-customization/references/analysis-module.md +64 -0
  32. package/.codex/skills/leniu-report-standard-customization/references/table-fields.md +113 -0
  33. package/.codex/skills/loki-log-query/SKILL.md +25 -55
  34. package/.codex/skills/mysql-debug/SKILL.md +6 -12
  35. package/.cursor/skills/jenkins-deploy/SKILL.md +21 -5
  36. package/.cursor/skills/jenkins-deploy/assets/env_param.template.json +51 -0
  37. package/.cursor/skills/jenkins-deploy/assets/jk_build.py +415 -0
  38. package/.cursor/skills/leniu-java-export/SKILL.md +570 -0
  39. package/.cursor/skills/leniu-java-report-query-param/SKILL.md +291 -0
  40. package/.cursor/skills/leniu-java-total-line/SKILL.md +196 -0
  41. package/.cursor/skills/leniu-marketing-price-rule-customizer/SKILL.md +301 -0
  42. package/.cursor/skills/leniu-marketing-recharge-rule-customizer/SKILL.md +285 -0
  43. package/.cursor/skills/leniu-mealtime/SKILL.md +215 -0
  44. package/.cursor/skills/leniu-report-customization/SKILL.md +415 -0
  45. package/.cursor/skills/leniu-report-customization/references/table-fields.md +93 -0
  46. package/.cursor/skills/leniu-report-standard-customization/SKILL.md +391 -0
  47. package/.cursor/skills/leniu-report-standard-customization/references/analysis-module.md +64 -0
  48. package/.cursor/skills/leniu-report-standard-customization/references/table-fields.md +113 -0
  49. package/.cursor/skills/loki-log-query/SKILL.md +25 -55
  50. package/.cursor/skills/mysql-debug/SKILL.md +6 -12
  51. package/bin/index.js +59 -15
  52. package/package.json +1 -1
  53. package/src/skills/jenkins-deploy/SKILL.md +150 -0
  54. package/src/skills/jenkins-deploy/assets/env_param.template.json +51 -0
  55. package/src/skills/jenkins-deploy/assets/jk_build.py +415 -0
@@ -0,0 +1,215 @@
1
+ ---
2
+ name: leniu-mealtime
3
+ description: |
4
+ leniu-tengyun-core 项目餐次处理规范。当编写涉及餐次(早餐/午餐/下午茶/晚餐/夜宵)的报表或查询代码时使用。
5
+
6
+ 触发场景:
7
+ - 在 Param 查询参数类中添加餐次筛选字段(mealtimeTypes)
8
+ - 在 VO 返回类中定义餐次字段和 MealtimeTypeConverter 转换器
9
+ - 编写 MyBatis XML 中的餐次 IN 查询条件
10
+ - 处理餐次枚举转换(AllocMealtimeTypeEnum)
11
+ - 区分正餐(早/午/晚)与非正餐(下午茶/夜宵)
12
+
13
+ 适用项目:
14
+ - leniu-tengyun-core:/Users/xujiajun/Developer/gongsi_proj/leniu-api/leniu-tengyun-core
15
+ - leniu-yunshitang:/Users/xujiajun/Developer/gongsi_proj/leniu-api/leniu-tengyun/leniu-yunshitang
16
+
17
+ 触发词:餐次、mealtime、mealtimeType、早餐、午餐、晚餐、下午茶、夜宵、AllocMealtimeTypeEnum
18
+ ---
19
+
20
+ # leniu-tengyun-core 餐次处理规范
21
+
22
+ ## 餐次枚举定义
23
+
24
+ ### AllocMealtimeTypeEnum
25
+
26
+ 餐次类型枚举 `AllocMealtimeTypeEnum` 定义了5种餐次:
27
+
28
+ | 枚举值 | key | 显示名称 | 说明 |
29
+ |--------|-----|----------|------|
30
+ | MEALTIME_BREAKFAST | 1 | 早餐 | 早餐 |
31
+ | MEALTIME_LUNCH | 2 | 午餐 | 午餐 |
32
+ | MEALTIME_AFTERNOON_TEA | 3 | 下午茶 | 下午茶 |
33
+ | MEALTIME_DINNER | 4 | 晚餐 | 晚餐 |
34
+ | MEALTIME_MIDNIGHT_SNACK | 5 | 夜宵 | 夜宵 |
35
+
36
+ ### 常用方法
37
+
38
+ ```java
39
+ // 根据key获取枚举
40
+ AllocMealtimeTypeEnum typeEnum = AllocMealtimeTypeEnum.getTypeEnum(1);
41
+
42
+ // 根据key获取显示名称
43
+ String desc = AllocMealtimeTypeEnum.getValueByKey(1); // 返回 "早餐"
44
+
45
+ // 获取所有餐次key列表
46
+ List<Integer> allTypes = AllocMealtimeTypeEnum.allTypeList(); // [1, 2, 3, 4, 5]
47
+
48
+ // 获取正餐类型列表
49
+ List<Integer> normalTypes = AllocMealtimeTypeEnum.normalTypeList(); // [1, 2, 4]
50
+ ```
51
+
52
+ ### 餐次分类
53
+
54
+ - **正餐**:早餐(1)、午餐(2)、晚餐(4)
55
+ - **非正餐**:下午茶(3)、夜宵(5)
56
+
57
+ ## 查询参数中的餐次处理
58
+
59
+ ### Param类定义
60
+
61
+ 在报表查询Param类中添加餐次筛选字段:
62
+
63
+ ```java
64
+ @ApiModelProperty(value = "餐次集合")
65
+ private List<Integer> mealtimeTypes;
66
+ ```
67
+
68
+ ### 完整示例
69
+
70
+ ```java
71
+ @Data
72
+ @AllArgsConstructor
73
+ @NoArgsConstructor
74
+ @EqualsAndHashCode(callSuper = true)
75
+ @ApiModel(value = "XXX查询入参")
76
+ public class XxxParam extends ReportBaseParam {
77
+
78
+ @ApiModelProperty(value = "餐次集合")
79
+ private List<Integer> mealtimeTypes;
80
+
81
+ // 其他查询字段...
82
+ }
83
+ ```
84
+
85
+ ## 返回VO中的餐次处理
86
+
87
+ ### VO字段定义
88
+
89
+ 在报表返回VO类中定义餐次字段:
90
+
91
+ ```java
92
+ @Data
93
+ @Accessors(chain = true)
94
+ @ApiModel(value = "XXX报表")
95
+ public class XxxVO {
96
+
97
+ @ExcelProperty(value = "餐次", order = 5, converter = MealtimeTypeConverter.class)
98
+ @ApiModelProperty(value = "餐次")
99
+ private Integer mealtimeType;
100
+
101
+ // 其他字段...
102
+ }
103
+ ```
104
+
105
+ ### 必要的导入
106
+
107
+ ```java
108
+ import com.alibaba.excel.annotation.ExcelProperty;
109
+ import net.xnzn.core.common.export.converter.MealtimeTypeConverter;
110
+ ```
111
+
112
+ ### 字段说明
113
+
114
+ - `mealtimeType`: 存储餐次的整数key值(1-5)
115
+ - `@ExcelProperty`: 配置EasyExcel导出,使用`MealtimeTypeConverter`进行转换
116
+ - `converter = MealtimeTypeConverter.class`: 自动将整数转换为可读的餐次名称
117
+
118
+ ## MyBatis XML中的餐次查询
119
+
120
+ ### 基本IN查询
121
+
122
+ ```xml
123
+ <!-- 餐次筛选 -->
124
+ <if test="mealtimeTypes != null and mealtimeTypes.size() > 0">
125
+ AND mealtime_type IN
126
+ <foreach collection="mealtimeTypes" item="type" open="(" separator="," close=")">
127
+ #{type}
128
+ </foreach>
129
+ </if>
130
+ ```
131
+
132
+ ### 正餐筛选
133
+
134
+ ```xml
135
+ <!-- 仅查询正餐(早餐、午餐、晚餐) -->
136
+ AND mealtime_type IN (1, 2, 4)
137
+ ```
138
+
139
+ ### 完整示例
140
+
141
+ ```xml
142
+ <select id="pageXxx" resultType="net.xnzn.core.xxx.vo.XxxVO">
143
+ SELECT
144
+ id,
145
+ order_date,
146
+ mealtime_type,
147
+ cust_name,
148
+ real_amount
149
+ FROM order_table
150
+ WHERE del_flag = 2
151
+ <if test="startDate != null">
152
+ AND order_date >= #{startDate}
153
+ </if>
154
+ <if test="endDate != null">
155
+ AND order_date &lt;= #{endDate}
156
+ </if>
157
+ <if test="mealtimeTypes != null and mealtimeTypes.size() > 0">
158
+ AND mealtime_type IN
159
+ <foreach collection="mealtimeTypes" item="type" open="(" separator="," close=")">
160
+ #{type}
161
+ </foreach>
162
+ </if>
163
+ <if test="canteenId != null">
164
+ AND canteen_id = #{canteenId}
165
+ </if>
166
+ ORDER BY order_date DESC
167
+ </select>
168
+ ```
169
+
170
+ ## 常见使用场景
171
+
172
+ ### 场景1:查询指定餐次的订单
173
+
174
+ ```java
175
+ // Param定义
176
+ @ApiModelProperty(value = "餐次集合")
177
+ private List<Integer> mealtimeTypes;
178
+
179
+ // Service调用
180
+ List<Integer> mealtimeTypes = Arrays.asList(1, 2); // 查询早餐和午餐
181
+ param.setMealtimeTypes(mealtimeTypes);
182
+ ```
183
+
184
+ ### 场景2:仅查询正餐
185
+
186
+ ```java
187
+ // 方式1:直接在XML中硬编码
188
+ AND mealtime_type IN (1, 2, 4)
189
+
190
+ // 方式2:使用枚举工具类
191
+ param.setMealtimeTypes(AllocMealtimeTypeEnum.normalTypeList());
192
+ ```
193
+
194
+ ### 场景3:导出时餐次列显示中文名称
195
+
196
+ ```java
197
+ // VO定义
198
+ @ExcelProperty(value = "餐次", order = 5, converter = MealtimeTypeConverter.class)
199
+ @ApiModelProperty(value = "餐次")
200
+ private Integer mealtimeType;
201
+
202
+ // 导出时自动转换:1 -> "早餐",2 -> "午餐" 等
203
+ ```
204
+
205
+ ## 注意事项
206
+
207
+ 1. **字段命名**:统一使用 `mealtimeType` (驼峰) 或 `mealtime_type` (下划线)
208
+ 2. **类型**:数据库和Java代码中均使用 `Integer` 类型
209
+ 3. **转换器**:导出时必须使用 `MealtimeTypeConverter` 进行转换
210
+ 4. **国际化的影响**:显示名称根据项目配置可能使用中文或国际化key
211
+ 5. **正餐判断**:需要区分正餐时使用 `AllocMealtimeTypeEnum.normalTypeList()` 方法
212
+
213
+ ## 参考文档
214
+
215
+ 详见:[leniu-tengyun-core 源码](/Users/xujiajun/Developer/gongsi_proj/core/leniu-tengyun-core)
@@ -0,0 +1,415 @@
1
+ ---
2
+ name: leniu-report-customization
3
+ description: |
4
+ leniu-tengyun-core 项目(v5.29)定制报表开发指南。基于 report_order_info / report_order_detail / report_account_flow 等报表基础表实现汇总报表。
5
+
6
+ 触发场景:
7
+ - 基于订单数据实现定制汇总报表
8
+ - 基于账户流水实现定制汇总报表
9
+ - 处理报表中的退款数据(正向/逆向/部分退)
10
+ - 实现报表的 MQ 消费逻辑和 fix 初始化逻辑
11
+ - 报表金额计算(消费金额减退款)
12
+
13
+ 触发词:定制报表、汇总报表、report_order_info、report_order_detail、report_account_flow、退款汇总、消费金额统计、订单报表、流水报表
14
+ ---
15
+
16
+ # leniu 定制报表开发指南
17
+
18
+ > 详细字段说明见 `references/table-fields.md`
19
+
20
+ ## 概述
21
+
22
+ 定制报表基于**报表基础表**(由 MQ 消息写入)进行二次汇总:
23
+ 1. **订单类**:`report_order_info` + `report_order_detail`(消费/退款数据)
24
+ 2. **账户流水类**:`report_account_flow` + `report_account_flow_detail`(钱包变动数据)
25
+
26
+ ---
27
+
28
+ ## 一、报表系统架构
29
+
30
+ ### 1.1 两阶段消费模型
31
+
32
+ ```
33
+ MQ 消息到达(下单/退款)
34
+
35
+ 第一阶段(order < 10,同步写基础表)
36
+ ├── ORDER=1 ReportOrderInfoService → report_order_info
37
+ ├── ORDER=3 ReportOrderDetailService → report_order_detail
38
+ └── ORDER=5 ReportRefundService → report_refund / report_refund_detail
39
+
40
+ 第二阶段(order >= 10,批量写汇总表,由 Redis 计数触发)
41
+ ├── ORDER=11 ReportSumCanteenService → report_sum_canteen(食堂汇总)
42
+ └── ORDER=3x ReportSumDishesService → 菜品销售汇总
43
+ ```
44
+
45
+ ### 1.2 MQ 消息类型
46
+
47
+ | 消息 | Topic/Tag | 监听器 |
48
+ |------|-----------|--------|
49
+ | 下单 | `order / order-v3-placed` | `ReportOrderMQListener` |
50
+ | 退款 | `order / order-v3-refunded` | `ReportOrderRefundMQListener` |
51
+
52
+ ### 1.3 触发汇总消费的机制
53
+
54
+ Redis 计数器 `ORDER_REPORT_COUNT_KEY:{merchantId}`,每来一条 MQ 消息递减,达到阈值时异步触发 `consumeOrderReport()`,从 `report_order_info` 中查出 `status=0`(未消费)的数据,批量调用所有 `order >= 10` 的 Service 的 `batchConsume()`。
55
+
56
+ ---
57
+
58
+ ## 二、核心基础表概要
59
+
60
+ ### 2.1 report_order_info(报表订单主表)
61
+
62
+ > v5.29 后,正向下单和逆向退款**都存在这张表中**,通过 `consumeType` 区分。
63
+
64
+ 关键字段:`orderId`(主键), `relationOrderId`(退款指向原始订单), `consumeType`(**1=消费,2=退款**), `orderRefundState`(1未退/2全退/3部分退), `realAmount`/`refundAmount`/`walletAmount`/`subsidyAmount`(金额,分), `payTime`, `orderDate`, `mealtimeType`, `orderType`, `canteenId/stallId`, `status`(0未消费/1已消费)
65
+
66
+ ### 2.2 report_order_detail(菜品明细表)
67
+
68
+ 关键字段:`detailId`, `orderId`, `goodsDishesId/goodsDishesName`, `price/totalAmount/realAmount`(分), `quantity`, `detailState`(**1正常/2已退菜/3部分退菜**), `goodsRefundNum`, `refundAmount`, `detailType`(1菜品/2套餐/3商品/4按键/5补扣/6报餐)
69
+
70
+ ### 2.3 report_account_flow(账户流水主表)
71
+
72
+ 关键字段:`flowId`, `custId`, `flowType`(AccTradeTypeEnum), `flowRealAmount/flowAmount`, `accTotalBal/accAllBal`, `payTime`, `status`(0未消费/1已消费)
73
+
74
+ > `custName`、`mobile` 使用 SM4 加密存储。
75
+
76
+ ### 2.4 report_account_flow_detail(流水钱包明细)
77
+
78
+ 关键字段:`flowId`, `walletId`(AccWalletIdEnum), `amount`(转出取负值), `walletBal`, `frozenBalance`
79
+
80
+ ---
81
+
82
+ ## 三、退款数据处理(核心重点)
83
+
84
+ ### 3.1 退款数据在 report_order_info 中的表现
85
+
86
+ ```
87
+ 正向订单:orderId=订单ID, consumeType=1, orderRefundState=1
88
+ 退款记录:orderId=退款单ID, consumeType=2, relationOrderId=原始订单ID
89
+ 金额字段全部为负数(realRefundAmount、walletAmount、subsidyAmount 都乘以 -1)
90
+ payTime = 退款审核时间(checkTime)
91
+ ```
92
+
93
+ ### 3.2 净消费金额计算(3种方式)
94
+
95
+ **方式一:直接 SUM(推荐)** - 退款金额已为负数
96
+
97
+ ```sql
98
+ SELECT SUM(real_amount) AS netAmount,
99
+ SUM(wallet_amount) AS netWalletAmount
100
+ FROM report_order_info WHERE pay_time BETWEEN #{startTime} AND #{endTime}
101
+ ```
102
+
103
+ **方式二:分别统计消费和退款**
104
+
105
+ ```sql
106
+ -- 消费总额(consumeType=1)/ 退款总额(consumeType=2,取ABS)
107
+ -- 净额 = 消费总额 - 退款总额
108
+ ```
109
+
110
+ **方式三:排除全退订单**
111
+
112
+ ```sql
113
+ SELECT SUM(real_amount - IFNULL(refund_amount, 0)) AS netAmount
114
+ FROM report_order_info
115
+ WHERE consume_type = 1 AND order_refund_state IN (1, 3)
116
+ ```
117
+
118
+ ### 3.3 菜品级别退款
119
+
120
+ ```
121
+ detailState=1(正常)/ 2(全退)/ 3(部分退)
122
+ 菜品净销量 = quantity - IFNULL(goods_refund_num, 0)
123
+ 菜品净金额 = total_amount - IFNULL(refund_amount, 0)
124
+ ```
125
+
126
+ ---
127
+
128
+ ## 四、钱包与交易类型枚举
129
+
130
+ ### 4.1 AccWalletIdEnum
131
+
132
+ | key | 枚举 | 含义 |
133
+ |-----|------|------|
134
+ | 1 | WALLET | 个人钱包 |
135
+ | 2 | SUBSIDY | 补贴钱包 |
136
+ | 4 | LUCK_MONEY | 红包 |
137
+
138
+ ### 4.2 AccTradeTypeEnum
139
+
140
+ | key | 枚举 | 金额方向 |
141
+ |-----|------|---------|
142
+ | 10/11 | RECHARGE/RECHARGE_GIFT | 正 |
143
+ | 12/40/50 | 撤销赠送/撤销充值/撤销补贴 | **负** |
144
+ | 20 | SUBSIDY 补贴 | 正 |
145
+ | 30 | WITHDRAW 提现 | **负** |
146
+ | 60/80/100 | 转出/冻结/补贴清空 | **负** |
147
+ | 70/90 | 转入/解冻 | 正 |
148
+ | 110/120 | CONSUME/CONSUME_REPAIR | **负** |
149
+ | 130 | CONSUME_REFUND 退款 | 正 |
150
+ | 131/132 | 账户预扣/预扣退款 | **负**/正 |
151
+ | 140/141/142 | 红包/撤销红包/红包清空 | 正/**负**/**负** |
152
+
153
+ ### 4.3 账户流水常用过滤
154
+
155
+ ```java
156
+ // 净消费额:消费为负,退款为正 → SUM 即为净消费额(负值)
157
+ WHERE wallet_id = #{walletId} AND flow_type IN (110, 130)
158
+ ```
159
+
160
+ ---
161
+
162
+ ## 五、汇总表开发标准模式
163
+
164
+ ### 5.1 实现 ReportOrderConsumeService 接口
165
+
166
+ ```java
167
+ @Service @Slf4j
168
+ public class ReportSumXxxService implements ReportOrderConsumeService {
169
+ @Override public int getOrder() { return 15; } // <10基础, 10-29汇总, 30+菜品
170
+
171
+ @Override public void consume(OrderChangePO payload, ReportOrderInfoDTO baseInfo) {
172
+ // 汇总表通常留空
173
+ }
174
+
175
+ @Override public void batchConsume(List<ReportOrderConsumeDTO> list) {
176
+ // 分组 → 查存量 → 累加/新建(见下方模板)
177
+ }
178
+
179
+ @Override public void fix(ReportBaseParam param) {
180
+ // 先删后插,从基础表重新聚合
181
+ }
182
+ }
183
+ ```
184
+
185
+ ### 5.2 batchConsume 模板
186
+
187
+ ```java
188
+ @Override
189
+ public void batchConsume(List<ReportOrderConsumeDTO> list) {
190
+ Map<String, List<ReportOrderConsumeDTO>> grouped = list.stream()
191
+ .collect(Collectors.groupingBy(e ->
192
+ e.getStatisticDate() + "_" + e.getCanteenId() + "_" + e.getStallId()));
193
+
194
+ List<ReportSumXxx> insertList = new ArrayList<>(), updateList = new ArrayList<>();
195
+
196
+ for (var entry : grouped.entrySet()) {
197
+ ReportOrderConsumeDTO first = entry.getValue().get(0);
198
+ ReportSumXxx existing = mapper.selectOne(Wrappers.<ReportSumXxx>lambdaQuery()
199
+ .eq(ReportSumXxx::getStatisticDate, first.getStatisticDate())
200
+ .eq(ReportSumXxx::getCanteenId, first.getCanteenId())
201
+ .eq(ReportSumXxx::getStallId, first.getStallId()));
202
+
203
+ if (existing == null) {
204
+ ReportSumXxx record = new ReportSumXxx();
205
+ record.setId(Id.next());
206
+ // 设置维度 + 累加金额
207
+ insertList.add(record);
208
+ } else {
209
+ // existing.setXxxAmount(existing.getXxxAmount().add(delta));
210
+ updateList.add(existing);
211
+ }
212
+ }
213
+ if (CollUtil.isNotEmpty(insertList)) { /* 批量插入 */ }
214
+ if (CollUtil.isNotEmpty(updateList)) { /* 批量更新 */ }
215
+ }
216
+ ```
217
+
218
+ ### 5.3 fix 方法 + SQL 模板
219
+
220
+ ```java
221
+ @Override
222
+ public void fix(ReportBaseParam param) {
223
+ mapper.delete(Wrappers.<ReportSumXxx>lambdaQuery()
224
+ .between(ReportSumXxx::getStatisticDate,
225
+ param.getStartTime().toLocalDate(), param.getEndTime().toLocalDate()));
226
+ mapper.initFix(null, param.getStartTime(), param.getEndTime());
227
+ }
228
+ ```
229
+
230
+ ```xml
231
+ <insert id="initFix">
232
+ INSERT INTO report_sum_xxx (id, statistic_date, canteen_id, canteen_name,
233
+ order_count, consume_amount, refund_amount, net_amount)
234
+ SELECT #{id}, DATE(pay_time), canteen_id, canteen_name,
235
+ COUNT(*),
236
+ SUM(CASE WHEN consume_type = 1 THEN real_amount ELSE 0 END),
237
+ SUM(CASE WHEN consume_type = 2 THEN ABS(real_refund_amount) ELSE 0 END),
238
+ SUM(real_amount) + SUM(IFNULL(real_refund_amount, 0))
239
+ FROM report_order_info
240
+ WHERE pay_time BETWEEN #{startTime} AND #{endTime}
241
+ GROUP BY DATE(pay_time), canteen_id, canteen_name
242
+ </insert>
243
+ ```
244
+
245
+ ---
246
+
247
+ ## 六、数据权限集成
248
+
249
+ - Service 注入 `MgrAuthV2Api` 和 `ReportDataPermissionService`
250
+ - 查询/导出方法首行调用 `mgrAuthApi.getUserAuthPO()` + `reportDataPermissionService.getDataPermission(authPO)`
251
+ - Mapper 签名携带 `@Param("authPO")` 和 `@Param("dataPermission")`
252
+ - XML `baseWhere` 末尾引入 `<include refid="net.xnzn.core.report.statistics.common.mapper.ReportDataPermissionMapper.dataPermission"/>`
253
+
254
+ ---
255
+
256
+ ## 七、查询接口标准模式(三并行 CompletableFuture)
257
+
258
+ ```java
259
+ public PageVO<XxxVO> pageSummary(XxxParam param) {
260
+ CompletableFuture<Long> countF = CompletableFuture.supplyAsync(() -> mapper.selectCount(param));
261
+ CompletableFuture<List<XxxVO>> listF = CompletableFuture.supplyAsync(() -> mapper.selectPageList(param));
262
+ CompletableFuture<XxxTotalVO> totalF = CompletableFuture.supplyAsync(() -> mapper.selectTotal(param));
263
+ CompletableFuture.allOf(countF, listF, totalF).join();
264
+
265
+ PageVO<XxxVO> pageVO = new PageVO<>();
266
+ pageVO.setTotal(countF.join());
267
+ pageVO.setList(listF.join());
268
+ pageVO.setTotalLine(totalF.join());
269
+ return pageVO;
270
+ }
271
+ ```
272
+
273
+ ---
274
+
275
+ ## 八、账户流水汇总报表
276
+
277
+ 实现 `ReportAccountConsumeService` 接口(类似 ReportOrderConsumeService),数据源为 `report_account_flow`。
278
+
279
+ ```sql
280
+ -- 按钱包类型统计消费/退款
281
+ SELECT d.wallet_id,
282
+ SUM(CASE WHEN d.flow_type = 110 THEN ABS(d.amount) ELSE 0 END) AS consume_amount,
283
+ SUM(CASE WHEN d.flow_type = 130 THEN d.amount ELSE 0 END) AS refund_amount
284
+ FROM report_account_flow f
285
+ JOIN report_account_flow_detail d ON f.flow_id = d.flow_id
286
+ WHERE f.pay_time BETWEEN #{startTime} AND #{endTime}
287
+ GROUP BY d.wallet_id
288
+ ```
289
+
290
+ 流水退款:`flowType=110` amount 为负,`flowType=130` amount 为正,直接 SUM 即净额。
291
+
292
+ ---
293
+
294
+ ## 九、MySQL only_full_group_by 规范(必须遵守)
295
+
296
+ > 生产环境 MySQL 开启了 `sql_mode=only_full_group_by`,所有报表 SQL 必须满足此规则,否则报 `BadSqlGrammarException`。
297
+
298
+ ### 核心规则
299
+
300
+ **SELECT 中所有非聚合字段,必须出现在 GROUP BY 中,且表达式必须完全一致。**
301
+
302
+ ### 常见错误:GROUP BY 表达式与 SELECT 不一致
303
+
304
+ ```sql
305
+ -- ❌ 错误:SELECT 用 DATE_FORMAT,GROUP BY 用 DATE
306
+ SELECT
307
+ DATE_FORMAT(atr.trade_time, '%Y-%m-%d') AS statisticDate,
308
+ SUM(atr.amount) AS totalAmount
309
+ FROM acc_trade atr
310
+ GROUP BY DATE(atr.trade_time) -- ❌ 表达式不同,触发 only_full_group_by 报错
311
+ ORDER BY DATE(atr.trade_time) ASC
312
+
313
+ -- ✅ 正确:GROUP BY 与 SELECT 使用完全相同的表达式
314
+ SELECT
315
+ DATE_FORMAT(atr.trade_time, '%Y-%m-%d') AS statisticDate,
316
+ SUM(atr.amount) AS totalAmount
317
+ FROM acc_trade atr
318
+ GROUP BY DATE_FORMAT(atr.trade_time, '%Y-%m-%d') -- ✅ 与 SELECT 一致
319
+ ORDER BY DATE_FORMAT(atr.trade_time, '%Y-%m-%d') ASC
320
+ ```
321
+
322
+ ### 常见错误:SELECT 包含非聚合字段未加入 GROUP BY
323
+
324
+ ```sql
325
+ -- ❌ 错误:canteen_name 未在 GROUP BY 中
326
+ SELECT
327
+ DATE(pay_time) AS orderDate,
328
+ canteen_name, -- ❌ 非聚合字段,未在 GROUP BY
329
+ SUM(real_amount) AS netAmount
330
+ FROM report_order_info
331
+ GROUP BY DATE(pay_time), canteen_id
332
+
333
+ -- ✅ 正确:所有非聚合字段都加入 GROUP BY
334
+ SELECT
335
+ DATE(pay_time) AS orderDate,
336
+ canteen_name,
337
+ SUM(real_amount) AS netAmount
338
+ FROM report_order_info
339
+ GROUP BY DATE(pay_time), canteen_id, canteen_name -- ✅ 包含 canteen_name
340
+ ```
341
+
342
+ ### fix SQL 中的正确写法
343
+
344
+ ```xml
345
+ <insert id="initFix">
346
+ INSERT INTO report_sum_xxx (id, statistic_date, canteen_id, canteen_name,
347
+ order_count, consume_amount, net_amount)
348
+ SELECT
349
+ #{id},
350
+ DATE(pay_time), -- SELECT 用 DATE(pay_time)
351
+ canteen_id,
352
+ canteen_name,
353
+ COUNT(*),
354
+ SUM(CASE WHEN consume_type = 1 THEN real_amount ELSE 0 END),
355
+ SUM(real_amount)
356
+ FROM report_order_info
357
+ WHERE pay_time BETWEEN #{startTime} AND #{endTime}
358
+ GROUP BY DATE(pay_time), canteen_id, canteen_name -- ✅ 与 SELECT 完全一致
359
+ </insert>
360
+ ```
361
+
362
+ ### 开发检查项
363
+
364
+ - [ ] SELECT 每个非聚合字段,在 GROUP BY 中都有对应
365
+ - [ ] GROUP BY 的表达式与 SELECT 中的**完全一致**(`DATE_FORMAT(x, 'Y-m-d')` ≠ `DATE(x)`)
366
+ - [ ] ORDER BY 也使用相同表达式(保持一致)
367
+
368
+ ---
369
+
370
+ ## 十、开发检查清单
371
+
372
+ ### 建表
373
+ - [ ] 分组维度字段 + 金额汇总字段 + 审计字段(crby/crtime/upby/uptime/del_flag),无 tenant_id
374
+
375
+ ### 实现
376
+ - [ ] 实现 `ReportOrderConsumeService`(或 Account 版本),设置 `getOrder()`
377
+ - [ ] `batchConsume()` — 分组 → 查存量 → 累加/新建
378
+ - [ ] `fix()` — 先删后插,从基础表重新聚合
379
+
380
+ ### 退款处理
381
+ - [ ] 选择退款计算方式(直接 SUM 负数 / 分别统计 / 排除全退)
382
+ - [ ] 菜品退款关注 `detailState` 和 `goodsRefundNum`
383
+
384
+ ### 查询接口
385
+ - [ ] 分页 + 合计行(PageVO + TotalVO)+ 三并行 CompletableFuture
386
+
387
+ ### SQL 合规(only_full_group_by)
388
+ - [ ] SELECT 非聚合字段全部在 GROUP BY 中
389
+ - [ ] GROUP BY 表达式与 SELECT 完全一致
390
+
391
+ ---
392
+
393
+ ## 十一、关键代码位置
394
+
395
+ | 类型 | 路径 |
396
+ |------|------|
397
+ | 下单 MQ 监听器 | `sys-canteen/.../report/statistics/config/mq/ReportOrderMQListener.java` |
398
+ | 退款 MQ 监听器 | `sys-canteen/.../report/statistics/config/mq/ReportOrderRefundMQListener.java` |
399
+ | 汇总消费调度 | `sys-canteen/.../report/statistics/config/mq/service/ReportConsumerService.java` |
400
+ | ConsumeService 接口 | `sys-canteen/.../report/statistics/config/mq/ReportOrderConsumeService.java` |
401
+ | 食堂汇总参考实现 | `sys-canteen/.../report/statistics/order/summary/service/ReportSumCanteenService.java` |
402
+ | Fix Controller | `sys-canteen/.../report/statistics/order/fix/controller/ReportFixController.java` |
403
+ | ReportOrderInfo 实体 | `sys-canteen/.../report/statistics/order/basic/model/ReportOrderInfo.java` |
404
+ | AccTradeTypeEnum | `sys-canteen/.../account/v3/constants/AccTradeTypeEnum.java` |
405
+ | AccWalletIdEnum | `sys-canteen/.../account/v3/constants/AccWalletIdEnum.java` |
406
+
407
+ ---
408
+
409
+ ## 注意
410
+
411
+ - CRUD 开发(非报表)请使用 `leniu-crud-development`
412
+ - MyBatis XML 编写规范请使用 `leniu-java-mybatis`
413
+ - 报表入参设计请使用 `leniu-java-report-query-param`
414
+ - 合计行实现请使用 `leniu-java-total-line`
415
+ - 餐次过滤请使用 `leniu-mealtime`