diff --git a/docs/API文档.md b/docs/API文档.md new file mode 100644 index 0000000..473adf4 --- /dev/null +++ b/docs/API文档.md @@ -0,0 +1,395 @@ +# 订单日报系统 API 接口文档 + +## 一、全局配置接口 + +### 1.1 获取求和数据采集字段配置 + +**接口地址**: `GET /api/global-config/sum-data-fields` + +**请求参数**: 无 + +**返回示例**: + +```json +{ + "success": true, + "data": ["sharp_electricity", "peak_electricity", "flat_electricity", "valley_electricity"] +} +``` + +**字段说明**: + +| 字段Key | 显示名称 | 说明 | +|---------|----------|------| +| sharp_electricity | 尖时电量(kWh) | 尖时段充电电量 | +| peak_electricity | 峰时电量(kWh) | 峰时段充电电量 | +| flat_electricity | 平时电量(kWh) | 平时段充电电量 | +| valley_electricity | 谷时电量(kWh) | 谷时段充电电量 | +| charge_elecfee_amount | 实收电费(元) | 实际收取的电费金额 | +| charge_servicefee_amount | 实收服务费(元) | 实际收取的服务费金额 | +| charge_amount | 实收总金额(元) | 实际收取的总金额(电费+服务费) | +| charge_frequency | 充电次数 | 充电订单次数 | +| custom_service_fee | 自定义服务费(元) | 按自定义单价计算的服务费 | +| custom_total_amount | 自定义实收总金额(元) | 实收电费+自定义服务费 | + +### 1.2 保存求和数据采集字段配置 + +**接口地址**: `POST /api/global-config/sum-data-fields` + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| sum_data_fields | Array | 是 | 选中的采集字段Key列表 | + +**请求示例**: + +```json +{ + "sum_data_fields": ["sharp_electricity", "peak_electricity", "flat_electricity", "valley_electricity"] +} +``` + +**返回示例**: + +```json +{ + "success": true, + "message": "配置保存成功" +} +``` + +## 二、求和数据接口 + +### 2.1 查询求和数据列表 + +**接口地址**: `GET /api/sum-data` + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| page | Integer | 否 | 页码(不传则返回全部数据) | +| page_size | Integer | 否 | 每页大小,默认20 | +| start_date | String | 否 | 开始日期(格式:YYYY-MM-DD) | +| end_date | String | 否 | 结束日期(格式:YYYY-MM-DD) | +| config_id | Integer | 否 | 配置ID | +| config_name | String | 否 | 配置名称(模糊搜索) | +| sum_field_key | String | 否 | 求和字段键名 | +| data_source | String | 否 | 数据来源(auto/手动编辑) | + +**返回示例**: + +```json +{ + "success": true, + "data": { + "list": [...], + "total": 100, + "page": 1, + "page_size": 20 + } +} +``` + +### 2.2 从历史记录采集求和数据 + +**接口地址**: `POST /api/sum-data/collect` + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| history_id | Integer | 否 | 历史记录ID(不传则采集所有) | +| start_date | String | 否 | 开始日期 | +| end_date | String | 否 | 结束日期 | + +**返回示例**: + +```json +{ + "success": true, + "message": "采集完成,共采集 50 条求和数据,跳过 0 条无求和数据的记录" +} +``` + +### 2.3 批量添加求和数据 + +**接口地址**: `POST /api/sum-data/batch-add` + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| report_date | String | 是 | 报表日期(YYYY-MM-DD) | +| config_name | String | 是 | 配置名称 | +| split_type | String | 是 | 拆分方式(company_id/user_id/station_id) | +| split_value | String | 是 | 拆分值 | +| split_name | String | 否 | 拆分显示名称 | +| total_orders | Integer | 否 | 订单数 | +| remark | String | 否 | 备注/修改原因 | +| fields | Array | 是 | 求和字段数据列表 | + +**fields数组结构**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| sum_field_key | String | 是 | 字段键名 | +| sum_field_name | String | 是 | 字段显示名称 | +| sum_value | Float | 是 | 求和值 | + +### 2.4 更新求和数据 + +**接口地址**: `PUT /api/sum-data/` + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| sum_value | Float | 是 | 更新后的求和值 | +| remark | String | 否 | 修改原因/备注 | + +### 2.5 删除求和数据 + +**接口地址**: `DELETE /api/sum-data/` + +**请求参数**: 无 + +### 2.6 批量删除求和数据 + +**接口地址**: `POST /api/sum-data/batch-delete` + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| ids | Array | 是 | 要删除的记录ID列表 | + +### 2.7 导出求和数据 + +**接口地址**: `GET /api/sum-data/export` + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| start_date | String | 否 | 开始日期 | +| end_date | String | 否 | 结束日期 | +| keyword | String | 否 | 配置名称关键字 | +| data_source | String | 否 | 数据来源 | + +**返回示例**: + +```json +{ + "success": true, + "total": 100, + "file_path": "/public/reports/求和数据导出_20260728.xlsx", + "filename": "求和数据导出_20260728.xlsx" +} +``` + +## 三、配置管理接口 + +### 3.1 创建配置 + +**接口地址**: `POST /api/config` + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| config_name | String | 是 | 配置名称 | +| split_type | String | 是 | 拆分方式 | +| split_value | String | 是 | 拆分值(逗号分隔) | +| split_name | String | 否 | 拆分显示名称 | +| selected_fields | Array | 是 | 选择的字段列表 | +| sum_fields | Array | 否 | 求和字段列表 | +| time_periods | Object | 否 | 分时段电量配置 | +| merge_telecom | Boolean | 否 | 是否合并特来电数据 | +| telecom_vehicle_no | String | 否 | 特来电车量自编号 | +| field_custom_names | Object | 否 | 字段自定义表头名称 | +| show_monthly_total | Boolean | 否 | 是否显示月度总计 | +| custom_service_fee_price | Float | 否 | 自定义服务费单价(元/kWh) | +| custom_service_fee_name | String | 否 | 自定义服务费表头名称 | +| show_custom_service_fee | Boolean | 否 | 是否显示自定义服务费列 | +| show_total_amount | Boolean | 否 | 是否显示实收总金额列 | +| total_amount_name | String | 否 | 实收总金额表头名称 | + +### 3.2 查询配置列表 + +**接口地址**: `GET /api/config` + +**请求参数**: 无 + +### 3.3 查询单个配置 + +**接口地址**: `GET /api/config/` + +**请求参数**: 无 + +### 3.4 更新配置 + +**接口地址**: `PUT /api/config/` + +**请求参数**: 同创建配置 + +### 3.5 删除配置 + +**接口地址**: `DELETE /api/config/` + +**请求参数**: 无 + +### 3.6 复制配置 + +**接口地址**: `POST /api/config//copy` + +**请求参数**: 无 + +### 3.7 启用/禁用配置 + +**接口地址**: `POST /api/config//toggle` + +**请求参数**: 无 + +### 3.8 配置排序 + +**接口地址**: `POST /api/config//move` + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| direction | String | 是 | 移动方向(up/down) | + +## 四、认证接口 + +### 4.1 登录 + +**接口地址**: `POST /api/auth/login` + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| username | String | 是 | 用户名 | +| password | String | 是 | 密码 | + +### 4.2 获取用户信息 + +**接口地址**: `GET /api/auth/info` + +**请求参数**: 无 + +### 4.3 修改密码 + +**接口地址**: `POST /api/auth/change-password` + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| old_password | String | 是 | 旧密码 | +| new_password | String | 是 | 新密码 | +| confirm_password | String | 是 | 确认新密码 | + +## 五、实体管理接口 + +### 5.1 查询企业列表 + +**接口地址**: `GET /api/entities/company` + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| page | Integer | 否 | 页码 | +| page_size | Integer | 否 | 每页大小 | +| keyword | String | 否 | 搜索关键字 | + +### 5.2 查询用户列表 + +**接口地址**: `GET /api/entities/user` + +**请求参数**: 同企业列表 + +### 5.3 查询场站列表 + +**接口地址**: `GET /api/entities/station` + +**请求参数**: 同企业列表 + +### 5.4 按ID批量查询实体 + +**接口地址**: `POST /api/entities/by_ids` + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| type | String | 是 | 实体类型(company/user/station) | +| ids | Array | 是 | 实体ID列表 | + +## 六、字段列表接口 + +### 6.1 获取字段列表 + +**接口地址**: `GET /api/fields` + +**请求参数**: 无 + +## 七、日报生成接口 + +### 7.1 生成日报 + +**接口地址**: `POST /api/report/generate` + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| config_id | Integer | 是 | 配置ID | +| report_date | String | 否 | 报表日期(默认昨天) | + +### 7.2 查询历史记录 + +**接口地址**: `GET /api/report/history` + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| page | Integer | 否 | 页码 | +| page_size | Integer | 否 | 每页大小 | +| start_date | String | 否 | 开始日期 | +| end_date | String | 否 | 结束日期 | +| config_name | String | 否 | 配置名称 | +| status | Integer | 否 | 状态(0失败/1成功) | + +### 7.3 删除历史记录 + +**接口地址**: `DELETE /api/report/history/` + +**请求参数**: 无 + +### 7.4 批量删除历史记录 + +**接口地址**: `POST /api/report/history/batch-delete` + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| ids | Array | 是 | 记录ID列表 | + +## 八、文件下载接口 + +### 8.1 下载报表文件 + +**接口地址**: `GET /api/download/report/` + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| file_name | String | 是 | 文件名 | \ No newline at end of file diff --git a/docs/变更日志.md b/docs/变更日志.md new file mode 100644 index 0000000..104ea02 --- /dev/null +++ b/docs/变更日志.md @@ -0,0 +1,176 @@ +# 订单日报系统 变更日志 + +## 版本记录 + +### v1.0.0 (2026-07-28) + +**新增功能**: + +1. **求和数据全局配置功能** + - 在"求和数据"页面添加"全局配置"按钮 + - 创建全局配置表 `t_daily_report_global_config` + - 支持配置求和数据采集和显示的字段范围 + - 充电电量(charge_degree)始终采集和显示 + - 未配置时显示所有字段(向后兼容) + +2. **新增全局配置API** + - `GET /api/global-config/sum-data-fields` - 获取配置 + - `POST /api/global-config/sum-data-fields` - 保存配置 + +**修复问题**: + +1. **配置加载失败** + - 修复编辑配置时 `selectedFields is not defined` 错误 + - 将 `refreshSumDataFieldsList(selectedFields, sumDataFields)` 改为 `refreshSumDataFieldsList(config.selected_fields, sumDataFields)` + +2. **求和数据显示字段过滤** + - 修改渲染逻辑,按全局配置过滤显示字段 + - 在加载数据前先获取全局配置 + - 页面加载时初始化全局配置 + +**重构优化**: + +1. **移除配置管理页面中的求和数据采集字段配置** + - 从 `index.html` 中移除相关UI + - 从 `config.js` 中移除相关逻辑 + - 从 `config_api.py` 中移除 `sum_data_fields` 参数处理 + +2. **修改求和数据采集逻辑** + - 从全局配置表读取字段配置 + - 过滤采集字段,只采集配置的字段 + +**文件变更**: + +| 文件路径 | 变更类型 | 说明 | +|----------|----------|------| +| `lib/db_init.py` | 修改 | 新增全局配置表 `t_daily_report_global_config` | +| `lib/api/global_config_api.py` | 新增 | 全局配置API | +| `lib/api/__init__.py` | 修改 | 注册全局配置蓝图 | +| `lib/api/config_api.py` | 修改 | 移除 `sum_data_fields` 参数处理 | +| `lib/sum_data_collector.py` | 修改 | 从全局配置读取字段配置 | +| `templates/index.html` | 修改 | 移除配置管理页面的求和数据采集字段UI,添加全局配置按钮和模态框 | +| `public/static/js/config.js` | 修改 | 移除求和数据采集字段相关逻辑 | +| `public/static/js/sum_data.js` | 修改 | 添加全局配置功能和字段过滤逻辑 | + +### v0.9.0 (2026-07-27) + +**新增功能**: + +1. **自定义服务费功能** + - 支持配置自定义服务费单价 + - 支持配置自定义服务费表头名称 + - 支持在报表中显示自定义服务费字段 + +2. **自定义实收总金额功能** + - 支持配置是否显示实收总金额字段 + - 支持配置实收总金额表头名称 + - 计算逻辑:实收电费 + 自定义服务费 + +3. **月度总计功能** + - 支持在报表底部显示当月充电量累计总计 + - 数据来源于求和采集表 + +**文件变更**: + +| 文件路径 | 变更类型 | 说明 | +|----------|----------|------| +| `lib/db_init.py` | 修改 | 新增 `show_monthly_total`, `custom_service_fee_price`, `custom_service_fee_name`, `show_custom_service_fee`, `show_total_amount`, `total_amount_name` 字段 | +| `lib/api/config_api.py` | 修改 | 添加自定义服务费和实收总金额配置处理 | +| `lib/report_generator.py` | 修改 | 添加自定义服务费和实收总金额计算逻辑 | +| `templates/index.html` | 修改 | 添加自定义服务费和实收总金额配置UI | +| `public/static/js/config.js` | 修改 | 添加自定义服务费和实收总金额配置逻辑 | + +### v0.8.0 (2026-07-20) + +**新增功能**: + +1. **特来电数据合并** + - 支持合并特来电平台数据 + - 支持配置特来电车量自编号 + +2. **分时段电量统计** + - 支持尖峰平谷分时段电量统计 + - 支持自定义时段配置 + +3. **求和数据管理** + - 支持从历史记录采集求和数据 + - 支持手动添加和编辑求和数据 + - 支持导出求和数据 + +**文件变更**: + +| 文件路径 | 变更类型 | 说明 | +|----------|----------|------| +| `lib/db_init.py` | 修改 | 创建求和数据表 `t_daily_report_sum_data` | +| `lib/sum_data_collector.py` | 新增 | 求和数据采集和管理逻辑 | +| `lib/api/sum_data_api.py` | 新增 | 求和数据API | +| `templates/index.html` | 修改 | 添加求和数据页面和相关模态框 | +| `public/static/js/sum_data.js` | 新增 | 求和数据前端逻辑 | + +### v0.7.0 (2026-07-15) + +**新增功能**: + +1. **配置管理** + - 支持创建、编辑、删除配置 + - 支持配置复制 + - 支持启用/禁用配置 + - 支持配置排序 + +2. **实体管理** + - 支持企业、用户、场站列表查询 + - 支持分页和搜索 + +3. **日报生成** + - 支持手动生成日报 + - 支持定时任务自动生成 + +4. **历史记录** + - 支持查看生成历史 + - 支持下载报表文件 + - 支持批量删除 + +**文件变更**: + +| 文件路径 | 变更类型 | 说明 | +|----------|----------|------| +| `app.py` | 新增 | Flask 主应用 | +| `lib/db.py` | 新增 | 数据库连接配置 | +| `lib/db_init.py` | 新增 | 数据库初始化 | +| `lib/field_mapping.py` | 新增 | 字段映射定义 | +| `lib/report_generator.py` | 新增 | 日报生成核心逻辑 | +| `lib/api/config_api.py` | 新增 | 配置管理API | +| `lib/api/entity_api.py` | 新增 | 实体管理API | +| `lib/api/field_api.py` | 新增 | 字段列表API | +| `lib/api/report_api.py` | 新增 | 日报生成和历史记录API | +| `lib/api/download_api.py` | 新增 | 文件下载API | +| `lib/api/auth_api.py` | 新增 | 认证API | +| `templates/index.html` | 新增 | 主页面 | +| `public/static/css/style.css` | 新增 | 全局样式 | +| `public/static/js/common.js` | 新增 | 通用工具函数 | +| `public/static/js/config.js` | 新增 | 配置管理逻辑 | +| `public/static/js/entity.js` | 新增 | 实体管理逻辑 | +| `public/static/js/report.js` | 新增 | 日报生成逻辑 | +| `public/static/js/history.js` | 新增 | 历史记录逻辑 | + +## 版本说明 + +### 版本号规则 + +- **主版本号**:重大功能变更或架构调整 +- **次版本号**:新增功能或修复问题 +- **修订号**:小修复和优化 + +### 向后兼容性 + +- v1.0.0 与 v0.9.0 完全兼容 +- v0.9.0 与 v0.8.0 完全兼容 +- v0.8.0 与 v0.7.0 完全兼容 + +### 数据库迁移 + +系统启动时会自动执行数据库迁移,无需手动操作: + +1. 创建缺失的表 +2. 添加缺失的字段 +3. 初始化全局配置记录(如果不存在) \ No newline at end of file diff --git a/docs/数据库变更与部署文档.md b/docs/数据库变更与部署文档.md new file mode 100644 index 0000000..8d36020 --- /dev/null +++ b/docs/数据库变更与部署文档.md @@ -0,0 +1,377 @@ +# 订单日报系统 数据库变更与部署文档 + +## 一、数据库变更说明 + +### 1.1 新增表:t_daily_report_global_config + +**表结构**: + +```sql +CREATE TABLE IF NOT EXISTS t_daily_report_global_config ( + id BIGINT NOT NULL DEFAULT 1 COMMENT '主键(固定为1)', + sum_data_fields VARCHAR(1000) NULL COMMENT '求和数据采集和显示字段列表(JSON格式)', + create_time DATETIME NULL COMMENT '创建时间', + update_time DATETIME NULL COMMENT '更新时间' +) +UNIQUE KEY(id) +DISTRIBUTED BY HASH(id) BUCKETS 1 +PROPERTIES("replication_num" = "1") +``` + +**字段说明**: + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| id | BIGINT | 主键,固定为1(全局配置只有一条记录) | +| sum_data_fields | VARCHAR(1000) | 求和数据采集和显示字段列表,JSON格式 | +| create_time | DATETIME | 创建时间 | +| update_time | DATETIME | 更新时间 | + +**数据示例**: + +```sql +INSERT INTO t_daily_report_global_config (id, sum_data_fields, create_time, update_time) +VALUES (1, '["sharp_electricity","peak_electricity","flat_electricity","valley_electricity"]', NOW(), NOW()); +``` + +### 1.2 自动初始化 + +系统启动时会自动执行以下操作: + +1. 检查表是否存在,不存在则创建 +2. 检查ID=1的记录是否存在,不存在则初始化一条空记录 + +**相关代码**:[db_init.py](file:///d:/py/code/ylt_power_diy/ylt_diy/lib/db_init.py#L186-L211) + +## 二、部署步骤 + +### 2.1 开发环境部署 + +**前置条件**: + +- Python 3.12+ +- MySQL/Apache Doris 数据库 +- Git + +**步骤**: + +1. **克隆代码** + +```bash +git clone <仓库地址> +cd ylt_diy +``` + +2. **安装依赖** + +```bash +pip install -r requirements.txt +``` + +3. **配置环境变量**(可选) + +```bash +# 设置数据库连接(默认值见下文) +export DB_HOST=haoslm2.xicp.net +export DB_PORT=10216 +export DB_USER=root +export DB_PASSWORD=your_password +export DB_DATABASE=ylt_power_diy +export DEPLOY_RUN_PORT=5000 +``` + +4. **启动开发服务器** + +```bash +python app.py +``` + +5. **访问系统** + +打开浏览器访问:http://localhost:5000 + +### 2.2 生产环境部署 + +**前置条件**: + +- Linux 服务器(推荐 CentOS 7+) +- Python 3.12+ +- MySQL/Apache Doris 数据库 +- Nginx(可选,用于反向代理) + +**步骤**: + +1. **克隆代码** + +```bash +git clone <仓库地址> +cd ylt_diy +``` + +2. **安装依赖** + +```bash +pip install -r requirements.txt +``` + +3. **配置环境变量** + +创建 `.env` 文件: + +```bash +DEPLOY_RUN_PORT=5000 +DB_HOST=haoslm2.xicp.net +DB_PORT=10216 +DB_USER=root +DB_PASSWORD=your_password +DB_DATABASE=ylt_power_diy +``` + +4. **创建服务配置** + +创建 `/etc/systemd/system/ylt_daily_report.service`: + +```ini +[Unit] +Description=订单日报系统 +After=network.target + +[Service] +Type=simple +User=www-data +WorkingDirectory=/path/to/ylt_diy +Environment="DEPLOY_RUN_PORT=5000" +Environment="DB_HOST=haoslm2.xicp.net" +Environment="DB_PORT=10216" +Environment="DB_USER=root" +Environment="DB_PASSWORD=your_password" +Environment="DB_DATABASE=ylt_power_diy" +ExecStart=/usr/bin/python3 app.py +Restart=always + +[Install] +WantedBy=multi-user.target +``` + +5. **启动服务** + +```bash +systemctl daemon-reload +systemctl start ylt_daily_report +systemctl enable ylt_daily_report +``` + +6. **配置 Nginx 反向代理**(可选) + +创建 `/etc/nginx/conf.d/ylt_daily_report.conf`: + +```nginx +server { + listen 80; + server_name your-domain.com; + + location / { + proxy_pass http://127.0.0.1:5000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + location /public/ { + root /path/to/ylt_diy; + expires 30d; + } +} +``` + +7. **重启 Nginx** + +```bash +nginx -t +systemctl restart nginx +``` + +### 2.3 数据库配置 + +**默认配置**: + +| 配置项 | 默认值 | 说明 | +|--------|--------|------| +| DB_HOST | haoslm2.xicp.net | 数据库主机地址 | +| DB_PORT | 10216 | 数据库端口 | +| DB_USER | root | 数据库用户名 | +| DB_PASSWORD | (空) | 数据库密码 | +| DB_DATABASE | ylt_power_diy | 数据库名 | + +**连接方式**: + +系统使用 PyMySQL 连接数据库,支持 MySQL 和 Apache Doris(MySQL 协议兼容)。 + +**相关代码**:[db.py](file:///d:/py/code/ylt_power_diy/ylt_diy/lib/db.py) + +## 三、版本升级 + +### 3.1 升级步骤 + +1. **拉取最新代码** + +```bash +cd /path/to/ylt_diy +git pull +``` + +2. **安装新依赖**(如果有) + +```bash +pip install -r requirements.txt +``` + +3. **重启服务** + +```bash +systemctl restart ylt_daily_report +``` + +### 3.2 数据库自动迁移 + +系统启动时会自动执行数据库初始化,包括: + +1. 创建缺失的表 +2. 添加缺失的字段 +3. 初始化全局配置记录 + +**注意**:如果出现数据库迁移失败,请检查数据库连接和权限。 + +## 四、备份与恢复 + +### 4.1 数据库备份 + +```bash +# 使用 mysqldump 备份 +mysqldump -h haoslm2.xicp.net -P 10216 -u root -p ylt_power_diy > backup.sql + +# 使用 Doris 备份命令(如果使用 Doris) +curl -X POST http://doris-be:8030/api/bootstrap -d '{"type":"backup","backup_name":"daily_report_backup","storage":"hdfs://your-hdfs/path"}' +``` + +### 4.2 数据库恢复 + +```bash +# 使用 mysql 命令恢复 +mysql -h haoslm2.xicp.net -P 10216 -u root -p ylt_power_diy < backup.sql +``` + +### 4.3 文件备份 + +```bash +# 备份报表文件 +tar -czvf reports_backup.tar.gz public/reports/ + +# 备份配置文件 +tar -czvf config_backup.tar.gz lib/ +``` + +## 五、常见部署问题 + +### 5.1 数据库连接失败 + +**问题**:系统启动时提示数据库连接失败 + +**解决**: + +- 检查数据库主机和端口是否正确 +- 确认数据库服务正常运行 +- 检查数据库用户名和密码 +- 确认网络连通性(ping 数据库主机) + +### 5.2 端口被占用 + +**问题**:启动时提示端口 5000 被占用 + +**解决**: + +```bash +# 查看占用进程 +lsof -i :5000 + +# 杀死占用进程 +kill -9 + +# 或修改端口 +export DEPLOY_RUN_PORT=5001 +``` + +### 5.3 静态文件加载失败 + +**问题**:页面样式和脚本无法加载 + +**解决**: + +- 检查 `public/static/` 目录是否存在 +- 确认 Flask 应用的 `static_folder` 配置正确 +- 清除浏览器缓存(Ctrl + Shift + R) + +### 5.4 定时任务不执行 + +**问题**:每天 08:01 的定时任务没有执行 + +**解决**: + +- 确认服务正常运行(`systemctl status ylt_daily_report`) +- 检查日志文件(`/app/work/logs/bypass/`) +- 确认系统时间正确 +- 检查配置是否启用(`is_active = 1`) + +### 5.5 内存不足 + +**问题**:服务运行一段时间后崩溃 + +**解决**: + +- 增加服务器内存 +- 优化查询语句,减少内存使用 +- 配置定时清理任务 + +## 六、日志管理 + +### 6.1 日志位置 + +- 应用日志:`/app/work/logs/bypass/` +- 系统日志:`/var/log/syslog`(Linux) + +### 6.2 日志级别 + +- INFO:普通信息 +- WARNING:警告信息 +- ERROR:错误信息 + +### 6.3 查看日志 + +```bash +# 查看应用日志 +tail -f /app/work/logs/bypass/app.log + +# 查看系统日志 +journalctl -u ylt_daily_report -f +``` + +## 七、安全建议 + +### 7.1 数据库安全 + +- 使用强密码 +- 限制数据库访问 IP +- 定期备份数据库 + +### 7.2 应用安全 + +- 使用 HTTPS +- 定期更新依赖 +- 关闭调试模式(生产环境) +- 配置防火墙 + +### 7.3 服务器安全 + +- 禁用 SSH 密码登录(使用密钥) +- 定期更新系统 +- 禁用不必要的服务 \ No newline at end of file diff --git a/docs/用户使用说明.md b/docs/用户使用说明.md new file mode 100644 index 0000000..2f52a1f --- /dev/null +++ b/docs/用户使用说明.md @@ -0,0 +1,236 @@ +# 订单日报系统 用户使用说明 + +## 一、系统概述 + +订单日报系统是一个基于 Flask 的 Web 应用,用于自动生成充电订单的日报报表。系统支持按企业/用户/场站拆分数据,自定义字段和求和功能,并支持合并特来电平台数据。 + +## 二、登录系统 + +1. 打开浏览器,访问系统地址(如:http://localhost:5000) +2. 在登录页面输入用户名和密码 +3. 点击"登录"按钮进入系统 + +## 三、配置管理 + +### 3.1 创建配置 + +1. 在"配置管理"页面点击"创建配置"按钮 +2. 填写配置名称(如:西南79路) +3. 选择拆分方式: + - 按企业:按企业ID拆分数据 + - 按用户:按用户ID拆分数据 + - 按场站:按场站ID拆分数据 +4. 搜索并选择拆分对象(可多选) +5. 在"选择字段"区域勾选需要在报表中显示的字段 +6. (可选)在"字段顺序"区域调整字段显示顺序和自定义表头名称 +7. (可选)勾选"合并特来电平台数据"并输入车量自编号 +8. (可选)配置分时段电量统计(尖峰平谷) +9. (可选)配置自定义服务费和实收总金额 +10. 点击"保存"按钮完成创建 + +### 3.2 编辑配置 + +1. 在配置列表中找到目标配置 +2. 点击"编辑"按钮 +3. 修改需要调整的配置项 +4. 点击"保存"按钮 + +### 3.3 复制配置 + +1. 在配置列表中找到目标配置 +2. 点击"复制"按钮 +3. 系统会自动创建一个"配置名_副本"的新配置 +4. 可根据需要修改新配置 + +### 3.4 启用/禁用配置 + +1. 在配置列表中找到目标配置 +2. 点击"启用"或"禁用"按钮 +3. 禁用的配置不会参与定时任务自动生成 + +### 3.5 删除配置 + +1. 在配置列表中找到目标配置 +2. 点击"删除"按钮 +3. 确认删除操作 + +## 四、日报生成 + +### 4.1 手动生成日报 + +1. 切换到"日报生成"页面 +2. 选择需要生成的配置(可多选) +3. 选择报表日期(默认昨天) +4. 点击"生成日报"按钮 +5. 等待生成完成,可查看生成结果 + +### 4.2 定时任务 + +系统每天 08:01 自动执行日报生成任务,遍历所有启用的配置逐一生成日报。 + +## 五、历史记录 + +### 5.1 查看历史记录 + +1. 切换到"历史记录"页面 +2. 可按日期范围和配置名称筛选 +3. 查看每条记录的状态(成功/失败)、订单数、总电量等信息 + +### 5.2 下载报表 + +1. 在历史记录列表中找到目标记录 +2. 点击"下载"按钮 +3. 浏览器会自动下载报表文件 + +### 5.3 删除历史记录 + +1. 在历史记录列表中找到目标记录 +2. 点击"删除"按钮,或勾选多条记录后点击"批量删除" +3. 确认删除操作 + +## 六、求和数据 + +### 6.1 查看求和数据 + +1. 切换到"求和数据"页面 +2. 可按日期范围、配置名称、数据来源筛选 +3. 表格显示各配置的每日求和数据,包括: + - 充电电量(必选字段) + - 分时电量(尖、峰、平、谷) + - 其他配置的求和字段 + +### 6.2 从历史采集 + +1. 在"求和数据"页面选择日期范围 +2. 点击"从历史采集"按钮 +3. 确认操作后,系统会从历史记录中采集求和数据 +4. **注意**:会先清除该日期范围内旧的自动采集数据 + +### 6.3 手动添加 + +1. 点击"手动添加"按钮 +2. 填写报表日期、配置名称、拆分方式、拆分值等信息 +3. 输入各求和字段的值(至少输入一个) +4. (可选)填写备注/修改原因 +5. 点击"保存"按钮 + +### 6.4 编辑求和数据 + +1. 在求和数据列表中找到目标记录 +2. 点击"编辑"按钮 +3. 修改求和字段的值 +4. (可选)填写修改原因 +5. 点击"保存"按钮 + +### 6.5 删除求和数据 + +1. 在求和数据列表中找到目标记录 +2. 点击"删除"按钮,或勾选多条记录后点击"批量删除" +3. 确认删除操作 + +### 6.6 导出求和数据 + +1. 在"求和数据"页面选择筛选条件 +2. 点击"导出"按钮 +3. 系统会生成 Excel 文件并下载 + +### 6.7 全局配置(重要) + +**功能说明**:控制求和数据采集和显示的字段范围,避免采集和显示不需要的数据。 + +**操作步骤**: + +1. 在"求和数据"页面点击"全局配置"按钮 +2. 在弹出的配置窗口中,勾选需要采集和显示的字段 +3. **注意**:充电电量(charge_degree)是必选字段,始终采集和显示 +4. 取消勾选不需要的字段(如电费金额、服务费金额等) +5. 点击"保存"按钮 +6. 页面会自动刷新,只显示勾选的字段 + +**可配置字段**: + +| 字段名称 | 说明 | +|----------|------| +| 尖时电量(kWh) | 尖时段充电电量 | +| 峰时电量(kWh) | 峰时段充电电量 | +| 平时电量(kWh) | 平时段充电电量 | +| 谷时电量(kWh) | 谷时段充电电量 | +| 实收电费(元) | 实际收取的电费金额 | +| 实收服务费(元) | 实际收取的服务费金额 | +| 实收总金额(元) | 实际收取的总金额(电费+服务费) | +| 充电次数 | 充电订单次数 | +| 自定义服务费(元) | 按自定义单价计算的服务费 | +| 自定义实收总金额(元) | 实收电费+自定义服务费 | + +**配置生效范围**: +- **采集时**:从历史记录采集数据时,只采集勾选的字段 +- **显示时**:求和数据页面只显示勾选的字段 +- **向后兼容**:如果没有配置任何字段,则采集和显示所有字段 + +## 七、常见问题 + +### 7.1 配置加载失败 + +**问题**:编辑配置时提示"加载配置失败: selectedFields is not defined" + +**解决**:系统已修复此问题,请刷新页面重试。 + +### 7.2 报表生成失败 + +**问题**:生成日报时提示错误 + +**解决**: +- 检查配置是否正确(拆分方式、拆分值、字段选择等) +- 确认订单数据存在 +- 查看日志文件(/app/work/logs/bypass/) + +### 7.3 求和数据显示字段过多 + +**问题**:求和数据页面显示了不需要的字段 + +**解决**: +- 点击"全局配置"按钮 +- 取消勾选不需要的字段 +- 点击"保存"按钮 + +### 7.4 充电电量显示为0或空 + +**问题**:求和数据中充电电量为0或显示"-" + +**解决**: +- 检查对应日期是否有充电订单数据 +- 尝试重新从历史采集数据 + +### 7.5 手动添加的数据不显示 + +**问题**:手动添加求和数据后,页面不显示 + +**解决**: +- 确认添加操作成功(弹出"添加成功"提示) +- 刷新页面查看 +- 检查筛选条件是否正确 + +### 7.6 配置名称搜索自动填充用户名 + +**问题**:配置名称搜索框自动填充了登录用户名 + +**解决**:搜索框已设置 `autocomplete="off"`,请清除浏览器缓存后重试。 + +## 八、快捷键 + +| 快捷键 | 功能 | +|--------|------| +| Ctrl + Enter | 搜索(在搜索框中) | + +## 九、系统要求 + +### 浏览器要求 + +- Chrome 90+ +- Firefox 88+ +- Edge 90+ + +### 分辨率要求 + +- 推荐分辨率:1920×1080 及以上 +- 最小分辨率:1280×720 \ No newline at end of file diff --git a/lib/field_mapping.py b/lib/field_mapping.py index 009bb28..cd068f2 100644 --- a/lib/field_mapping.py +++ b/lib/field_mapping.py @@ -177,6 +177,9 @@ FIELD_MAPPING = { # 车辆信息(从 t_car 表关联) 'car_bus_path': '公交线路', 'car_sn': '车辆编码', + + # 充电卡信息(从 t_user_card 表关联) + 'user_card_memo': '充电卡备注', } # 获取字段中文名称 diff --git a/lib/report_generator.py b/lib/report_generator.py index 71cf241..548fe92 100644 --- a/lib/report_generator.py +++ b/lib/report_generator.py @@ -350,10 +350,10 @@ def generate_daily_report(config_id, start_time=None, end_time=None): report_date = start_time.strftime('%Y-%m-%d') delete_old_history(config_id, report_date, config['split_type'], config['split_value']) - # 构建查询SQL - 过滤掉虚拟字段(时段电量、自定义服务费、自定义实收总金额、车辆信息等) + # 构建查询SQL - 过滤掉虚拟字段(时段电量、自定义服务费、自定义实收总金额、车辆信息、充电卡信息等) # 虚拟字段不是数据库表中的实际字段,需要动态计算或关联查询 # 注意:total_amount 是旧版虚拟字段名,为了向后兼容也需要包含在内 - VIRTUAL_FIELDS = {'sharp_electricity', 'peak_electricity', 'flat_electricity', 'valley_electricity', 'custom_service_fee', 'custom_total_amount', 'total_amount', 'car_bus_path', 'car_sn'} + VIRTUAL_FIELDS = {'sharp_electricity', 'peak_electricity', 'flat_electricity', 'valley_electricity', 'custom_service_fee', 'custom_total_amount', 'total_amount', 'car_bus_path', 'car_sn', 'user_card_memo'} db_fields = [f for f in selected_fields if f not in VIRTUAL_FIELDS] # 确保 charge_degree 总是被查询(用于计算总电量) @@ -373,6 +373,11 @@ def generate_daily_report(config_id, start_time=None, end_time=None): if need_car_info and 'charge_vin' not in db_fields: db_fields.append('charge_vin') + # 如果选择了充电卡备注字段,需要确保查询 user_card_no(用于关联 t_user_card 表) + need_card_memo = 'user_card_memo' in selected_fields + if need_card_memo and 'user_card_no' not in db_fields: + db_fields.append('user_card_no') + fields_str = ', '.join(db_fields) # 检查是否为多选值(逗号分隔) @@ -447,6 +452,30 @@ def generate_daily_report(config_id, start_time=None, end_time=None): order['car_sn'] = '' log_info(f"[日报生成] 关联查询到 {len(car_map)} 条车辆信息", 'report') + # 如果需要充电卡信息,从 t_user_card 表关联查询 + if need_card_memo and orders: + # 获取所有充电卡号 + card_no_list = list(set(order.get('user_card_no') for order in orders if order.get('user_card_no'))) + if card_no_list: + placeholders = ', '.join(['%s'] * len(card_no_list)) + card_sql = f""" + SELECT card_no, memo + FROM t_user_card + WHERE card_no IN ({placeholders}) + """ + card_results = execute_query(card_sql, tuple(card_no_list)) + card_map = {card['card_no']: card for card in card_results} + + # 将充电卡备注添加到订单中 + for order in orders: + card_no = order.get('user_card_no') + if card_no and card_no in card_map: + card_info = card_map[card_no] + order['user_card_memo'] = card_info.get('memo', '') + else: + order['user_card_memo'] = '' + log_info(f"[日报生成] 关联查询到 {len(card_map)} 条充电卡信息", 'report') + # 检查是否有补单记录(finish_type = 2 表示补单结束) has_supplement = any(order.get('finish_type') == 2 for order in orders) if has_supplement: @@ -472,6 +501,52 @@ def generate_daily_report(config_id, start_time=None, end_time=None): # 检查特来电数据中是否有补单记录 if not has_supplement: has_supplement = any(order.get('finish_type') == 2 for order in telecom_orders) + + # 合并后再次处理车辆信息(针对特来电订单) + if need_car_info: + telecom_vin_list = list(set(order.get('charge_vin') for order in telecom_orders if order.get('charge_vin'))) + if telecom_vin_list: + placeholders = ', '.join(['%s'] * len(telecom_vin_list)) + car_sql = f""" + SELECT car_vin, car_bus_path, car_sn + FROM t_car + WHERE car_vin IN ({placeholders}) + """ + car_results = execute_query(car_sql, tuple(telecom_vin_list)) + car_map = {car['car_vin']: car for car in car_results} + + for order in telecom_orders: + vin = order.get('charge_vin') + if vin and vin in car_map: + car_info = car_map[vin] + order['car_bus_path'] = car_info.get('car_bus_path', '') + order['car_sn'] = car_info.get('car_sn', '') + else: + order['car_bus_path'] = '' + order['car_sn'] = '' + log_info(f"[日报生成] 特来电数据关联查询到 {len(car_map)} 条车辆信息", 'report') + + # 合并后再次处理充电卡信息(针对特来电订单) + if need_card_memo: + telecom_card_no_list = list(set(order.get('user_card_no') for order in telecom_orders if order.get('user_card_no'))) + if telecom_card_no_list: + placeholders = ', '.join(['%s'] * len(telecom_card_no_list)) + card_sql = f""" + SELECT card_no, memo + FROM t_user_card + WHERE card_no IN ({placeholders}) + """ + card_results = execute_query(card_sql, tuple(telecom_card_no_list)) + card_map = {card['card_no']: card for card in card_results} + + for order in telecom_orders: + card_no = order.get('user_card_no') + if card_no and card_no in card_map: + card_info = card_map[card_no] + order['user_card_memo'] = card_info.get('memo', '') + else: + order['user_card_memo'] = '' + log_info(f"[日报生成] 特来电数据关联查询到 {len(card_map)} 条充电卡信息", 'report') if not orders: # 即使没有数据,也保存一条失败记录