即时销售报表
This commit is contained in:
1 parent
daa318f320
commit
aadefd2981
8 files changed
+1999
-537
No files matched your search
+1
@@ -320,6 +320,7 @@ public class VisitorsServiceImpl extends ServiceImpl<VisitorsMapper, VisitorsEnt
|
||||
@Override
|
||||
@Transactional
|
||||
public Object updateCheckStatus(Long id, String checkStatus) {
|
||||
//todo 需要判断数据权限,防止水平越权
|
||||
VisitorsEntity dataVisitors = baseMapper.selectById(id);
|
||||
if (dataVisitors == null) {
|
||||
throw new ServiceException("数据不存在");
|
||||
|
||||
@@ -1,229 +0,0 @@
|
||||
# 商品SPU口味功能说明
|
||||
|
||||
## 概述
|
||||
|
||||
本功能为商品SPU添加了口味集合管理能力,支持在新增和编辑商品时管理多种口味,每种口味可以独立设置库存数量。
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 1. 新增商品SPU时的口味处理
|
||||
- 支持同时添加多个口味
|
||||
- 每个口味自动关联到对应的SPU
|
||||
- 自动设置默认库存为0(如果未指定)
|
||||
- 口味数据与商品SPU在同一事务中保存
|
||||
|
||||
### 2. 编辑商品SPU时的口味处理
|
||||
- **新增口味**:ID为null的口味会被识别为新增
|
||||
- **更新口味**:ID不为null的口味会被识别为更新
|
||||
- **删除口味**:提交数据中不包含的口味会被自动删除
|
||||
- 支持混合操作(同时新增、更新、删除)
|
||||
|
||||
### 3. 数据完整性
|
||||
- 自动维护口味与SPU的关联关系
|
||||
- 支持级联删除(删除SPU时自动删除相关口味)
|
||||
- 事务保证数据一致性
|
||||
|
||||
## 数据结构
|
||||
|
||||
### SmErpTasteStockEntity(口味库存实体)
|
||||
```java
|
||||
public class SmErpTasteStockEntity {
|
||||
private Long id; // 主键ID
|
||||
private Long spuId; // 关联的SPU ID
|
||||
private String tasteName; // 口味名称
|
||||
private Double stock; // 该口味的库存数量
|
||||
private LocalDateTime createTime; // 创建时间
|
||||
private LocalDateTime updateTime; // 更新时间
|
||||
}
|
||||
```
|
||||
|
||||
### SmErpSpuEntity(商品SPU实体)
|
||||
```java
|
||||
public class SmErpSpuEntity {
|
||||
// ... 其他字段 ...
|
||||
|
||||
@Schema(description="口味集合")
|
||||
@TableField(exist = false)
|
||||
private List<SmErpTasteStockEntity> smErpTasteStockEntityList;
|
||||
}
|
||||
```
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. 新增商品SPU(包含口味)
|
||||
|
||||
```java
|
||||
@PostMapping
|
||||
public R save(@RequestBody SmErpSpuEntity supermarketSpu) {
|
||||
supermarketSpu.setDeptId(SecurityUtils.getUser().getDeptId());
|
||||
return smErpSpuService.saveSpu(supermarketSpu);
|
||||
}
|
||||
```
|
||||
|
||||
**请求示例:**
|
||||
```json
|
||||
{
|
||||
"productCode": "SPU001",
|
||||
"productName": "果汁饮料",
|
||||
"spec": "500ml",
|
||||
"status": "0",
|
||||
"stock": 100.0,
|
||||
"smErpTasteStockEntityList": [
|
||||
{
|
||||
"tasteName": "原味",
|
||||
"stock": 50.0
|
||||
},
|
||||
{
|
||||
"tasteName": "草莓味",
|
||||
"stock": 30.0
|
||||
},
|
||||
{
|
||||
"tasteName": "橙子味",
|
||||
"stock": 20.0
|
||||
}
|
||||
],
|
||||
"smErpUnitEntityList": [
|
||||
{
|
||||
"unit": "瓶",
|
||||
"packageNum": 1,
|
||||
"isBase": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 编辑商品SPU(修改口味)
|
||||
|
||||
```java
|
||||
@PutMapping
|
||||
public R updateById(@RequestBody SmErpSpuEntity supermarketSpu) {
|
||||
return smErpSpuService.updateSpuById(supermarketSpu);
|
||||
}
|
||||
```
|
||||
|
||||
**请求示例(更新现有口味,新增口味,删除口味):**
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"productCode": "SPU001",
|
||||
"productName": "果汁饮料",
|
||||
"spec": "500ml",
|
||||
"status": "0",
|
||||
"stock": 120.0,
|
||||
"smErpTasteStockEntityList": [
|
||||
{
|
||||
"id": 1, // 有ID表示更新
|
||||
"tasteName": "原味",
|
||||
"stock": 60.0 // 更新库存
|
||||
},
|
||||
{
|
||||
"id": 2, // 有ID表示更新
|
||||
"tasteName": "草莓味",
|
||||
"stock": 40.0 // 更新库存
|
||||
},
|
||||
{
|
||||
"tasteName": "葡萄味", // 无ID表示新增
|
||||
"stock": 20.0
|
||||
}
|
||||
// 不包含巧克力味,会被自动删除
|
||||
],
|
||||
"smErpUnitEntityList": [
|
||||
{
|
||||
"id": 1,
|
||||
"unit": "瓶",
|
||||
"packageNum": 1,
|
||||
"isBase": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 业务逻辑
|
||||
|
||||
### 新增逻辑
|
||||
1. 验证商品基本信息
|
||||
2. 保存商品SPU主表
|
||||
3. 创建库存记录
|
||||
4. **处理口味集合**:
|
||||
- 遍历口味列表
|
||||
- 设置SPU ID关联
|
||||
- 设置默认库存(如果未指定)
|
||||
- 批量保存口味数据
|
||||
5. 处理包装单位
|
||||
6. 处理辅助条码
|
||||
|
||||
### 编辑逻辑
|
||||
1. 更新商品SPU主表
|
||||
2. 更新库存记录
|
||||
3. 处理包装单位(新增/更新/删除)
|
||||
4. 处理辅助条码(新增/更新/删除)
|
||||
5. **处理口味集合**:
|
||||
- 分离新增和更新的口味
|
||||
- 新增口味:设置SPU ID,设置默认库存
|
||||
- 更新口味:保持ID,更新数据
|
||||
- 删除口味:计算需要删除的口味ID,批量删除
|
||||
|
||||
## 注意事项
|
||||
|
||||
### 1. 数据一致性
|
||||
- 所有操作都在事务中执行
|
||||
- 删除操作会级联删除相关数据
|
||||
- 口味ID必须与SPU ID匹配
|
||||
|
||||
### 2. 性能考虑
|
||||
- 使用批量操作提高性能
|
||||
- 避免N+1查询问题
|
||||
- 合理设置批量大小
|
||||
|
||||
### 3. 业务规则
|
||||
- 口味名称在同一SPU下必须唯一
|
||||
- 库存数量不能为负数
|
||||
- 删除SPU时自动清理相关口味
|
||||
|
||||
### 4. 错误处理
|
||||
- 提供详细的错误信息
|
||||
- 支持事务回滚
|
||||
- 记录操作日志
|
||||
|
||||
## 测试用例
|
||||
|
||||
项目包含完整的测试用例,覆盖以下场景:
|
||||
- 新增商品SPU(包含口味)
|
||||
- 编辑商品SPU(更新口味)
|
||||
- 编辑商品SPU(删除口味)
|
||||
- 混合操作(新增+更新+删除)
|
||||
|
||||
运行测试:
|
||||
```bash
|
||||
mvn test -Dtest=SmErpSpuServiceTest
|
||||
```
|
||||
|
||||
## 扩展功能
|
||||
|
||||
### 1. 口味库存管理
|
||||
- 支持口味级别的库存调整
|
||||
- 库存预警功能
|
||||
- 库存盘点功能
|
||||
|
||||
### 2. 口味统计分析
|
||||
- 口味销售统计
|
||||
- 口味库存分析
|
||||
- 口味偏好分析
|
||||
|
||||
### 3. 口味权限控制
|
||||
- 基于角色的口味管理权限
|
||||
- 口味数据访问控制
|
||||
- 操作审计日志
|
||||
|
||||
## 相关文件
|
||||
|
||||
- `SmErpSpuServiceImpl.java` - 服务实现类
|
||||
- `SmErpTasteStockEntity.java` - 口味实体类
|
||||
- `SmErpSpuEntity.java` - 商品SPU实体类
|
||||
- `SmErpSpuServiceTest.java` - 测试类
|
||||
- `README_TASTE_FEATURE.md` - 本文档
|
||||
|
||||
## 技术支持
|
||||
|
||||
如有问题或建议,请联系开发团队或提交Issue。
|
||||
|
||||
@@ -1,308 +0,0 @@
|
||||
# 下拉选择接口使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
为了方便后台管理系统使用下拉选择组件,我们提供了一系列统一的下拉选择接口。这些接口返回标准化的选项数据格式,便于前端组件使用。
|
||||
|
||||
## 统一返回格式
|
||||
|
||||
所有下拉选择接口都返回以下格式的数据:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"msg": "操作成功",
|
||||
"data": [
|
||||
{
|
||||
"value": 1,
|
||||
"label": "选项名称",
|
||||
"disabled": false,
|
||||
"extra": "扩展信息"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 字段说明
|
||||
- `value`: 选项值,可以是数字、字符串等类型
|
||||
- `label`: 选项显示名称
|
||||
- `disabled`: 是否禁用,默认为false
|
||||
- `extra`: 扩展信息,可以包含额外的数据
|
||||
|
||||
## 接口列表
|
||||
|
||||
### 1. 商户下拉选择
|
||||
|
||||
#### 接口地址
|
||||
```
|
||||
GET /selectOption/merchant?keyWord=关键字
|
||||
```
|
||||
|
||||
#### 请求参数
|
||||
- `keyWord` (可选): 关键字,支持商户名称和手机号模糊查询
|
||||
|
||||
#### 权限要求
|
||||
需要 `supermarket_mallMerchant_view` 权限
|
||||
|
||||
#### 使用示例
|
||||
```
|
||||
# 获取所有商户
|
||||
GET /selectOption/merchant
|
||||
|
||||
# 按商户名称搜索
|
||||
GET /selectOption/merchant?keyWord=农场
|
||||
|
||||
# 按手机号搜索
|
||||
GET /selectOption/merchant?keyWord=138
|
||||
|
||||
# 按关键字搜索(商户名称或手机号)
|
||||
GET /selectOption/merchant?keyWord=张三
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"msg": "操作成功",
|
||||
"data": [
|
||||
{
|
||||
"value": 1,
|
||||
"label": "张三农场",
|
||||
"disabled": false,
|
||||
"extra": "13800138000"
|
||||
},
|
||||
{
|
||||
"value": 2,
|
||||
"label": "李四果园",
|
||||
"disabled": false,
|
||||
"extra": "13900139000"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 分类下拉选择
|
||||
|
||||
#### 接口地址
|
||||
```
|
||||
GET /selectOption/category
|
||||
```
|
||||
|
||||
#### 权限要求
|
||||
需要 `supermarket_mallCategory_view` 权限
|
||||
|
||||
#### 响应示例
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"msg": "操作成功",
|
||||
"data": [
|
||||
{
|
||||
"value": 1,
|
||||
"label": "水果",
|
||||
"disabled": false,
|
||||
"extra": 0
|
||||
},
|
||||
{
|
||||
"value": 2,
|
||||
"label": "蔬菜",
|
||||
"disabled": false,
|
||||
"extra": 0
|
||||
},
|
||||
{
|
||||
"value": 3,
|
||||
"label": "苹果",
|
||||
"disabled": false,
|
||||
"extra": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 分类下拉选择(树形结构)
|
||||
|
||||
#### 接口地址
|
||||
```
|
||||
GET /selectOption/category/tree
|
||||
```
|
||||
|
||||
#### 权限要求
|
||||
需要 `supermarket_mallCategory_view` 权限
|
||||
|
||||
#### 响应示例
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"msg": "操作成功",
|
||||
"data": [
|
||||
{
|
||||
"value": 1,
|
||||
"label": "水果",
|
||||
"disabled": false,
|
||||
"extra": 0
|
||||
},
|
||||
{
|
||||
"value": 3,
|
||||
"label": " └─ 苹果",
|
||||
"disabled": false,
|
||||
"extra": 1
|
||||
},
|
||||
{
|
||||
"value": 2,
|
||||
"label": "蔬菜",
|
||||
"disabled": false,
|
||||
"extra": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 传统接口
|
||||
|
||||
除了统一的下拉选择接口,原有的控制器也提供了下拉选择接口:
|
||||
|
||||
### 商户下拉选择(传统接口)
|
||||
```
|
||||
GET /mallMerchant/selectList
|
||||
```
|
||||
|
||||
### 分类下拉选择(传统接口)
|
||||
```
|
||||
GET /mallCategory/selectList
|
||||
```
|
||||
|
||||
## 前端使用示例
|
||||
|
||||
### Vue.js + Element Plus
|
||||
|
||||
```javascript
|
||||
// 获取商户下拉选择
|
||||
async getMerchantOptions(keyword = '') {
|
||||
try {
|
||||
const params = keyword ? { keyWord: keyword } : {};
|
||||
const response = await this.$http.get('/selectOption/merchant', { params });
|
||||
this.merchantOptions = response.data.data.map(item => ({
|
||||
value: item.value,
|
||||
label: item.label,
|
||||
disabled: item.disabled
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error('获取商户选项失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索商户
|
||||
async searchMerchants(keyword) {
|
||||
await this.getMerchantOptions(keyword);
|
||||
}
|
||||
|
||||
// 获取分类下拉选择
|
||||
async getCategoryOptions() {
|
||||
try {
|
||||
const response = await this.$http.get('/selectOption/category');
|
||||
this.categoryOptions = response.data.data.map(item => ({
|
||||
value: item.value,
|
||||
label: item.label,
|
||||
disabled: item.disabled
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error('获取分类选项失败:', error);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### HTML模板
|
||||
|
||||
```html
|
||||
<template>
|
||||
<div>
|
||||
<!-- 商户选择(支持搜索) -->
|
||||
<el-select
|
||||
v-model="form.merchantId"
|
||||
placeholder="请选择商户"
|
||||
filterable
|
||||
remote
|
||||
:remote-method="searchMerchants"
|
||||
:loading="merchantLoading">
|
||||
<el-option
|
||||
v-for="item in merchantOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
:disabled="item.disabled">
|
||||
<span style="float: left">{{ item.label }}</span>
|
||||
<span style="float: right; color: #8492a6; font-size: 13px">{{ item.extra }}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
|
||||
<!-- 分类选择 -->
|
||||
<el-select v-model="form.categoryId" placeholder="请选择分类">
|
||||
<el-option
|
||||
v-for="item in categoryOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
:disabled="item.disabled">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
merchantId: null,
|
||||
categoryId: null
|
||||
},
|
||||
merchantOptions: [],
|
||||
categoryOptions: [],
|
||||
merchantLoading: false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getMerchantOptions();
|
||||
this.getCategoryOptions();
|
||||
},
|
||||
methods: {
|
||||
// 搜索商户
|
||||
async searchMerchants(keyword) {
|
||||
this.merchantLoading = true;
|
||||
try {
|
||||
await this.getMerchantOptions(keyword);
|
||||
} finally {
|
||||
this.merchantLoading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
## 扩展说明
|
||||
|
||||
### 添加新的下拉选择类型
|
||||
|
||||
如果需要添加新的下拉选择类型,可以:
|
||||
|
||||
1. 在 `SelectOptionService` 中添加新的方法
|
||||
2. 在 `SelectOptionController` 中添加对应的接口
|
||||
3. 按照统一的格式返回数据
|
||||
|
||||
### 自定义扩展信息
|
||||
|
||||
`extra` 字段可以包含任何额外的信息,例如:
|
||||
- 联系电话
|
||||
- 父级ID
|
||||
- 状态信息
|
||||
- 其他业务相关数据
|
||||
|
||||
前端可以根据需要解析和使用这些扩展信息。
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 所有接口都需要相应的权限验证
|
||||
2. 只返回启用状态的数据
|
||||
3. 数据按照指定的排序规则返回
|
||||
4. 建议前端对接口返回的数据进行缓存,避免频繁请求
|
||||
@@ -0,0 +1,226 @@
|
||||
# 出入库记录totalAmount字段SQL修复脚本使用指南
|
||||
|
||||
## 问题描述
|
||||
|
||||
由于历史数据问题,线上存在大量出入库记录的主表 `totalAmount` 字段与明细表数据不一致的脏数据。这些数据在编辑时会导致总金额显示不准确。
|
||||
|
||||
## 解决方案
|
||||
|
||||
使用SQL脚本在数据库层面直接修复数据不一致问题。
|
||||
|
||||
## SQL修复脚本
|
||||
|
||||
### 执行文件
|
||||
- **脚本文件**: `fix_inventory_total_amount.sql`
|
||||
- **说明文档**: `SQL_FIX_SCRIPT_GUIDE.md`
|
||||
|
||||
### 执行步骤
|
||||
|
||||
#### 1. 查看问题数据
|
||||
|
||||
```sql
|
||||
-- 查看数据不一致的记录数量
|
||||
SELECT
|
||||
'数据不一致记录统计' as description,
|
||||
COUNT(*) as inconsistent_count
|
||||
FROM sm_erp_inventory_records r
|
||||
WHERE r.total_amount != (
|
||||
SELECT COALESCE(SUM(i.total_amount), 0)
|
||||
FROM sm_erp_inventory_records_item i
|
||||
WHERE i.inventory_id = r.id
|
||||
);
|
||||
```
|
||||
|
||||
#### 2. 查看具体的不一致记录详情(前10条)
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
r.id,
|
||||
r.inventory_code,
|
||||
r.inventory_date,
|
||||
r.total_amount as main_table_amount,
|
||||
COALESCE(SUM(i.total_amount), 0) as calculated_amount,
|
||||
(r.total_amount - COALESCE(SUM(i.total_amount), 0)) as difference
|
||||
FROM sm_erp_inventory_records r
|
||||
LEFT JOIN sm_erp_inventory_records_item i ON i.inventory_id = r.id
|
||||
GROUP BY r.id, r.inventory_code, r.inventory_date, r.total_amount
|
||||
HAVING r.total_amount != COALESCE(SUM(i.total_amount), 0)
|
||||
ORDER BY r.id
|
||||
LIMIT 10;
|
||||
```
|
||||
|
||||
#### 3. 创建备份表
|
||||
|
||||
```sql
|
||||
-- 备份原始数据
|
||||
CREATE TABLE IF NOT EXISTS sm_erp_inventory_records_backup_20250127 AS
|
||||
SELECT * FROM sm_erp_inventory_records;
|
||||
|
||||
-- 创建备份表的索引
|
||||
CREATE INDEX IF NOT EXISTS idx_backup_inventory_id ON sm_erp_inventory_records_backup_20250127(id);
|
||||
```
|
||||
|
||||
#### 4. 执行修复
|
||||
|
||||
```sql
|
||||
-- 修复数据不一致的记录
|
||||
UPDATE sm_erp_inventory_records r
|
||||
SET total_amount = (
|
||||
SELECT COALESCE(SUM(i.total_amount), 0)
|
||||
FROM sm_erp_inventory_records_item i
|
||||
WHERE i.inventory_id = r.id
|
||||
)
|
||||
WHERE r.total_amount != (
|
||||
SELECT COALESCE(SUM(i.total_amount), 0)
|
||||
FROM sm_erp_inventory_records_item i
|
||||
WHERE i.inventory_id = r.id
|
||||
);
|
||||
```
|
||||
|
||||
#### 5. 验证修复结果
|
||||
|
||||
```sql
|
||||
-- 检查是否还有不一致的记录
|
||||
SELECT
|
||||
'修复后数据检查' as description,
|
||||
COUNT(*) as remaining_inconsistent_count
|
||||
FROM sm_erp_inventory_records r
|
||||
WHERE r.total_amount != (
|
||||
SELECT COALESCE(SUM(i.total_amount), 0)
|
||||
FROM sm_erp_inventory_records_item i
|
||||
WHERE i.inventory_id = r.id
|
||||
);
|
||||
```
|
||||
|
||||
#### 6. 生成修复报告
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
'修复报告' as report_type,
|
||||
COUNT(*) as total_records,
|
||||
SUM(CASE
|
||||
WHEN r.total_amount = COALESCE(SUM(i.total_amount), 0) THEN 1
|
||||
ELSE 0
|
||||
END) as consistent_records,
|
||||
SUM(CASE
|
||||
WHEN r.total_amount != COALESCE(SUM(i.total_amount), 0) THEN 1
|
||||
ELSE 0
|
||||
END) as inconsistent_records,
|
||||
ROUND(
|
||||
SUM(CASE
|
||||
WHEN r.total_amount = COALESCE(SUM(i.total_amount), 0) THEN 1
|
||||
ELSE 0
|
||||
END) * 100.0 / COUNT(*), 2
|
||||
) as consistency_rate
|
||||
FROM sm_erp_inventory_records r
|
||||
LEFT JOIN sm_erp_inventory_records_item i ON i.inventory_id = r.id
|
||||
GROUP BY r.id, r.total_amount;
|
||||
```
|
||||
|
||||
## 大数据量分批执行
|
||||
|
||||
如果数据量很大,建议分批执行避免锁表时间过长:
|
||||
|
||||
```sql
|
||||
-- 分批修复(每次1000条)
|
||||
UPDATE sm_erp_inventory_records r
|
||||
SET total_amount = (
|
||||
SELECT COALESCE(SUM(i.total_amount), 0)
|
||||
FROM sm_erp_inventory_records_item i
|
||||
WHERE i.inventory_id = r.id
|
||||
)
|
||||
WHERE r.id BETWEEN 1 AND 1000
|
||||
AND r.total_amount != (
|
||||
SELECT COALESCE(SUM(i.total_amount), 0)
|
||||
FROM sm_erp_inventory_records_item i
|
||||
WHERE i.inventory_id = r.id
|
||||
);
|
||||
```
|
||||
|
||||
## 数据一致性检查视图
|
||||
|
||||
创建视图方便后续检查数据一致性:
|
||||
|
||||
```sql
|
||||
-- 创建数据一致性检查视图
|
||||
CREATE OR REPLACE VIEW v_inventory_data_consistency AS
|
||||
SELECT
|
||||
r.id,
|
||||
r.inventory_code,
|
||||
r.inventory_date,
|
||||
r.total_amount as main_table_amount,
|
||||
COALESCE(SUM(i.total_amount), 0) as calculated_amount,
|
||||
(r.total_amount - COALESCE(SUM(i.total_amount), 0)) as difference,
|
||||
CASE
|
||||
WHEN r.total_amount = COALESCE(SUM(i.total_amount), 0) THEN 'CONSISTENT'
|
||||
ELSE 'INCONSISTENT'
|
||||
END as status
|
||||
FROM sm_erp_inventory_records r
|
||||
LEFT JOIN sm_erp_inventory_records_item i ON i.inventory_id = r.id
|
||||
GROUP BY r.id, r.inventory_code, r.inventory_date, r.total_amount;
|
||||
|
||||
-- 查询不一致的记录
|
||||
SELECT * FROM v_inventory_data_consistency WHERE status = 'INCONSISTENT' LIMIT 10;
|
||||
```
|
||||
|
||||
## 执行建议
|
||||
|
||||
### 推荐执行顺序
|
||||
|
||||
1. **测试环境验证**
|
||||
- 先在测试环境执行脚本
|
||||
- 验证修复逻辑正确性
|
||||
|
||||
2. **生产环境执行**
|
||||
- 选择业务低峰期执行
|
||||
- 建议分批执行大数据量
|
||||
|
||||
3. **验证结果**
|
||||
- 检查修复后的数据一致性
|
||||
- 验证业务功能正常
|
||||
|
||||
### 注意事项
|
||||
|
||||
1. **备份数据**
|
||||
- 执行前务必备份相关数据表
|
||||
- 保留备份直到确认修复成功
|
||||
|
||||
2. **业务影响**
|
||||
- 建议在业务低峰期执行
|
||||
- 避免在执行过程中进行数据编辑操作
|
||||
|
||||
3. **性能考虑**
|
||||
- 大数据量建议分批执行
|
||||
- 监控数据库性能指标
|
||||
|
||||
4. **回滚方案**
|
||||
- 如果出现问题,可以从备份表恢复数据
|
||||
```sql
|
||||
-- 回滚示例
|
||||
UPDATE sm_erp_inventory_records r
|
||||
SET total_amount = b.total_amount
|
||||
FROM sm_erp_inventory_records_backup_20250127 b
|
||||
WHERE r.id = b.id;
|
||||
```
|
||||
|
||||
## 清理备份表
|
||||
|
||||
确认修复成功后,可以清理备份表:
|
||||
|
||||
```sql
|
||||
-- 注意:请确认修复成功后再执行删除操作
|
||||
DROP TABLE IF EXISTS sm_erp_inventory_records_backup_20250127;
|
||||
```
|
||||
|
||||
## 后续维护
|
||||
|
||||
建议定期检查数据一致性:
|
||||
|
||||
```sql
|
||||
-- 定期检查脚本
|
||||
SELECT COUNT(*) as inconsistent_count
|
||||
FROM v_inventory_data_consistency
|
||||
WHERE status = 'INCONSISTENT';
|
||||
```
|
||||
|
||||
如果发现新的不一致数据,可以重新执行修复脚本。
|
||||
@@ -0,0 +1,161 @@
|
||||
-- 出入库记录totalAmount字段修复SQL脚本
|
||||
-- 用途:修复由于历史数据问题导致的主表totalAmount字段与明细表数据不一致的问题
|
||||
-- 作者:pjcloud
|
||||
-- 日期:2025-01-27
|
||||
|
||||
-- ============================================
|
||||
-- 1. 查看数据不一致的记录数量
|
||||
-- ============================================
|
||||
SELECT
|
||||
'数据不一致记录统计' as description,
|
||||
COUNT(*) as inconsistent_count
|
||||
FROM sm_erp_inventory_records r
|
||||
WHERE r.total_amount != (
|
||||
SELECT COALESCE(SUM(i.total_amount), 0)
|
||||
FROM sm_erp_inventory_records_item i
|
||||
WHERE i.inventory_id = r.id
|
||||
);
|
||||
|
||||
-- ============================================
|
||||
-- 2. 查看具体的不一致记录详情(前10条)
|
||||
-- ============================================
|
||||
SELECT
|
||||
r.id,
|
||||
r.inventory_code,
|
||||
r.inventory_date,
|
||||
r.total_amount as main_table_amount,
|
||||
COALESCE(SUM(i.total_amount), 0) as calculated_amount,
|
||||
(r.total_amount - COALESCE(SUM(i.total_amount), 0)) as difference
|
||||
FROM sm_erp_inventory_records r
|
||||
LEFT JOIN sm_erp_inventory_records_item i ON i.inventory_id = r.id
|
||||
GROUP BY r.id, r.inventory_code, r.inventory_date, r.total_amount
|
||||
HAVING r.total_amount != COALESCE(SUM(i.total_amount), 0)
|
||||
ORDER BY r.id
|
||||
LIMIT 10;
|
||||
|
||||
-- ============================================
|
||||
-- 3. 备份原始数据(可选,建议执行)
|
||||
-- ============================================
|
||||
-- 创建备份表
|
||||
CREATE TABLE IF NOT EXISTS sm_erp_inventory_records_backup_20250127 AS
|
||||
SELECT * FROM sm_erp_inventory_records;
|
||||
|
||||
-- 创建备份表的索引
|
||||
CREATE INDEX IF NOT EXISTS idx_backup_inventory_id ON sm_erp_inventory_records_backup_20250127(id);
|
||||
|
||||
-- ============================================
|
||||
-- 4. 修复数据不一致的记录
|
||||
-- ============================================
|
||||
-- 方法1:使用UPDATE JOIN(推荐,性能较好)
|
||||
UPDATE sm_erp_inventory_records r
|
||||
SET total_amount = (
|
||||
SELECT COALESCE(SUM(i.total_amount), 0)
|
||||
FROM sm_erp_inventory_records_item i
|
||||
WHERE i.inventory_id = r.id
|
||||
)
|
||||
WHERE r.total_amount != (
|
||||
SELECT COALESCE(SUM(i.total_amount), 0)
|
||||
FROM sm_erp_inventory_records_item i
|
||||
WHERE i.inventory_id = r.id
|
||||
);
|
||||
|
||||
-- ============================================
|
||||
-- 5. 验证修复结果
|
||||
-- ============================================
|
||||
-- 检查是否还有不一致的记录
|
||||
SELECT
|
||||
'修复后数据检查' as description,
|
||||
COUNT(*) as remaining_inconsistent_count
|
||||
FROM sm_erp_inventory_records r
|
||||
WHERE r.total_amount != (
|
||||
SELECT COALESCE(SUM(i.total_amount), 0)
|
||||
FROM sm_erp_inventory_records_item i
|
||||
WHERE i.inventory_id = r.id
|
||||
);
|
||||
|
||||
-- ============================================
|
||||
-- 6. 生成修复报告
|
||||
-- ============================================
|
||||
SELECT
|
||||
'修复报告' as report_type,
|
||||
COUNT(*) as total_records,
|
||||
SUM(CASE
|
||||
WHEN r.total_amount = COALESCE(SUM(i.total_amount), 0) THEN 1
|
||||
ELSE 0
|
||||
END) as consistent_records,
|
||||
SUM(CASE
|
||||
WHEN r.total_amount != COALESCE(SUM(i.total_amount), 0) THEN 1
|
||||
ELSE 0
|
||||
END) as inconsistent_records,
|
||||
ROUND(
|
||||
SUM(CASE
|
||||
WHEN r.total_amount = COALESCE(SUM(i.total_amount), 0) THEN 1
|
||||
ELSE 0
|
||||
END) * 100.0 / COUNT(*), 2
|
||||
) as consistency_rate
|
||||
FROM sm_erp_inventory_records r
|
||||
LEFT JOIN sm_erp_inventory_records_item i ON i.inventory_id = r.id
|
||||
GROUP BY r.id, r.total_amount;
|
||||
|
||||
-- ============================================
|
||||
-- 7. 清理备份表(可选,确认修复成功后执行)
|
||||
-- ============================================
|
||||
-- 注意:请确认修复成功后再执行删除操作
|
||||
-- DROP TABLE IF EXISTS sm_erp_inventory_records_backup_20250127;
|
||||
|
||||
-- ============================================
|
||||
-- 8. 创建数据一致性检查视图(可选)
|
||||
-- ============================================
|
||||
CREATE OR REPLACE VIEW v_inventory_data_consistency AS
|
||||
SELECT
|
||||
r.id,
|
||||
r.inventory_code,
|
||||
r.inventory_date,
|
||||
r.total_amount as main_table_amount,
|
||||
COALESCE(SUM(i.total_amount), 0) as calculated_amount,
|
||||
(r.total_amount - COALESCE(SUM(i.total_amount), 0)) as difference,
|
||||
CASE
|
||||
WHEN r.total_amount = COALESCE(SUM(i.total_amount), 0) THEN 'CONSISTENT'
|
||||
ELSE 'INCONSISTENT'
|
||||
END as status
|
||||
FROM sm_erp_inventory_records r
|
||||
LEFT JOIN sm_erp_inventory_records_item i ON i.inventory_id = r.id
|
||||
GROUP BY r.id, r.inventory_code, r.inventory_date, r.total_amount;
|
||||
|
||||
-- 查询数据一致性视图
|
||||
-- SELECT * FROM v_inventory_data_consistency WHERE status = 'INCONSISTENT' LIMIT 10;
|
||||
|
||||
-- ============================================
|
||||
-- 使用说明:
|
||||
-- ============================================
|
||||
/*
|
||||
1. 执行前建议:
|
||||
- 在测试环境先验证脚本
|
||||
- 备份相关数据表
|
||||
- 在业务低峰期执行
|
||||
|
||||
2. 执行步骤:
|
||||
- 先执行步骤1-2查看问题数据
|
||||
- 执行步骤3创建备份
|
||||
- 执行步骤4进行修复
|
||||
- 执行步骤5-6验证结果
|
||||
|
||||
3. 注意事项:
|
||||
- 脚本会修改数据,请谨慎执行
|
||||
- 建议分批次执行,避免锁表时间过长
|
||||
- 执行后请验证业务功能正常
|
||||
|
||||
4. 如果数据量很大,可以考虑分批执行:
|
||||
UPDATE sm_erp_inventory_records r
|
||||
SET total_amount = (
|
||||
SELECT COALESCE(SUM(i.total_amount), 0)
|
||||
FROM sm_erp_inventory_records_item i
|
||||
WHERE i.inventory_id = r.id
|
||||
)
|
||||
WHERE r.id BETWEEN 1 AND 1000
|
||||
AND r.total_amount != (
|
||||
SELECT COALESCE(SUM(i.total_amount), 0)
|
||||
FROM sm_erp_inventory_records_item i
|
||||
WHERE i.inventory_id = r.id
|
||||
);
|
||||
*/
|
||||
+262
@@ -0,0 +1,262 @@
|
||||
package com.bsd.pjcloud.supermarket.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.common.log.annotation.SysLog;
|
||||
import com.bsd.pjcloud.supermarket.dto.realtime.RealtimeSalesReportQueryDTO;
|
||||
import com.bsd.pjcloud.supermarket.service.RealtimeSalesReportService;
|
||||
import com.bsd.pjcloud.supermarket.vo.realtime.RealtimeSalesReportVO;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 即时销售报表控制器
|
||||
*
|
||||
* @author pjcloud
|
||||
* @date 2025-01-27 10:00:00
|
||||
*/
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping("/realtimeSalesReport")
|
||||
@Tag(description = "即时销售报表", name = "即时销售报表管理")
|
||||
@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)
|
||||
public class RealtimeSalesReportController {
|
||||
|
||||
private final RealtimeSalesReportService realtimeSalesReportService;
|
||||
|
||||
/**
|
||||
* 获取即时销售报表
|
||||
*
|
||||
* @param queryDTO 查询条件
|
||||
* @return 即时销售报表数据
|
||||
*/
|
||||
@Operation(summary = "获取即时销售报表", description = "获取完整的即时销售报表,包括销售汇总、畅销品排名、滞销品排名、客单价分析等")
|
||||
@GetMapping("/report")
|
||||
@PreAuthorize("@pms.hasPermission('supermarket_realtimeSalesReport_view')")
|
||||
public R<RealtimeSalesReportVO> getRealtimeSalesReport(
|
||||
@Parameter(description = "查询条件") RealtimeSalesReportQueryDTO queryDTO) {
|
||||
RealtimeSalesReportVO report = realtimeSalesReportService.getRealtimeSalesReport(queryDTO);
|
||||
return R.ok(report);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实时销售数据
|
||||
*
|
||||
* @param queryDTO 查询条件
|
||||
* @return 实时销售数据
|
||||
*/
|
||||
@Operation(summary = "获取实时销售数据", description = "获取实时销售数据,通常查询最近1小时的数据,按分钟更新")
|
||||
@GetMapping("/realtime")
|
||||
@PreAuthorize("@pms.hasPermission('supermarket_realtimeSalesReport_view')")
|
||||
public R<RealtimeSalesReportVO> getRealtimeSalesData(
|
||||
@Parameter(description = "查询条件") RealtimeSalesReportQueryDTO queryDTO) {
|
||||
RealtimeSalesReportVO report = realtimeSalesReportService.getRealtimeSalesData(queryDTO);
|
||||
return R.ok(report);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取畅销品排名
|
||||
*
|
||||
* @param page 分页参数
|
||||
* @param queryDTO 查询条件
|
||||
* @return 畅销品排名列表
|
||||
*/
|
||||
@Operation(summary = "获取畅销品排名", description = "获取畅销品排名,按销售额降序排列")
|
||||
@GetMapping("/topSellingProducts")
|
||||
@PreAuthorize("@pms.hasPermission('supermarket_realtimeSalesReport_view')")
|
||||
public R<Page<RealtimeSalesReportVO.ProductRankingVO>> getTopSellingProducts(
|
||||
@Parameter(description = "分页参数") Page<RealtimeSalesReportVO.ProductRankingVO> page,
|
||||
@Parameter(description = "查询条件") RealtimeSalesReportQueryDTO queryDTO) {
|
||||
Page<RealtimeSalesReportVO.ProductRankingVO> result =
|
||||
realtimeSalesReportService.getTopSellingProducts(page, queryDTO);
|
||||
return R.ok(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取滞销品排名
|
||||
*
|
||||
* @param page 分页参数
|
||||
* @param queryDTO 查询条件
|
||||
* @return 滞销品排名列表
|
||||
*/
|
||||
@Operation(summary = "获取滞销品排名", description = "获取滞销品排名,按销售额升序排列")
|
||||
@GetMapping("/slowSellingProducts")
|
||||
@PreAuthorize("@pms.hasPermission('supermarket_realtimeSalesReport_view')")
|
||||
public R<Page<RealtimeSalesReportVO.ProductRankingVO>> getSlowSellingProducts(
|
||||
@Parameter(description = "分页参数") Page<RealtimeSalesReportVO.ProductRankingVO> page,
|
||||
@Parameter(description = "查询条件") RealtimeSalesReportQueryDTO queryDTO) {
|
||||
Page<RealtimeSalesReportVO.ProductRankingVO> result =
|
||||
realtimeSalesReportService.getSlowSellingProducts(page, queryDTO);
|
||||
return R.ok(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取时段销售数据
|
||||
*
|
||||
* @param page 分页参数
|
||||
* @param queryDTO 查询条件
|
||||
* @return 时段销售数据列表
|
||||
*/
|
||||
@Operation(summary = "获取时段销售数据", description = "获取时段销售数据,支持按小时/天/月查询")
|
||||
@GetMapping("/timePeriodSales")
|
||||
@PreAuthorize("@pms.hasPermission('supermarket_realtimeSalesReport_view')")
|
||||
public R<Page<RealtimeSalesReportVO.TimePeriodSalesVO>> getTimePeriodSales(
|
||||
@Parameter(description = "分页参数") Page<RealtimeSalesReportVO.TimePeriodSalesVO> page,
|
||||
@Parameter(description = "查询条件") RealtimeSalesReportQueryDTO queryDTO) {
|
||||
Page<RealtimeSalesReportVO.TimePeriodSalesVO> result =
|
||||
realtimeSalesReportService.getTimePeriodSales(page, queryDTO);
|
||||
return R.ok(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客单价分析数据
|
||||
*
|
||||
* @param queryDTO 查询条件
|
||||
* @return 客单价分析数据
|
||||
*/
|
||||
@Operation(summary = "获取客单价分析数据", description = "获取客单价分析数据,包括平均客单价、最高最低客单价、分布区间等")
|
||||
@GetMapping("/customerAnalysis")
|
||||
@PreAuthorize("@pms.hasPermission('supermarket_realtimeSalesReport_view')")
|
||||
public R<RealtimeSalesReportVO.CustomerAnalysisVO> getCustomerAnalysis(
|
||||
@Parameter(description = "查询条件") RealtimeSalesReportQueryDTO queryDTO) {
|
||||
RealtimeSalesReportVO.CustomerAnalysisVO analysis =
|
||||
realtimeSalesReportService.getCustomerAnalysis(queryDTO);
|
||||
return R.ok(analysis);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取销售趋势数据
|
||||
*
|
||||
* @param page 分页参数
|
||||
* @param queryDTO 查询条件
|
||||
* @return 销售趋势数据列表
|
||||
*/
|
||||
@Operation(summary = "获取销售趋势数据", description = "获取销售趋势数据,用于图表展示")
|
||||
@GetMapping("/salesTrend")
|
||||
@PreAuthorize("@pms.hasPermission('supermarket_realtimeSalesReport_view')")
|
||||
public R<Page<RealtimeSalesReportVO.SalesTrendVO>> getSalesTrend(
|
||||
@Parameter(description = "分页参数") Page<RealtimeSalesReportVO.SalesTrendVO> page,
|
||||
@Parameter(description = "查询条件") RealtimeSalesReportQueryDTO queryDTO) {
|
||||
Page<RealtimeSalesReportVO.SalesTrendVO> result =
|
||||
realtimeSalesReportService.getSalesTrend(page, queryDTO);
|
||||
return R.ok(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新实时销售数据缓存
|
||||
*
|
||||
* @return 刷新结果
|
||||
*/
|
||||
@Operation(summary = "刷新实时销售数据缓存", description = "手动刷新实时销售数据缓存")
|
||||
@SysLog("刷新实时销售数据缓存")
|
||||
@PostMapping("/refresh")
|
||||
@PreAuthorize("@pms.hasPermission('supermarket_realtimeSalesReport_view')")
|
||||
public R<Boolean> refreshRealtimeData() {
|
||||
Boolean result = realtimeSalesReportService.refreshRealtimeData();
|
||||
return R.ok(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取今日销售汇总
|
||||
*
|
||||
* @return 今日销售汇总数据
|
||||
*/
|
||||
@Operation(summary = "获取今日销售汇总", description = "获取今日销售汇总数据,包括销售额、销售笔数、客单价等")
|
||||
@GetMapping("/todaySummary")
|
||||
@PreAuthorize("@pms.hasPermission('supermarket_realtimeSalesReport_view')")
|
||||
public R<RealtimeSalesReportVO.SalesSummaryVO> getTodaySummary() {
|
||||
RealtimeSalesReportQueryDTO queryDTO = new RealtimeSalesReportQueryDTO();
|
||||
queryDTO.setQueryType("day");
|
||||
queryDTO.setStartTime(java.time.LocalDateTime.now().withHour(0).withMinute(0).withSecond(0));
|
||||
queryDTO.setEndTime(java.time.LocalDateTime.now());
|
||||
|
||||
RealtimeSalesReportVO report = realtimeSalesReportService.getRealtimeSalesReport(queryDTO);
|
||||
return R.ok(report.getSalesSummary());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取本月销售汇总
|
||||
*
|
||||
* @return 本月销售汇总数据
|
||||
*/
|
||||
@Operation(summary = "获取本月销售汇总", description = "获取本月销售汇总数据")
|
||||
@GetMapping("/monthSummary")
|
||||
@PreAuthorize("@pms.hasPermission('supermarket_realtimeSalesReport_view')")
|
||||
public R<RealtimeSalesReportVO.SalesSummaryVO> getMonthSummary() {
|
||||
RealtimeSalesReportQueryDTO queryDTO = new RealtimeSalesReportQueryDTO();
|
||||
queryDTO.setQueryType("month");
|
||||
queryDTO.setStartTime(java.time.LocalDateTime.now().withDayOfMonth(1).withHour(0).withMinute(0).withSecond(0));
|
||||
queryDTO.setEndTime(java.time.LocalDateTime.now());
|
||||
|
||||
RealtimeSalesReportVO report = realtimeSalesReportService.getRealtimeSalesReport(queryDTO);
|
||||
return R.ok(report.getSalesSummary());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实时畅销品TOP10
|
||||
*
|
||||
* @return 实时畅销品TOP10
|
||||
*/
|
||||
@Operation(summary = "获取实时畅销品TOP10", description = "获取实时畅销品TOP10排名")
|
||||
@GetMapping("/top10Products")
|
||||
@PreAuthorize("@pms.hasPermission('supermarket_realtimeSalesReport_view')")
|
||||
public R<Page<RealtimeSalesReportVO.ProductRankingVO>> getTop10Products() {
|
||||
RealtimeSalesReportQueryDTO queryDTO = new RealtimeSalesReportQueryDTO();
|
||||
queryDTO.setQueryType("day");
|
||||
queryDTO.setStartTime(java.time.LocalDateTime.now().withHour(0).withMinute(0).withSecond(0));
|
||||
queryDTO.setEndTime(java.time.LocalDateTime.now());
|
||||
queryDTO.setTopLimit(10);
|
||||
|
||||
Page<RealtimeSalesReportVO.ProductRankingVO> page = new Page<>(1, 10);
|
||||
Page<RealtimeSalesReportVO.ProductRankingVO> result =
|
||||
realtimeSalesReportService.getTopSellingProducts(page, queryDTO);
|
||||
return R.ok(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实时滞销品TOP10
|
||||
*
|
||||
* @return 实时滞销品TOP10
|
||||
*/
|
||||
@Operation(summary = "获取实时滞销品TOP10", description = "获取实时滞销品TOP10排名")
|
||||
@GetMapping("/bottom10Products")
|
||||
@PreAuthorize("@pms.hasPermission('supermarket_realtimeSalesReport_view')")
|
||||
public R<Page<RealtimeSalesReportVO.ProductRankingVO>> getBottom10Products() {
|
||||
RealtimeSalesReportQueryDTO queryDTO = new RealtimeSalesReportQueryDTO();
|
||||
queryDTO.setQueryType("day");
|
||||
queryDTO.setStartTime(java.time.LocalDateTime.now().withHour(0).withMinute(0).withSecond(0));
|
||||
queryDTO.setEndTime(java.time.LocalDateTime.now());
|
||||
queryDTO.setTopLimit(10);
|
||||
|
||||
Page<RealtimeSalesReportVO.ProductRankingVO> page = new Page<>(1, 10);
|
||||
Page<RealtimeSalesReportVO.ProductRankingVO> result =
|
||||
realtimeSalesReportService.getSlowSellingProducts(page, queryDTO);
|
||||
return R.ok(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取小时销售趋势(今日)
|
||||
*
|
||||
* @return 今日小时销售趋势数据
|
||||
*/
|
||||
@Operation(summary = "获取小时销售趋势", description = "获取今日小时销售趋势数据")
|
||||
@GetMapping("/hourlyTrend")
|
||||
@PreAuthorize("@pms.hasPermission('supermarket_realtimeSalesReport_view')")
|
||||
public R<Page<RealtimeSalesReportVO.TimePeriodSalesVO>> getHourlyTrend() {
|
||||
RealtimeSalesReportQueryDTO queryDTO = new RealtimeSalesReportQueryDTO();
|
||||
queryDTO.setQueryType("hour");
|
||||
queryDTO.setStartTime(java.time.LocalDateTime.now().withHour(0).withMinute(0).withSecond(0));
|
||||
queryDTO.setEndTime(java.time.LocalDateTime.now());
|
||||
|
||||
Page<RealtimeSalesReportVO.TimePeriodSalesVO> page = new Page<>(1, 24);
|
||||
Page<RealtimeSalesReportVO.TimePeriodSalesVO> result =
|
||||
realtimeSalesReportService.getTimePeriodSales(page, queryDTO);
|
||||
return R.ok(result);
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
package com.bsd.pjcloud.supermarket.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.bsd.pjcloud.supermarket.dto.realtime.RealtimeSalesReportQueryDTO;
|
||||
import com.bsd.pjcloud.supermarket.vo.realtime.RealtimeSalesReportVO;
|
||||
|
||||
/**
|
||||
* 即时销售报表服务接口
|
||||
*
|
||||
* @author pjcloud
|
||||
* @date 2025-01-27 10:00:00
|
||||
*/
|
||||
public interface RealtimeSalesReportService {
|
||||
|
||||
/**
|
||||
* 获取即时销售报表
|
||||
*
|
||||
* @param queryDTO 查询条件
|
||||
* @return 即时销售报表数据
|
||||
*/
|
||||
RealtimeSalesReportVO getRealtimeSalesReport(RealtimeSalesReportQueryDTO queryDTO);
|
||||
|
||||
/**
|
||||
* 获取实时销售数据(按分钟更新)
|
||||
*
|
||||
* @param queryDTO 查询条件
|
||||
* @return 实时销售数据
|
||||
*/
|
||||
RealtimeSalesReportVO getRealtimeSalesData(RealtimeSalesReportQueryDTO queryDTO);
|
||||
|
||||
/**
|
||||
* 获取畅销品排名
|
||||
*
|
||||
* @param page 分页参数
|
||||
* @param queryDTO 查询条件
|
||||
* @return 畅销品排名列表
|
||||
*/
|
||||
Page<RealtimeSalesReportVO.ProductRankingVO> getTopSellingProducts(Page<RealtimeSalesReportVO.ProductRankingVO> page, RealtimeSalesReportQueryDTO queryDTO);
|
||||
|
||||
/**
|
||||
* 获取滞销品排名
|
||||
*
|
||||
* @param page 分页参数
|
||||
* @param queryDTO 查询条件
|
||||
* @return 滞销品排名列表
|
||||
*/
|
||||
Page<RealtimeSalesReportVO.ProductRankingVO> getSlowSellingProducts(Page<RealtimeSalesReportVO.ProductRankingVO> page, RealtimeSalesReportQueryDTO queryDTO);
|
||||
|
||||
/**
|
||||
* 获取时段销售数据
|
||||
*
|
||||
* @param page 分页参数
|
||||
* @param queryDTO 查询条件
|
||||
* @return 时段销售数据列表
|
||||
*/
|
||||
Page<RealtimeSalesReportVO.TimePeriodSalesVO> getTimePeriodSales(Page<RealtimeSalesReportVO.TimePeriodSalesVO> page, RealtimeSalesReportQueryDTO queryDTO);
|
||||
|
||||
/**
|
||||
* 获取客单价分析数据
|
||||
*
|
||||
* @param queryDTO 查询条件
|
||||
* @return 客单价分析数据
|
||||
*/
|
||||
RealtimeSalesReportVO.CustomerAnalysisVO getCustomerAnalysis(RealtimeSalesReportQueryDTO queryDTO);
|
||||
|
||||
/**
|
||||
* 获取销售趋势数据
|
||||
*
|
||||
* @param page 分页参数
|
||||
* @param queryDTO 查询条件
|
||||
* @return 销售趋势数据列表
|
||||
*/
|
||||
Page<RealtimeSalesReportVO.SalesTrendVO> getSalesTrend(Page<RealtimeSalesReportVO.SalesTrendVO> page, RealtimeSalesReportQueryDTO queryDTO);
|
||||
|
||||
/**
|
||||
* 刷新实时销售数据缓存
|
||||
*
|
||||
* @return 是否刷新成功
|
||||
*/
|
||||
Boolean refreshRealtimeData();
|
||||
}
|
||||
+1268
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user