mirror of
https://devops.liangqichi.top/qichi.liang/Orbitin.git
synced 2026-02-10 07:41:29 +08:00
feat: 实现月底/月初数据调整功能
1. 新增月底/月初智能数据调整功能 - 月底最后一天自动弹出剔除数据对话框 - 月初1号自动弹出添加数据对话框 - 普通日期不弹出对话框 2. 实现月底剔除数据自动转移到次月1号 - 月底剔除的数据自动添加到次月1号统计 - 支持跨月、跨年数据转移 - 数据备注自动记录转移信息 3. 修复自动获取数据后不弹出调整对话框的问题 - 修改auto_fetch_data()方法,成功获取数据后调用调整处理 - 确保第一次打开GUI也能弹出相应对话框 4. 修复月度统计不包含调整数据的问题 - 修改get_monthly_stats()方法包含手动调整数据 - 确保调整数据正确影响月度统计 5. 恢复日报原始模板格式 - 移除调整数据的详细说明 - 保持原始日报模板,只显示最终结果 6. 数据库增强 - 新增manual_adjustments表存储手动调整数据 - 实现调整数据的增删改查方法 - 实现包含调整数据的每日数据获取方法 测试通过:所有功能正常工作,数据计算准确。
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -26,3 +26,4 @@ Thumbs.db
|
|||||||
|
|
||||||
# IDE
|
# IDE
|
||||||
.vscode/
|
.vscode/
|
||||||
|
plans/
|
||||||
@@ -68,6 +68,7 @@ OrbitIn/
|
|||||||
- `get_ships_with_monthly_teu(year_month)` - 获取当月每艘船的作业量
|
- `get_ships_with_monthly_teu(year_month)` - 获取当月每艘船的作业量
|
||||||
- `insert_unaccounted(year_month, teu, note)` - 添加未统计数据
|
- `insert_unaccounted(year_month, teu, note)` - 添加未统计数据
|
||||||
- `get_unaccounted(year_month)` - 获取未统计数据
|
- `get_unaccounted(year_month)` - 获取未统计数据
|
||||||
|
- `delete_unaccounted(year_month)` - 去除未统计数据(对称功能)
|
||||||
|
|
||||||
### DailyReportGenerator (src/report.py:15)
|
### DailyReportGenerator (src/report.py:15)
|
||||||
|
|
||||||
@@ -79,6 +80,7 @@ OrbitIn/
|
|||||||
|
|
||||||
- tkinter 图形界面
|
- tkinter 图形界面
|
||||||
- 支持获取数据、生成日报、添加未统计数据
|
- 支持获取数据、生成日报、添加未统计数据
|
||||||
|
- 支持去除多余统计数据(对称功能)
|
||||||
- 日报内容可复制
|
- 日报内容可复制
|
||||||
|
|
||||||
### FeishuScheduleManager (src/feishu.py:150)
|
### FeishuScheduleManager (src/feishu.py:150)
|
||||||
@@ -118,6 +120,9 @@ python3 main.py parse-test
|
|||||||
# 添加未统计数据
|
# 添加未统计数据
|
||||||
python3 main.py --unaccounted 118 --month 2025-12
|
python3 main.py --unaccounted 118 --month 2025-12
|
||||||
|
|
||||||
|
# 去除未统计数据
|
||||||
|
python3 main.py --remove-unaccounted --month 2025-12
|
||||||
|
|
||||||
# GUI界面
|
# GUI界面
|
||||||
python3 src/gui.py
|
python3 src/gui.py
|
||||||
```
|
```
|
||||||
|
|||||||
71
README.md
71
README.md
@@ -9,6 +9,7 @@
|
|||||||
- SQLite3 数据库存储
|
- SQLite3 数据库存储
|
||||||
- 生成日报和月度统计
|
- 生成日报和月度统计
|
||||||
- 支持未统计数据手动录入
|
- 支持未统计数据手动录入
|
||||||
|
- 支持去除多余统计数据(对称功能)
|
||||||
- 支持二次靠泊记录合并
|
- 支持二次靠泊记录合并
|
||||||
- GUI 图形界面(可选)
|
- GUI 图形界面(可选)
|
||||||
- 飞书排班表集成(自动获取班次人员)
|
- 飞书排班表集成(自动获取班次人员)
|
||||||
@@ -124,6 +125,9 @@ python3 main.py config-test
|
|||||||
# 添加未统计数据
|
# 添加未统计数据
|
||||||
python3 main.py --unaccounted 118 --month 2025-12
|
python3 main.py --unaccounted 118 --month 2025-12
|
||||||
|
|
||||||
|
# 去除未统计数据
|
||||||
|
python3 main.py --remove-unaccounted --month 2025-12
|
||||||
|
|
||||||
# 显示帮助
|
# 显示帮助
|
||||||
python3 main.py --help
|
python3 main.py --help
|
||||||
```
|
```
|
||||||
@@ -136,14 +140,22 @@ python3 src/gui.py
|
|||||||
|
|
||||||
GUI 功能:
|
GUI 功能:
|
||||||
- 获取并处理数据
|
- 获取并处理数据
|
||||||
- 获取 (Debug模式)
|
- 重置数据库(删除并重新获取)
|
||||||
- 生成日报
|
- 生成日报
|
||||||
- 今日日报(自动获取前一天数据)
|
- 今日日报(自动获取前一天数据)
|
||||||
- 添加未统计数据
|
- 添加未统计数据
|
||||||
|
- 去除多余统计数据(对称功能)
|
||||||
|
- 月底/月初智能调整(自动弹出对话框)
|
||||||
- 数据库统计(显示当月每艘船的作业量)
|
- 数据库统计(显示当月每艘船的作业量)
|
||||||
- 日报内容可复制
|
- 日报内容可复制
|
||||||
- 自动刷新排班信息
|
- 自动刷新排班信息
|
||||||
|
|
||||||
|
#### 智能调整功能
|
||||||
|
- **月初1号**:自动询问是否添加上月数据
|
||||||
|
- **月底最后一天**:自动询问是否剔除12点后数据
|
||||||
|
- **其他日期**:保留手动调整入口
|
||||||
|
- **调整数据**:在日报中清晰显示,确保数据准确性
|
||||||
|
|
||||||
## 数据格式
|
## 数据格式
|
||||||
|
|
||||||
### 日报表 (daily_handover_logs)
|
### 日报表 (daily_handover_logs)
|
||||||
@@ -169,6 +181,20 @@ GUI 功能:
|
|||||||
| note | TEXT | 备注 |
|
| note | TEXT | 备注 |
|
||||||
| created_at | TEXT | 创建时间 |
|
| created_at | TEXT | 创建时间 |
|
||||||
|
|
||||||
|
### 手动调整表 (manual_adjustments)
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| id | INTEGER | 主键 |
|
||||||
|
| date | TEXT | 调整适用的日期 YYYY-MM-DD |
|
||||||
|
| ship_name | TEXT | 船名 |
|
||||||
|
| teu | INTEGER | TEU数量 |
|
||||||
|
| twenty_feet | INTEGER | 20尺箱量 |
|
||||||
|
| forty_feet | INTEGER | 40尺箱量 |
|
||||||
|
| adjustment_type | TEXT | 调整类型 'add' 或 'exclude' |
|
||||||
|
| note | TEXT | 备注 |
|
||||||
|
| created_at | TEXT | 创建时间 |
|
||||||
|
|
||||||
## 特性说明
|
## 特性说明
|
||||||
|
|
||||||
### 二次靠泊合并
|
### 二次靠泊合并
|
||||||
@@ -183,6 +209,49 @@ GUI 功能:
|
|||||||
|
|
||||||
可以在数据库统计中查看当月每艘船的作业量总计,便于跟踪船舶运营情况。
|
可以在数据库统计中查看当月每艘船的作业量总计,便于跟踪船舶运营情况。
|
||||||
|
|
||||||
|
### 去除多余统计数据
|
||||||
|
|
||||||
|
针对月底夜班箱量有时会被算入下个月的情况,系统提供了对称的"去除多余统计数据"功能:
|
||||||
|
- **添加未统计数据**: 用于补全缺失的箱量
|
||||||
|
- **去除未统计数据**: 用于删除多余统计的箱量
|
||||||
|
|
||||||
|
这两个功能配合使用,可以精确调整月度统计数据。
|
||||||
|
|
||||||
|
### 月底/月初数据调整功能
|
||||||
|
|
||||||
|
系统支持智能化的月底/月初数据调整:
|
||||||
|
|
||||||
|
#### 1. 月底最后一天(自动剔除12点后数据)
|
||||||
|
- 当获取数据的日期为月份最后一天时,GUI会自动询问是否需要剔除12点后的数据
|
||||||
|
- 用户可以输入需要剔除的船名、TEU以及具体尺寸(20尺/40尺)
|
||||||
|
- 剔除后的数据会从当月统计中移除,计入下月统计
|
||||||
|
|
||||||
|
#### 2. 月初1号(自动添加上月数据)
|
||||||
|
- 当获取数据的日期为月份1号时,GUI会自动询问是否需要添加上月的作业数据
|
||||||
|
- 用户可以输入需要添加的船名、TEU以及具体尺寸(20尺/40尺)
|
||||||
|
- 添加的数据会计入上月统计,确保月度数据的准确性
|
||||||
|
|
||||||
|
#### 3. 其他日期(手动调整入口)
|
||||||
|
- 非月初和月底的日期,默认不弹出调整对话框
|
||||||
|
- 但GUI侧边栏保留了手动添加/剔除TEU的功能入口
|
||||||
|
- 用户可以随时手动调整任何日期的数据
|
||||||
|
|
||||||
|
#### 4. 调整数据存储
|
||||||
|
所有手动调整数据存储在 `manual_adjustments` 表中:
|
||||||
|
- `date`: 调整适用的日期
|
||||||
|
- `ship_name`: 船名
|
||||||
|
- `teu`: TEU数量
|
||||||
|
- `twenty_feet`: 20尺箱量
|
||||||
|
- `forty_feet`: 40尺箱量
|
||||||
|
- `adjustment_type`: 'add' 或 'exclude'
|
||||||
|
- `note`: 备注信息
|
||||||
|
|
||||||
|
#### 5. 日报显示
|
||||||
|
调整数据会在日报中清晰显示:
|
||||||
|
- 每艘船下方显示具体的添加/剔除记录
|
||||||
|
- 日报末尾显示调整汇总信息
|
||||||
|
- 净调整量计算,确保数据准确性
|
||||||
|
|
||||||
## 示例输出
|
## 示例输出
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
67
main.py
67
main.py
@@ -189,6 +189,36 @@ def add_unaccounted(year_month: str, teu: int, note: str = ''):
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def remove_unaccounted(year_month: str, teu_to_reduce: int = None):
|
||||||
|
"""
|
||||||
|
去除未统计数据
|
||||||
|
|
||||||
|
参数:
|
||||||
|
year_month: 年月字符串,格式 "2025-12"
|
||||||
|
teu_to_reduce: 要减少的TEU数量,如果为None则删除整个记录
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
db = DailyLogsDatabase()
|
||||||
|
|
||||||
|
if teu_to_reduce is None:
|
||||||
|
# 如果没有指定减少数量,则删除整个记录
|
||||||
|
result = db.delete_unaccounted(year_month)
|
||||||
|
if result:
|
||||||
|
logger.info(f"已删除 {year_month} 月未统计数据")
|
||||||
|
else:
|
||||||
|
logger.error("删除失败")
|
||||||
|
else:
|
||||||
|
# 减少指定数量的TEU
|
||||||
|
result = db.reduce_unaccounted(year_month, teu_to_reduce)
|
||||||
|
if result:
|
||||||
|
logger.info(f"已减少 {year_month} 月未统计数据: {teu_to_reduce}TEU")
|
||||||
|
else:
|
||||||
|
logger.error("减少失败")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"去除未统计数据失败: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
def show_stats(date: str):
|
def show_stats(date: str):
|
||||||
"""
|
"""
|
||||||
显示指定日期的统计
|
显示指定日期的统计
|
||||||
@@ -288,11 +318,19 @@ def main():
|
|||||||
config-test 配置测试
|
config-test 配置测试
|
||||||
stats 显示今日统计
|
stats 显示今日统计
|
||||||
|
|
||||||
|
参数:
|
||||||
|
--unaccounted, -u TEU 添加未统计数据(需同时指定月份)
|
||||||
|
--remove-unaccounted, -r [TEU] 去除未统计数据(需同时指定月份)。如果指定TEU值,则减少该数量;如果不指定,则删除整个记录
|
||||||
|
--month, -m YEAR-MONTH 指定月份(与 -u 或 -r 配合使用)
|
||||||
|
|
||||||
示例:
|
示例:
|
||||||
python3 main.py fetch
|
python3 main.py fetch
|
||||||
python3 main.py fetch-save
|
python3 main.py fetch-save
|
||||||
python3 main.py report 2025-12-28
|
python3 main.py report 2025-12-28
|
||||||
python3 main.py config-test
|
python3 main.py config-test
|
||||||
|
python3 main.py --unaccounted 118 --month 2025-12
|
||||||
|
python3 main.py --remove-unaccounted --month 2025-12 # 删除整个记录
|
||||||
|
python3 main.py --remove-unaccounted 118 --month 2025-12 # 减少118TEU
|
||||||
'''
|
'''
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
@@ -314,11 +352,20 @@ def main():
|
|||||||
type=int,
|
type=int,
|
||||||
help='添加未统计数据(需同时指定月份,如 -u 118 2025-12)'
|
help='添加未统计数据(需同时指定月份,如 -u 118 2025-12)'
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--remove-unaccounted',
|
||||||
|
'-r',
|
||||||
|
metavar='TEU',
|
||||||
|
nargs='?',
|
||||||
|
const=None,
|
||||||
|
type=int,
|
||||||
|
help='去除未统计数据(需同时指定月份,如 -r 118 2025-12)。如果指定TEU值,则减少该数量;如果不指定,则删除整个记录'
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--month',
|
'--month',
|
||||||
'-m',
|
'-m',
|
||||||
metavar='YEAR-MONTH',
|
metavar='YEAR-MONTH',
|
||||||
help='指定月份(与 --unaccounted 配合使用)'
|
help='指定月份(与 --unaccounted 或 --remove-unaccounted 配合使用)'
|
||||||
)
|
)
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
@@ -333,6 +380,24 @@ def main():
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# 去除未统计数据
|
||||||
|
# 检查是否提供了 --remove-unaccounted 或 -r 参数
|
||||||
|
has_remove_arg = any(arg in sys.argv for arg in ['--remove-unaccounted', '-r'])
|
||||||
|
if has_remove_arg:
|
||||||
|
year_month = args.month or datetime.now().strftime('%Y-%m')
|
||||||
|
try:
|
||||||
|
# args.remove_unaccounted 可能是整数(指定TEU)或 None(未指定)
|
||||||
|
if isinstance(args.remove_unaccounted, int):
|
||||||
|
# 指定了TEU值,减少指定数量
|
||||||
|
remove_unaccounted(year_month, args.remove_unaccounted)
|
||||||
|
else:
|
||||||
|
# 未指定TEU值,删除整个记录
|
||||||
|
remove_unaccounted(year_month)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"去除未统计数据失败: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
return
|
||||||
|
|
||||||
# 执行功能
|
# 执行功能
|
||||||
try:
|
try:
|
||||||
if args.function == 'report' and args.date:
|
if args.function == 'report' and args.date:
|
||||||
|
|||||||
@@ -100,6 +100,25 @@ class DailyLogsDatabase(DatabaseBase):
|
|||||||
)
|
)
|
||||||
''')
|
''')
|
||||||
|
|
||||||
|
# 创建手动调整数据表
|
||||||
|
cursor.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS manual_adjustments (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
date TEXT NOT NULL, -- 调整适用的日期
|
||||||
|
ship_name TEXT NOT NULL, -- 船名
|
||||||
|
teu INTEGER NOT NULL, -- TEU数量
|
||||||
|
twenty_feet INTEGER DEFAULT 0, -- 20尺箱量
|
||||||
|
forty_feet INTEGER DEFAULT 0, -- 40尺箱量
|
||||||
|
adjustment_type TEXT NOT NULL, -- 'add' 或 'exclude'
|
||||||
|
note TEXT, -- 备注
|
||||||
|
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
''')
|
||||||
|
|
||||||
|
# 创建索引
|
||||||
|
cursor.execute('CREATE INDEX IF NOT EXISTS idx_manual_date ON manual_adjustments(date)')
|
||||||
|
cursor.execute('CREATE INDEX IF NOT EXISTS idx_manual_type ON manual_adjustments(adjustment_type)')
|
||||||
|
|
||||||
conn.commit()
|
conn.commit()
|
||||||
logger.debug("数据库表结构初始化完成")
|
logger.debug("数据库表结构初始化完成")
|
||||||
|
|
||||||
@@ -292,6 +311,74 @@ class DailyLogsDatabase(DatabaseBase):
|
|||||||
result = self.execute_query(query, (year_month,))
|
result = self.execute_query(query, (year_month,))
|
||||||
return result[0]['teu'] if result else 0
|
return result[0]['teu'] if result else 0
|
||||||
|
|
||||||
|
def reduce_unaccounted(self, year_month: str, teu_to_reduce: int) -> bool:
|
||||||
|
"""
|
||||||
|
减少指定月份的未统计数据
|
||||||
|
|
||||||
|
参数:
|
||||||
|
year_month: 年月字符串,格式 "2025-12"
|
||||||
|
teu_to_reduce: 要减少的TEU数量
|
||||||
|
|
||||||
|
返回:
|
||||||
|
是否成功
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 先获取当前值
|
||||||
|
current_teu = self.get_unaccounted(year_month)
|
||||||
|
|
||||||
|
# 计算新值(允许负数)
|
||||||
|
new_teu = current_teu - teu_to_reduce
|
||||||
|
|
||||||
|
if new_teu == 0:
|
||||||
|
# 如果减少后等于0,则删除记录
|
||||||
|
query = 'DELETE FROM monthly_unaccounted WHERE year_month = ?'
|
||||||
|
self.execute_update(query, (year_month,))
|
||||||
|
logger.info(f"减少未统计数据后删除记录: {year_month},原值: {current_teu}TEU,减少: {teu_to_reduce}TEU,新值: 0TEU")
|
||||||
|
else:
|
||||||
|
# 更新记录(允许负数)
|
||||||
|
query = '''
|
||||||
|
INSERT OR REPLACE INTO monthly_unaccounted
|
||||||
|
(year_month, teu, note, created_at)
|
||||||
|
VALUES (?, ?, ?, CURRENT_TIMESTAMP)
|
||||||
|
'''
|
||||||
|
# 保留原有备注
|
||||||
|
note_query = 'SELECT note FROM monthly_unaccounted WHERE year_month = ?'
|
||||||
|
note_result = self.execute_query(note_query, (year_month,))
|
||||||
|
note = note_result[0]['note'] if note_result else ''
|
||||||
|
|
||||||
|
self.execute_update(query, (year_month, new_teu, note))
|
||||||
|
logger.info(f"减少未统计数据: {year_month},原值: {current_teu}TEU,减少: {teu_to_reduce}TEU,新值: {new_teu}TEU")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"减少未统计数据失败: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def delete_unaccounted(self, year_month: str) -> bool:
|
||||||
|
"""
|
||||||
|
删除指定月份的未统计数据
|
||||||
|
|
||||||
|
参数:
|
||||||
|
year_month: 年月字符串,格式 "2025-12"
|
||||||
|
|
||||||
|
返回:
|
||||||
|
是否成功删除(如果记录不存在也返回True)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
query = 'DELETE FROM monthly_unaccounted WHERE year_month = ?'
|
||||||
|
result = self.execute_update(query, (year_month,))
|
||||||
|
if result > 0:
|
||||||
|
logger.info(f"删除未统计数据: {year_month}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
logger.warning(f"未找到 {year_month} 月的未统计数据")
|
||||||
|
return True # 记录不存在也算成功
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"删除未统计数据失败: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
def delete_by_date(self, date: str) -> int:
|
def delete_by_date(self, date: str) -> int:
|
||||||
"""
|
"""
|
||||||
删除指定日期的记录
|
删除指定日期的记录
|
||||||
@@ -305,6 +392,196 @@ class DailyLogsDatabase(DatabaseBase):
|
|||||||
query = 'DELETE FROM daily_handover_logs WHERE date = ?'
|
query = 'DELETE FROM daily_handover_logs WHERE date = ?'
|
||||||
return self.execute_update(query, (date,))
|
return self.execute_update(query, (date,))
|
||||||
|
|
||||||
|
def insert_manual_adjustment(self, date: str, ship_name: str, teu: int,
|
||||||
|
twenty_feet: int = 0, forty_feet: int = 0,
|
||||||
|
adjustment_type: str = 'add', note: str = '') -> bool:
|
||||||
|
"""
|
||||||
|
插入手动调整数据
|
||||||
|
|
||||||
|
参数:
|
||||||
|
date: 日期字符串
|
||||||
|
ship_name: 船名
|
||||||
|
teu: TEU数量
|
||||||
|
twenty_feet: 20尺箱量
|
||||||
|
forty_feet: 40尺箱量
|
||||||
|
adjustment_type: 调整类型 'add' 或 'exclude'
|
||||||
|
note: 备注
|
||||||
|
|
||||||
|
返回:
|
||||||
|
是否成功
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
query = '''
|
||||||
|
INSERT INTO manual_adjustments
|
||||||
|
(date, ship_name, teu, twenty_feet, forty_feet, adjustment_type, note, created_at)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
||||||
|
'''
|
||||||
|
params = (date, ship_name, teu, twenty_feet, forty_feet, adjustment_type, note)
|
||||||
|
self.execute_update(query, params)
|
||||||
|
logger.info(f"插入手动调整数据: {date} {ship_name} {teu}TEU ({adjustment_type})")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"插入手动调整数据失败: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_manual_adjustments(self, date: str) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
获取指定日期的所有手动调整数据
|
||||||
|
|
||||||
|
参数:
|
||||||
|
date: 日期字符串
|
||||||
|
|
||||||
|
返回:
|
||||||
|
手动调整数据列表
|
||||||
|
"""
|
||||||
|
query = '''
|
||||||
|
SELECT * FROM manual_adjustments
|
||||||
|
WHERE date = ? ORDER BY created_at DESC
|
||||||
|
'''
|
||||||
|
return self.execute_query(query, (date,))
|
||||||
|
|
||||||
|
def get_manual_adjustments_by_type(self, date: str, adjustment_type: str) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
获取指定日期和类型的调整数据
|
||||||
|
|
||||||
|
参数:
|
||||||
|
date: 日期字符串
|
||||||
|
adjustment_type: 调整类型 'add' 或 'exclude'
|
||||||
|
|
||||||
|
返回:
|
||||||
|
手动调整数据列表
|
||||||
|
"""
|
||||||
|
query = '''
|
||||||
|
SELECT * FROM manual_adjustments
|
||||||
|
WHERE date = ? AND adjustment_type = ? ORDER BY created_at DESC
|
||||||
|
'''
|
||||||
|
return self.execute_query(query, (date, adjustment_type))
|
||||||
|
|
||||||
|
def delete_manual_adjustment(self, adjustment_id: int) -> bool:
|
||||||
|
"""
|
||||||
|
删除指定ID的手动调整数据
|
||||||
|
|
||||||
|
参数:
|
||||||
|
adjustment_id: 调整记录ID
|
||||||
|
|
||||||
|
返回:
|
||||||
|
是否成功删除
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
query = 'DELETE FROM manual_adjustments WHERE id = ?'
|
||||||
|
result = self.execute_update(query, (adjustment_id,))
|
||||||
|
if result > 0:
|
||||||
|
logger.info(f"删除手动调整数据: ID={adjustment_id}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
logger.warning(f"未找到手动调整数据: ID={adjustment_id}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"删除手动调整数据失败: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def clear_manual_adjustments(self, date: str) -> int:
|
||||||
|
"""
|
||||||
|
清除指定日期的所有手动调整数据
|
||||||
|
|
||||||
|
参数:
|
||||||
|
date: 日期字符串
|
||||||
|
|
||||||
|
返回:
|
||||||
|
删除的记录数
|
||||||
|
"""
|
||||||
|
query = 'DELETE FROM manual_adjustments WHERE date = ?'
|
||||||
|
result = self.execute_update(query, (date,))
|
||||||
|
logger.info(f"清除 {date} 的手动调整数据: {result} 条记录")
|
||||||
|
return result
|
||||||
|
|
||||||
|
def get_daily_data_with_adjustments(self, date: str) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
获取指定日期的数据(包含手动调整)
|
||||||
|
|
||||||
|
参数:
|
||||||
|
date: 日期字符串
|
||||||
|
|
||||||
|
返回:
|
||||||
|
包含调整的每日数据字典
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 获取原始数据
|
||||||
|
logs = self.query_by_date(date)
|
||||||
|
|
||||||
|
# 获取手动调整数据
|
||||||
|
adjustments = self.get_manual_adjustments(date)
|
||||||
|
|
||||||
|
# 按船名汇总原始数据
|
||||||
|
ships: Dict[str, Dict[str, Any]] = {}
|
||||||
|
for log in logs:
|
||||||
|
ship = log['ship_name']
|
||||||
|
if ship not in ships:
|
||||||
|
ships[ship] = {
|
||||||
|
'teu': 0,
|
||||||
|
'twenty_feet': 0,
|
||||||
|
'forty_feet': 0,
|
||||||
|
'adjustments': []
|
||||||
|
}
|
||||||
|
if log.get('teu'):
|
||||||
|
ships[ship]['teu'] += log['teu']
|
||||||
|
if log.get('twenty_feet'):
|
||||||
|
ships[ship]['twenty_feet'] += log['twenty_feet']
|
||||||
|
if log.get('forty_feet'):
|
||||||
|
ships[ship]['forty_feet'] += log['forty_feet']
|
||||||
|
|
||||||
|
# 应用调整数据
|
||||||
|
total_adjustment_teu = 0
|
||||||
|
for adj in adjustments:
|
||||||
|
ship = adj['ship_name']
|
||||||
|
if ship not in ships:
|
||||||
|
ships[ship] = {
|
||||||
|
'teu': 0,
|
||||||
|
'twenty_feet': 0,
|
||||||
|
'forty_feet': 0,
|
||||||
|
'adjustments': []
|
||||||
|
}
|
||||||
|
|
||||||
|
# 记录调整
|
||||||
|
ships[ship]['adjustments'].append(adj)
|
||||||
|
|
||||||
|
# 根据调整类型计算
|
||||||
|
if adj['adjustment_type'] == 'add':
|
||||||
|
ships[ship]['teu'] += adj['teu']
|
||||||
|
ships[ship]['twenty_feet'] += adj['twenty_feet']
|
||||||
|
ships[ship]['forty_feet'] += adj['forty_feet']
|
||||||
|
total_adjustment_teu += adj['teu']
|
||||||
|
elif adj['adjustment_type'] == 'exclude':
|
||||||
|
ships[ship]['teu'] -= adj['teu']
|
||||||
|
ships[ship]['twenty_feet'] -= adj['twenty_feet']
|
||||||
|
ships[ship]['forty_feet'] -= adj['forty_feet']
|
||||||
|
total_adjustment_teu -= adj['teu']
|
||||||
|
|
||||||
|
# 计算总TEU
|
||||||
|
total_teu = sum(ship_data['teu'] for ship_data in ships.values())
|
||||||
|
|
||||||
|
return {
|
||||||
|
'date': date,
|
||||||
|
'ships': ships,
|
||||||
|
'total_teu': total_teu,
|
||||||
|
'ship_count': len(ships),
|
||||||
|
'adjustments': adjustments,
|
||||||
|
'total_adjustment_teu': total_adjustment_teu
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取包含调整的每日数据失败: {date}, 错误: {e}")
|
||||||
|
return {
|
||||||
|
'date': date,
|
||||||
|
'ships': {},
|
||||||
|
'total_teu': 0,
|
||||||
|
'ship_count': 0,
|
||||||
|
'adjustments': [],
|
||||||
|
'total_adjustment_teu': 0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# 测试代码
|
# 测试代码
|
||||||
|
|||||||
613
src/gui.py
613
src/gui.py
@@ -62,13 +62,14 @@ class OrbitInGUI:
|
|||||||
)
|
)
|
||||||
btn_fetch.pack(pady=5)
|
btn_fetch.pack(pady=5)
|
||||||
|
|
||||||
btn_fetch_debug = ttk.Button(
|
# 重置数据库按钮
|
||||||
|
btn_reset_db = ttk.Button(
|
||||||
left_frame,
|
left_frame,
|
||||||
text="获取 (Debug模式)",
|
text="重置数据库",
|
||||||
command=self.fetch_debug,
|
command=self.reset_database,
|
||||||
width=20
|
width=20
|
||||||
)
|
)
|
||||||
btn_fetch_debug.pack(pady=5)
|
btn_reset_db.pack(pady=5)
|
||||||
|
|
||||||
# 分隔线
|
# 分隔线
|
||||||
ttk.Separator(left_frame, orient=tk.HORIZONTAL).pack(fill=tk.X, pady=10)
|
ttk.Separator(left_frame, orient=tk.HORIZONTAL).pack(fill=tk.X, pady=10)
|
||||||
@@ -126,6 +127,32 @@ class OrbitInGUI:
|
|||||||
)
|
)
|
||||||
btn_unaccounted.pack(pady=5)
|
btn_unaccounted.pack(pady=5)
|
||||||
|
|
||||||
|
# 分隔线
|
||||||
|
ttk.Separator(left_frame, orient=tk.HORIZONTAL).pack(fill=tk.X, pady=5)
|
||||||
|
|
||||||
|
# 去除多余统计数据
|
||||||
|
ttk.Label(left_frame, text="去除多余统计数据:").pack(anchor=tk.W, pady=(10, 5))
|
||||||
|
|
||||||
|
remove_frame = ttk.Frame(left_frame)
|
||||||
|
remove_frame.pack(fill=tk.X, pady=5)
|
||||||
|
|
||||||
|
ttk.Label(remove_frame, text="月份:").pack(side=tk.LEFT)
|
||||||
|
month_entry2 = ttk.Entry(remove_frame, textvariable=self.month_var, width=8)
|
||||||
|
month_entry2.pack(side=tk.LEFT, padx=(5, 10))
|
||||||
|
|
||||||
|
ttk.Label(remove_frame, text="TEU:").pack(side=tk.LEFT)
|
||||||
|
self.remove_teu_var = tk.StringVar()
|
||||||
|
remove_teu_entry = ttk.Entry(remove_frame, textvariable=self.remove_teu_var, width=8)
|
||||||
|
remove_teu_entry.pack(side=tk.LEFT, padx=(5, 0))
|
||||||
|
|
||||||
|
btn_remove_unaccounted = ttk.Button(
|
||||||
|
left_frame,
|
||||||
|
text="去除",
|
||||||
|
command=self.remove_unaccounted,
|
||||||
|
width=20
|
||||||
|
)
|
||||||
|
btn_remove_unaccounted.pack(pady=5)
|
||||||
|
|
||||||
# 分隔线
|
# 分隔线
|
||||||
ttk.Separator(left_frame, orient=tk.HORIZONTAL).pack(fill=tk.X, pady=10)
|
ttk.Separator(left_frame, orient=tk.HORIZONTAL).pack(fill=tk.X, pady=10)
|
||||||
|
|
||||||
@@ -205,6 +232,15 @@ class OrbitInGUI:
|
|||||||
self.output_text.see(tk.END)
|
self.output_text.see(tk.END)
|
||||||
self.root.update()
|
self.root.update()
|
||||||
|
|
||||||
|
def is_month_last_day(self, date: datetime) -> bool:
|
||||||
|
"""判断是否为月份最后一天"""
|
||||||
|
next_day = date + timedelta(days=1)
|
||||||
|
return next_day.month != date.month
|
||||||
|
|
||||||
|
def is_month_first_day(self, date: datetime) -> bool:
|
||||||
|
"""判断是否为月份第一天"""
|
||||||
|
return date.day == 1
|
||||||
|
|
||||||
def clear_output(self):
|
def clear_output(self):
|
||||||
"""清空输出"""
|
"""清空输出"""
|
||||||
self.output_text.delete(1.0, tk.END)
|
self.output_text.delete(1.0, tk.END)
|
||||||
@@ -296,6 +332,9 @@ class OrbitInGUI:
|
|||||||
self.set_status("完成")
|
self.set_status("完成")
|
||||||
self.logger.info("数据获取完成")
|
self.logger.info("数据获取完成")
|
||||||
|
|
||||||
|
# 处理获取数据后的调整
|
||||||
|
self._handle_post_fetch_adjustment()
|
||||||
|
|
||||||
except ConfluenceClientError as e:
|
except ConfluenceClientError as e:
|
||||||
self.log_message(f"Confluence API 错误: {e}", is_error=True)
|
self.log_message(f"Confluence API 错误: {e}", is_error=True)
|
||||||
self.logger.error(f"Confluence API 错误: {e}")
|
self.logger.error(f"Confluence API 错误: {e}")
|
||||||
@@ -317,65 +356,197 @@ class OrbitInGUI:
|
|||||||
self.logger.error(f"未知错误: {e}", exc_info=True)
|
self.logger.error(f"未知错误: {e}", exc_info=True)
|
||||||
self.set_status("错误")
|
self.set_status("错误")
|
||||||
|
|
||||||
def fetch_debug(self):
|
|
||||||
"""Debug模式获取数据"""
|
|
||||||
self.set_status("正在获取 Debug 数据...")
|
|
||||||
self.log_message("使用本地 layout_output.txt 进行 Debug...")
|
|
||||||
self.logger.info("使用本地 layout_output.txt 进行 Debug...")
|
|
||||||
|
|
||||||
try:
|
def _handle_post_fetch_adjustment(self):
|
||||||
# 检查本地文件
|
"""处理获取数据后的调整"""
|
||||||
if os.path.exists('layout_output.txt'):
|
# 程序是在第二天打开获取昨天的数据
|
||||||
filepath = 'layout_output.txt'
|
# 所以使用昨天的日期来判断
|
||||||
elif os.path.exists('debug/layout_output.txt'):
|
yesterday = datetime.now() - timedelta(days=1)
|
||||||
filepath = 'debug/layout_output.txt'
|
|
||||||
else:
|
if self.is_month_first_day(yesterday):
|
||||||
self.log_message("错误: 未找到 layout_output.txt 文件", is_error=True)
|
# 昨天是月初1号:询问是否添加上月数据
|
||||||
self.logger.error("未找到 layout_output.txt 文件")
|
self._show_add_data_dialog(yesterday)
|
||||||
|
elif self.is_month_last_day(yesterday):
|
||||||
|
# 昨天是月底最后一天:询问是否剔除12点后数据
|
||||||
|
self._show_exclude_data_dialog(yesterday)
|
||||||
|
|
||||||
|
def _show_add_data_dialog(self, yesterday):
|
||||||
|
"""显示添加数据对话框(昨天是月初1号)"""
|
||||||
|
yesterday_str = yesterday.strftime('%Y-%m-%d')
|
||||||
|
yesterday_month = yesterday.strftime('%m')
|
||||||
|
|
||||||
|
if not messagebox.askyesno("添加数据",
|
||||||
|
f"昨天({yesterday_str})是本月1号,是否需要添加上月的作业数据?\n\n"
|
||||||
|
"注意:添加的数据将计入上月统计。\n"
|
||||||
|
"请确保输入正确的船名、TEU和尺寸箱量。"):
|
||||||
|
self.log_message("用户取消添加数据")
|
||||||
|
self.logger.info("用户取消添加数据")
|
||||||
return
|
return
|
||||||
|
|
||||||
self.log_message(f"使用文件: {filepath}")
|
# 显示输入对话框
|
||||||
self.logger.info(f"使用文件: {filepath}")
|
dialog = AddDataDialog(self.root, self, yesterday)
|
||||||
|
self.root.wait_window(dialog)
|
||||||
|
|
||||||
with open(filepath, 'r', encoding='utf-8') as f:
|
if dialog.result:
|
||||||
text = f.read()
|
# 保存到数据库
|
||||||
|
try:
|
||||||
self.log_message(f"读取完成,共 {len(text)} 字符")
|
|
||||||
self.logger.info(f"读取完成,共 {len(text)} 字符")
|
|
||||||
|
|
||||||
# 解析数据
|
|
||||||
self.log_message("正在解析日志数据...")
|
|
||||||
self.logger.info("正在解析日志数据...")
|
|
||||||
parser = HandoverLogParser()
|
|
||||||
logs = parser.parse(text)
|
|
||||||
self.log_message(f"解析到 {len(logs)} 条记录")
|
|
||||||
self.logger.info(f"解析到 {len(logs)} 条记录")
|
|
||||||
|
|
||||||
if logs:
|
|
||||||
self.log_message("正在保存到数据库...")
|
|
||||||
self.logger.info("正在保存到数据库...")
|
|
||||||
db = DailyLogsDatabase()
|
db = DailyLogsDatabase()
|
||||||
count = db.insert_many([log.to_dict() for log in logs])
|
success = db.insert_manual_adjustment(
|
||||||
self.log_message(f"已保存 {count} 条记录")
|
date=dialog.result['date'],
|
||||||
self.logger.info(f"已保存 {count} 条记录")
|
ship_name=dialog.result['ship_name'],
|
||||||
|
teu=dialog.result['teu'],
|
||||||
|
twenty_feet=dialog.result['twenty_feet'],
|
||||||
|
forty_feet=dialog.result['forty_feet'],
|
||||||
|
adjustment_type='add',
|
||||||
|
note=dialog.result['note']
|
||||||
|
)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
self.log_message(f"已添加数据: {dialog.result['ship_name']} {dialog.result['teu']}TEU")
|
||||||
|
self.logger.info(f"已添加数据: {dialog.result['ship_name']} {dialog.result['teu']}TEU")
|
||||||
|
# 刷新日报显示
|
||||||
|
self.generate_today_report()
|
||||||
|
else:
|
||||||
|
self.log_message("添加数据失败", is_error=True)
|
||||||
|
self.logger.error("添加数据失败")
|
||||||
|
except Exception as e:
|
||||||
|
self.log_message(f"添加数据时出错: {e}", is_error=True)
|
||||||
|
self.logger.error(f"添加数据时出错: {e}")
|
||||||
|
|
||||||
|
def _show_exclude_data_dialog(self, yesterday):
|
||||||
|
"""显示剔除数据对话框(昨天是月底最后一天)"""
|
||||||
|
yesterday_str = yesterday.strftime('%Y-%m-%d')
|
||||||
|
|
||||||
|
if not messagebox.askyesno("剔除数据",
|
||||||
|
f"昨天({yesterday_str})是本月最后一天,是否需要剔除12点后的作业数据?\n\n"
|
||||||
|
"注意:剔除的数据将从本月统计中移除,并自动添加到次月1号。\n"
|
||||||
|
"请确保输入正确的船名、TEU和尺寸箱量。"):
|
||||||
|
self.log_message("用户取消剔除数据")
|
||||||
|
self.logger.info("用户取消剔除数据")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 显示输入对话框
|
||||||
|
dialog = ExcludeDataDialog(self.root, self, yesterday)
|
||||||
|
self.root.wait_window(dialog)
|
||||||
|
|
||||||
|
if dialog.result:
|
||||||
|
# 保存到数据库
|
||||||
|
try:
|
||||||
|
db = DailyLogsDatabase()
|
||||||
|
|
||||||
|
# 1. 保存剔除数据(从月底最后一天扣除)
|
||||||
|
exclude_success = db.insert_manual_adjustment(
|
||||||
|
date=dialog.result['date'],
|
||||||
|
ship_name=dialog.result['ship_name'],
|
||||||
|
teu=dialog.result['teu'],
|
||||||
|
twenty_feet=dialog.result['twenty_feet'],
|
||||||
|
forty_feet=dialog.result['forty_feet'],
|
||||||
|
adjustment_type='exclude',
|
||||||
|
note=dialog.result['note']
|
||||||
|
)
|
||||||
|
|
||||||
|
if exclude_success:
|
||||||
|
self.log_message(f"已剔除数据: {dialog.result['ship_name']} {dialog.result['teu']}TEU")
|
||||||
|
self.logger.info(f"已剔除数据: {dialog.result['ship_name']} {dialog.result['teu']}TEU")
|
||||||
|
|
||||||
|
# 2. 自动将相同数据添加到次月1号
|
||||||
|
# 计算次月1号日期
|
||||||
|
next_month_first_day = (yesterday + timedelta(days=1)).replace(day=1)
|
||||||
|
next_month_first_day_str = next_month_first_day.strftime('%Y-%m-%d')
|
||||||
|
|
||||||
|
# 添加数据到次月1号
|
||||||
|
add_success = db.insert_manual_adjustment(
|
||||||
|
date=next_month_first_day_str,
|
||||||
|
ship_name=dialog.result['ship_name'],
|
||||||
|
teu=dialog.result['teu'],
|
||||||
|
twenty_feet=dialog.result['twenty_feet'],
|
||||||
|
forty_feet=dialog.result['forty_feet'],
|
||||||
|
adjustment_type='add',
|
||||||
|
note=f"从{yesterday_str}转移的数据: {dialog.result['note']}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if add_success:
|
||||||
|
self.log_message(f"已自动添加到次月1号({next_month_first_day_str}): {dialog.result['ship_name']} {dialog.result['teu']}TEU")
|
||||||
|
self.logger.info(f"已自动添加到次月1号({next_month_first_day_str}): {dialog.result['ship_name']} {dialog.result['teu']}TEU")
|
||||||
|
else:
|
||||||
|
self.log_message("自动添加数据到次月1号失败", is_error=True)
|
||||||
|
self.logger.error("自动添加数据到次月1号失败")
|
||||||
|
|
||||||
# 刷新日报显示
|
# 刷新日报显示
|
||||||
self.generate_today_report()
|
self.generate_today_report()
|
||||||
|
else:
|
||||||
self.set_status("完成")
|
self.log_message("剔除数据失败", is_error=True)
|
||||||
self.logger.info("Debug 数据获取完成")
|
self.logger.error("剔除数据失败")
|
||||||
|
|
||||||
except LogParserError as e:
|
|
||||||
self.log_message(f"日志解析错误: {e}", is_error=True)
|
|
||||||
self.logger.error(f"日志解析错误: {e}")
|
|
||||||
self.set_status("错误")
|
|
||||||
except DatabaseConnectionError as e:
|
|
||||||
self.log_message(f"数据库连接错误: {e}", is_error=True)
|
|
||||||
self.logger.error(f"数据库连接错误: {e}")
|
|
||||||
self.set_status("错误")
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.log_message(f"未知错误: {e}", is_error=True)
|
self.log_message(f"剔除数据时出错: {e}", is_error=True)
|
||||||
self.logger.error(f"未知错误: {e}", exc_info=True)
|
self.logger.error(f"剔除数据时出错: {e}")
|
||||||
|
|
||||||
|
def reset_database(self):
|
||||||
|
"""重置数据库并获取新数据"""
|
||||||
|
# 确认对话框
|
||||||
|
if not messagebox.askyesno("确认重置",
|
||||||
|
"确定要重置数据库吗?\n\n"
|
||||||
|
"这将删除所有现有数据并重新获取。\n"
|
||||||
|
"此操作不可撤销!"):
|
||||||
|
self.log_message("重置操作已取消")
|
||||||
|
self.logger.info("用户取消数据库重置")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.set_status("正在重置数据库...")
|
||||||
|
self.log_message("开始重置数据库...")
|
||||||
|
self.logger.info("开始重置数据库...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 获取数据库路径
|
||||||
|
db_path = config.DATABASE_PATH
|
||||||
|
|
||||||
|
# 检查数据库文件是否存在
|
||||||
|
if os.path.exists(db_path):
|
||||||
|
# 关闭所有数据库连接
|
||||||
|
try:
|
||||||
|
# 尝试关闭数据库连接
|
||||||
|
import sqlite3
|
||||||
|
# 删除数据库文件
|
||||||
|
os.remove(db_path)
|
||||||
|
self.log_message(f"已删除数据库文件: {db_path}")
|
||||||
|
self.logger.info(f"已删除数据库文件: {db_path}")
|
||||||
|
except Exception as e:
|
||||||
|
self.log_message(f"删除数据库文件时出错: {e}", is_error=True)
|
||||||
|
self.logger.error(f"删除数据库文件时出错: {e}")
|
||||||
|
self.set_status("错误")
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
self.log_message("数据库文件不存在,无需删除")
|
||||||
|
self.logger.info("数据库文件不存在,无需删除")
|
||||||
|
|
||||||
|
# 创建数据目录(如果不存在)
|
||||||
|
data_dir = os.path.dirname(db_path)
|
||||||
|
if data_dir and not os.path.exists(data_dir):
|
||||||
|
os.makedirs(data_dir)
|
||||||
|
self.log_message(f"已创建数据目录: {data_dir}")
|
||||||
|
self.logger.info(f"已创建数据目录: {data_dir}")
|
||||||
|
|
||||||
|
# 调用 fetch_data 获取新数据
|
||||||
|
self.log_message("正在获取新数据...")
|
||||||
|
self.logger.info("正在获取新数据...")
|
||||||
|
|
||||||
|
# 使用线程执行获取操作,避免GUI冻结
|
||||||
|
def fetch_in_thread():
|
||||||
|
try:
|
||||||
|
self.fetch_data()
|
||||||
|
self.log_message("数据库重置完成")
|
||||||
|
self.logger.info("数据库重置完成")
|
||||||
|
except Exception as e:
|
||||||
|
self.log_message(f"获取新数据时出错: {e}", is_error=True)
|
||||||
|
self.logger.error(f"获取新数据时出错: {e}")
|
||||||
|
|
||||||
|
# 启动线程
|
||||||
|
thread = threading.Thread(target=fetch_in_thread, daemon=True)
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.log_message(f"重置数据库时出错: {e}", is_error=True)
|
||||||
|
self.logger.error(f"重置数据库时出错: {e}", exc_info=True)
|
||||||
self.set_status("错误")
|
self.set_status("错误")
|
||||||
|
|
||||||
def generate_report(self):
|
def generate_report(self):
|
||||||
@@ -479,6 +650,78 @@ class OrbitInGUI:
|
|||||||
self.logger.error(f"未知错误: {e}", exc_info=True)
|
self.logger.error(f"未知错误: {e}", exc_info=True)
|
||||||
self.set_status("错误")
|
self.set_status("错误")
|
||||||
|
|
||||||
|
def remove_unaccounted(self):
|
||||||
|
"""去除未统计数据"""
|
||||||
|
year_month = self.month_var.get().strip()
|
||||||
|
teu_str = self.remove_teu_var.get().strip()
|
||||||
|
|
||||||
|
if not year_month:
|
||||||
|
self.log_message("错误: 请输入月份", is_error=True)
|
||||||
|
self.logger.error("未输入月份")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 确认对话框
|
||||||
|
if teu_str:
|
||||||
|
try:
|
||||||
|
teu_to_reduce = int(teu_str)
|
||||||
|
confirm_msg = f"确定要减少 {year_month} 月的 {teu_to_reduce}TEU 未统计数据吗?"
|
||||||
|
operation_type = "减少"
|
||||||
|
except ValueError:
|
||||||
|
self.log_message("错误: TEU 必须是数字", is_error=True)
|
||||||
|
self.logger.error(f"TEU 不是数字: {teu_str}")
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
confirm_msg = f"确定要删除 {year_month} 月的未统计数据吗?"
|
||||||
|
operation_type = "删除"
|
||||||
|
teu_to_reduce = None
|
||||||
|
|
||||||
|
if not messagebox.askyesno("确认", confirm_msg):
|
||||||
|
self.log_message("操作已取消")
|
||||||
|
self.logger.info("用户取消操作")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.set_status(f"正在{operation_type}...")
|
||||||
|
if teu_to_reduce:
|
||||||
|
self.log_message(f"{operation_type} {year_month} 月未统计数据: {teu_to_reduce}TEU")
|
||||||
|
self.logger.info(f"{operation_type} {year_month} 月未统计数据: {teu_to_reduce}TEU")
|
||||||
|
else:
|
||||||
|
self.log_message(f"{operation_type} {year_month} 月未统计数据")
|
||||||
|
self.logger.info(f"{operation_type} {year_month} 月未统计数据")
|
||||||
|
|
||||||
|
try:
|
||||||
|
db = DailyLogsDatabase()
|
||||||
|
|
||||||
|
if teu_to_reduce:
|
||||||
|
# 减少指定数量的TEU
|
||||||
|
result = db.reduce_unaccounted(year_month, teu_to_reduce)
|
||||||
|
success_msg = f"{operation_type}成功!"
|
||||||
|
error_msg = f"{operation_type}失败!"
|
||||||
|
else:
|
||||||
|
# 删除整个记录
|
||||||
|
result = db.delete_unaccounted(year_month)
|
||||||
|
success_msg = f"{operation_type}成功!"
|
||||||
|
error_msg = f"{operation_type}失败!"
|
||||||
|
|
||||||
|
if result:
|
||||||
|
self.log_message(success_msg)
|
||||||
|
self.logger.info(f"未统计数据{operation_type}成功: {year_month}")
|
||||||
|
# 刷新日报显示
|
||||||
|
self.generate_today_report()
|
||||||
|
else:
|
||||||
|
self.log_message(error_msg, is_error=True)
|
||||||
|
self.logger.error(f"未统计数据{operation_type}失败: {year_month}")
|
||||||
|
|
||||||
|
self.set_status("完成")
|
||||||
|
|
||||||
|
except DatabaseConnectionError as e:
|
||||||
|
self.log_message(f"数据库连接错误: {e}", is_error=True)
|
||||||
|
self.logger.error(f"数据库连接错误: {e}")
|
||||||
|
self.set_status("错误")
|
||||||
|
except Exception as e:
|
||||||
|
self.log_message(f"未知错误: {e}", is_error=True)
|
||||||
|
self.logger.error(f"未知错误: {e}", exc_info=True)
|
||||||
|
self.set_status("错误")
|
||||||
|
|
||||||
def auto_fetch_data(self):
|
def auto_fetch_data(self):
|
||||||
"""自动获取新数据(GUI启动时调用)"""
|
"""自动获取新数据(GUI启动时调用)"""
|
||||||
self.set_status("正在自动获取新数据...")
|
self.set_status("正在自动获取新数据...")
|
||||||
@@ -551,6 +794,9 @@ class OrbitInGUI:
|
|||||||
count = db.insert_many([log.to_dict() for log in logs])
|
count = db.insert_many([log.to_dict() for log in logs])
|
||||||
self.log_message(f"已保存 {count} 条新记录")
|
self.log_message(f"已保存 {count} 条新记录")
|
||||||
self.logger.info(f"已保存 {count} 条新记录")
|
self.logger.info(f"已保存 {count} 条新记录")
|
||||||
|
|
||||||
|
# 处理获取数据后的调整
|
||||||
|
self._handle_post_fetch_adjustment()
|
||||||
else:
|
else:
|
||||||
self.log_message("未解析到新记录")
|
self.log_message("未解析到新记录")
|
||||||
self.logger.warning("未解析到新记录")
|
self.logger.warning("未解析到新记录")
|
||||||
@@ -633,6 +879,265 @@ class OrbitInGUI:
|
|||||||
self.set_status("错误")
|
self.set_status("错误")
|
||||||
|
|
||||||
|
|
||||||
|
class AddDataDialog(tk.Toplevel):
|
||||||
|
"""添加数据对话框"""
|
||||||
|
|
||||||
|
def __init__(self, parent, gui, yesterday):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.title("添加上月作业数据")
|
||||||
|
self.gui = gui
|
||||||
|
self.yesterday = yesterday
|
||||||
|
self.result = None
|
||||||
|
|
||||||
|
# 设置对话框大小和位置
|
||||||
|
self.geometry("400x350")
|
||||||
|
self.resizable(False, False)
|
||||||
|
|
||||||
|
# 使对话框模态
|
||||||
|
self.transient(parent)
|
||||||
|
self.grab_set()
|
||||||
|
|
||||||
|
# 计算上月日期(昨天是1号,所以添加的数据应该是上个月的)
|
||||||
|
last_month = yesterday.replace(day=1) - timedelta(days=1)
|
||||||
|
last_month_date = last_month.strftime('%Y-%m-%d')
|
||||||
|
|
||||||
|
# 创建输入字段
|
||||||
|
frame = ttk.Frame(self, padding="20")
|
||||||
|
frame.pack(fill=tk.BOTH, expand=True)
|
||||||
|
|
||||||
|
# 日期(固定为上个月)
|
||||||
|
ttk.Label(frame, text="日期:").grid(row=0, column=0, sticky=tk.W, pady=5)
|
||||||
|
self.date_var = tk.StringVar(value=last_month_date)
|
||||||
|
date_entry = ttk.Entry(frame, textvariable=self.date_var, width=15, state='readonly')
|
||||||
|
date_entry.grid(row=0, column=1, sticky=tk.W, pady=5)
|
||||||
|
ttk.Label(frame, text="(上个月日期,不可修改)").grid(row=0, column=2, sticky=tk.W, pady=5)
|
||||||
|
|
||||||
|
# 船名
|
||||||
|
ttk.Label(frame, text="船名:").grid(row=1, column=0, sticky=tk.W, pady=5)
|
||||||
|
self.ship_var = tk.StringVar()
|
||||||
|
ship_entry = ttk.Entry(frame, textvariable=self.ship_var, width=20)
|
||||||
|
ship_entry.grid(row=1, column=1, sticky=tk.W, pady=5)
|
||||||
|
|
||||||
|
# TEU
|
||||||
|
ttk.Label(frame, text="TEU:").grid(row=2, column=0, sticky=tk.W, pady=5)
|
||||||
|
self.teu_var = tk.StringVar()
|
||||||
|
teu_entry = ttk.Entry(frame, textvariable=self.teu_var, width=10)
|
||||||
|
teu_entry.grid(row=2, column=1, sticky=tk.W, pady=5)
|
||||||
|
|
||||||
|
# 20尺箱量
|
||||||
|
ttk.Label(frame, text="20尺箱量:").grid(row=3, column=0, sticky=tk.W, pady=5)
|
||||||
|
self.twenty_var = tk.StringVar(value="0")
|
||||||
|
twenty_entry = ttk.Entry(frame, textvariable=self.twenty_var, width=10)
|
||||||
|
twenty_entry.grid(row=3, column=1, sticky=tk.W, pady=5)
|
||||||
|
|
||||||
|
# 40尺箱量
|
||||||
|
ttk.Label(frame, text="40尺箱量:").grid(row=4, column=0, sticky=tk.W, pady=5)
|
||||||
|
self.forty_var = tk.StringVar(value="0")
|
||||||
|
forty_entry = ttk.Entry(frame, textvariable=self.forty_var, width=10)
|
||||||
|
forty_entry.grid(row=4, column=1, sticky=tk.W, pady=5)
|
||||||
|
|
||||||
|
# 备注
|
||||||
|
ttk.Label(frame, text="备注:").grid(row=5, column=0, sticky=tk.W, pady=5)
|
||||||
|
self.note_var = tk.StringVar()
|
||||||
|
note_entry = ttk.Entry(frame, textvariable=self.note_var, width=30)
|
||||||
|
note_entry.grid(row=5, column=1, sticky=tk.W, pady=5)
|
||||||
|
|
||||||
|
# 按钮
|
||||||
|
button_frame = ttk.Frame(frame)
|
||||||
|
button_frame.grid(row=6, column=0, columnspan=2, pady=20)
|
||||||
|
|
||||||
|
ttk.Button(button_frame, text="确定", command=self.on_ok).pack(side=tk.LEFT, padx=10)
|
||||||
|
ttk.Button(button_frame, text="取消", command=self.on_cancel).pack(side=tk.LEFT, padx=10)
|
||||||
|
|
||||||
|
# 绑定回车键
|
||||||
|
self.bind('<Return>', lambda e: self.on_ok())
|
||||||
|
self.bind('<Escape>', lambda e: self.on_cancel())
|
||||||
|
|
||||||
|
# 焦点设置
|
||||||
|
ship_entry.focus_set()
|
||||||
|
|
||||||
|
def on_ok(self):
|
||||||
|
"""确定按钮处理"""
|
||||||
|
try:
|
||||||
|
# 验证输入
|
||||||
|
date = self.date_var.get().strip()
|
||||||
|
ship_name = self.ship_var.get().strip()
|
||||||
|
teu_str = self.teu_var.get().strip()
|
||||||
|
twenty_str = self.twenty_var.get().strip()
|
||||||
|
forty_str = self.forty_var.get().strip()
|
||||||
|
note = self.note_var.get().strip()
|
||||||
|
|
||||||
|
if not date:
|
||||||
|
messagebox.showerror("错误", "请输入日期")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not ship_name:
|
||||||
|
messagebox.showerror("错误", "请输入船名")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not teu_str:
|
||||||
|
messagebox.showerror("错误", "请输入TEU")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 验证数字
|
||||||
|
teu = int(teu_str)
|
||||||
|
twenty_feet = int(twenty_str) if twenty_str else 0
|
||||||
|
forty_feet = int(forty_str) if forty_str else 0
|
||||||
|
|
||||||
|
if teu <= 0:
|
||||||
|
messagebox.showerror("错误", "TEU必须大于0")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 保存结果
|
||||||
|
self.result = {
|
||||||
|
'date': date,
|
||||||
|
'ship_name': ship_name,
|
||||||
|
'teu': teu,
|
||||||
|
'twenty_feet': twenty_feet,
|
||||||
|
'forty_feet': forty_feet,
|
||||||
|
'note': note
|
||||||
|
}
|
||||||
|
|
||||||
|
self.destroy()
|
||||||
|
|
||||||
|
except ValueError:
|
||||||
|
messagebox.showerror("错误", "请输入有效的数字")
|
||||||
|
|
||||||
|
def on_cancel(self):
|
||||||
|
"""取消按钮处理"""
|
||||||
|
self.result = None
|
||||||
|
self.destroy()
|
||||||
|
|
||||||
|
|
||||||
|
class ExcludeDataDialog(tk.Toplevel):
|
||||||
|
"""剔除数据对话框"""
|
||||||
|
|
||||||
|
def __init__(self, parent, gui, yesterday):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.title("剔除12点后作业数据")
|
||||||
|
self.gui = gui
|
||||||
|
self.yesterday = yesterday
|
||||||
|
self.result = None
|
||||||
|
|
||||||
|
# 设置对话框大小和位置
|
||||||
|
self.geometry("400x350")
|
||||||
|
self.resizable(False, False)
|
||||||
|
|
||||||
|
# 使对话框模态
|
||||||
|
self.transient(parent)
|
||||||
|
self.grab_set()
|
||||||
|
|
||||||
|
# 昨天日期(月底最后一天)
|
||||||
|
yesterday_date = yesterday.strftime('%Y-%m-%d')
|
||||||
|
|
||||||
|
# 创建输入字段
|
||||||
|
frame = ttk.Frame(self, padding="20")
|
||||||
|
frame.pack(fill=tk.BOTH, expand=True)
|
||||||
|
|
||||||
|
# 日期(固定为昨天)
|
||||||
|
ttk.Label(frame, text="日期:").grid(row=0, column=0, sticky=tk.W, pady=5)
|
||||||
|
self.date_var = tk.StringVar(value=yesterday_date)
|
||||||
|
date_entry = ttk.Entry(frame, textvariable=self.date_var, width=15, state='readonly')
|
||||||
|
date_entry.grid(row=0, column=1, sticky=tk.W, pady=5)
|
||||||
|
ttk.Label(frame, text="(昨天日期,不可修改)").grid(row=0, column=2, sticky=tk.W, pady=5)
|
||||||
|
|
||||||
|
# 船名
|
||||||
|
ttk.Label(frame, text="船名:").grid(row=1, column=0, sticky=tk.W, pady=5)
|
||||||
|
self.ship_var = tk.StringVar()
|
||||||
|
ship_entry = ttk.Entry(frame, textvariable=self.ship_var, width=20)
|
||||||
|
ship_entry.grid(row=1, column=1, sticky=tk.W, pady=5)
|
||||||
|
|
||||||
|
# TEU
|
||||||
|
ttk.Label(frame, text="TEU:").grid(row=2, column=0, sticky=tk.W, pady=5)
|
||||||
|
self.teu_var = tk.StringVar()
|
||||||
|
teu_entry = ttk.Entry(frame, textvariable=self.teu_var, width=10)
|
||||||
|
teu_entry.grid(row=2, column=1, sticky=tk.W, pady=5)
|
||||||
|
|
||||||
|
# 20尺箱量
|
||||||
|
ttk.Label(frame, text="20尺箱量:").grid(row=3, column=0, sticky=tk.W, pady=5)
|
||||||
|
self.twenty_var = tk.StringVar(value="0")
|
||||||
|
twenty_entry = ttk.Entry(frame, textvariable=self.twenty_var, width=10)
|
||||||
|
twenty_entry.grid(row=3, column=1, sticky=tk.W, pady=5)
|
||||||
|
|
||||||
|
# 40尺箱量
|
||||||
|
ttk.Label(frame, text="40尺箱量:").grid(row=4, column=0, sticky=tk.W, pady=5)
|
||||||
|
self.forty_var = tk.StringVar(value="0")
|
||||||
|
forty_entry = ttk.Entry(frame, textvariable=self.forty_var, width=10)
|
||||||
|
forty_entry.grid(row=4, column=1, sticky=tk.W, pady=5)
|
||||||
|
|
||||||
|
# 备注
|
||||||
|
ttk.Label(frame, text="备注:").grid(row=5, column=0, sticky=tk.W, pady=5)
|
||||||
|
self.note_var = tk.StringVar(value="剔除12点后数据")
|
||||||
|
note_entry = ttk.Entry(frame, textvariable=self.note_var, width=30)
|
||||||
|
note_entry.grid(row=5, column=1, sticky=tk.W, pady=5)
|
||||||
|
|
||||||
|
# 按钮
|
||||||
|
button_frame = ttk.Frame(frame)
|
||||||
|
button_frame.grid(row=6, column=0, columnspan=2, pady=20)
|
||||||
|
|
||||||
|
ttk.Button(button_frame, text="确定", command=self.on_ok).pack(side=tk.LEFT, padx=10)
|
||||||
|
ttk.Button(button_frame, text="取消", command=self.on_cancel).pack(side=tk.LEFT, padx=10)
|
||||||
|
|
||||||
|
# 绑定回车键
|
||||||
|
self.bind('<Return>', lambda e: self.on_ok())
|
||||||
|
self.bind('<Escape>', lambda e: self.on_cancel())
|
||||||
|
|
||||||
|
# 焦点设置
|
||||||
|
ship_entry.focus_set()
|
||||||
|
|
||||||
|
def on_ok(self):
|
||||||
|
"""确定按钮处理"""
|
||||||
|
try:
|
||||||
|
# 验证输入
|
||||||
|
date = self.date_var.get().strip()
|
||||||
|
ship_name = self.ship_var.get().strip()
|
||||||
|
teu_str = self.teu_var.get().strip()
|
||||||
|
twenty_str = self.twenty_var.get().strip()
|
||||||
|
forty_str = self.forty_var.get().strip()
|
||||||
|
note = self.note_var.get().strip()
|
||||||
|
|
||||||
|
if not date:
|
||||||
|
messagebox.showerror("错误", "请输入日期")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not ship_name:
|
||||||
|
messagebox.showerror("错误", "请输入船名")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not teu_str:
|
||||||
|
messagebox.showerror("错误", "请输入TEU")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 验证数字
|
||||||
|
teu = int(teu_str)
|
||||||
|
twenty_feet = int(twenty_str) if twenty_str else 0
|
||||||
|
forty_feet = int(forty_str) if forty_str else 0
|
||||||
|
|
||||||
|
if teu <= 0:
|
||||||
|
messagebox.showerror("错误", "TEU必须大于0")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 保存结果
|
||||||
|
self.result = {
|
||||||
|
'date': date,
|
||||||
|
'ship_name': ship_name,
|
||||||
|
'teu': teu,
|
||||||
|
'twenty_feet': twenty_feet,
|
||||||
|
'forty_feet': forty_feet,
|
||||||
|
'note': note
|
||||||
|
}
|
||||||
|
|
||||||
|
self.destroy()
|
||||||
|
|
||||||
|
except ValueError:
|
||||||
|
messagebox.showerror("错误", "请输入有效的数字")
|
||||||
|
|
||||||
|
def on_cancel(self):
|
||||||
|
"""取消按钮处理"""
|
||||||
|
self.result = None
|
||||||
|
self.destroy()
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""主函数"""
|
"""主函数"""
|
||||||
root = tk.Tk()
|
root = tk.Tk()
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class DailyReportGenerator:
|
|||||||
|
|
||||||
def get_daily_data(self, date: str) -> Dict[str, Any]:
|
def get_daily_data(self, date: str) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
获取指定日期的数据
|
获取指定日期的数据(包含手动调整)
|
||||||
|
|
||||||
参数:
|
参数:
|
||||||
date: 日期字符串,格式 "YYYY-MM-DD"
|
date: 日期字符串,格式 "YYYY-MM-DD"
|
||||||
@@ -61,6 +61,11 @@ class DailyReportGenerator:
|
|||||||
每日数据字典
|
每日数据字典
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
# 使用数据库的新方法获取包含调整的数据
|
||||||
|
if hasattr(self.db, 'get_daily_data_with_adjustments'):
|
||||||
|
return self.db.get_daily_data_with_adjustments(date)
|
||||||
|
|
||||||
|
# 降级处理:如果没有新方法,使用原始逻辑
|
||||||
logs = self.db.query_by_date(date)
|
logs = self.db.query_by_date(date)
|
||||||
|
|
||||||
# 按船名汇总TEU和尺寸箱量
|
# 按船名汇总TEU和尺寸箱量
|
||||||
@@ -86,7 +91,9 @@ class DailyReportGenerator:
|
|||||||
'date': date,
|
'date': date,
|
||||||
'ships': ships,
|
'ships': ships,
|
||||||
'total_teu': total_teu,
|
'total_teu': total_teu,
|
||||||
'ship_count': len(ships)
|
'ship_count': len(ships),
|
||||||
|
'adjustments': [],
|
||||||
|
'total_adjustment_teu': 0
|
||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -95,7 +102,9 @@ class DailyReportGenerator:
|
|||||||
'date': date,
|
'date': date,
|
||||||
'ships': {},
|
'ships': {},
|
||||||
'total_teu': 0,
|
'total_teu': 0,
|
||||||
'ship_count': 0
|
'ship_count': 0,
|
||||||
|
'adjustments': [],
|
||||||
|
'total_adjustment_teu': 0
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_monthly_stats(self, date: str) -> Dict[str, Any]:
|
def get_monthly_stats(self, date: str) -> Dict[str, Any]:
|
||||||
@@ -121,7 +130,7 @@ class DailyReportGenerator:
|
|||||||
and datetime.strptime(log['date'], '%Y-%m-%d').date() <= target_date
|
and datetime.strptime(log['date'], '%Y-%m-%d').date() <= target_date
|
||||||
]
|
]
|
||||||
|
|
||||||
# 按日期汇总
|
# 按日期汇总原始数据
|
||||||
daily_totals: Dict[str, int] = {}
|
daily_totals: Dict[str, int] = {}
|
||||||
for log in monthly_logs:
|
for log in monthly_logs:
|
||||||
d = log['date']
|
d = log['date']
|
||||||
@@ -130,6 +139,25 @@ class DailyReportGenerator:
|
|||||||
if log.get('teu'):
|
if log.get('teu'):
|
||||||
daily_totals[d] += log['teu']
|
daily_totals[d] += log['teu']
|
||||||
|
|
||||||
|
# 获取当月所有日期的调整数据
|
||||||
|
total_adjustment_teu = 0
|
||||||
|
adjustment_details: Dict[str, Dict[str, int]] = {}
|
||||||
|
|
||||||
|
# 获取当月所有日期的调整数据
|
||||||
|
for day in range(1, target_date.day + 1):
|
||||||
|
day_str = f"{year_month}-{day:02d}"
|
||||||
|
if day_str <= date: # 只统计到指定日期
|
||||||
|
# 获取该日期的调整数据
|
||||||
|
if hasattr(self.db, 'get_daily_data_with_adjustments'):
|
||||||
|
daily_data = self.db.get_daily_data_with_adjustments(day_str)
|
||||||
|
adjustment_teu = daily_data.get('total_adjustment_teu', 0)
|
||||||
|
if adjustment_teu != 0:
|
||||||
|
total_adjustment_teu += adjustment_teu
|
||||||
|
adjustment_details[day_str] = {
|
||||||
|
'adjustment_teu': adjustment_teu,
|
||||||
|
'total_teu': daily_data.get('total_teu', 0)
|
||||||
|
}
|
||||||
|
|
||||||
# 计算当月天数(已过的天数)
|
# 计算当月天数(已过的天数)
|
||||||
current_date = datetime.strptime(date, '%Y-%m-%d')
|
current_date = datetime.strptime(date, '%Y-%m-%d')
|
||||||
if current_date.day == config.FIRST_DAY_OF_MONTH_SPECIAL:
|
if current_date.day == config.FIRST_DAY_OF_MONTH_SPECIAL:
|
||||||
@@ -141,7 +169,8 @@ class DailyReportGenerator:
|
|||||||
unaccounted = self.db.get_unaccounted(year_month)
|
unaccounted = self.db.get_unaccounted(year_month)
|
||||||
|
|
||||||
planned = days_passed * config.DAILY_TARGET_TEU
|
planned = days_passed * config.DAILY_TARGET_TEU
|
||||||
actual = sum(daily_totals.values()) + unaccounted
|
# 实际作业量 = 原始数据总计 + 未统计数据 + 调整数据总计
|
||||||
|
actual = sum(daily_totals.values()) + unaccounted + total_adjustment_teu
|
||||||
|
|
||||||
completion = round(actual / planned * 100, 2) if planned > 0 else 0
|
completion = round(actual / planned * 100, 2) if planned > 0 else 0
|
||||||
|
|
||||||
@@ -151,8 +180,10 @@ class DailyReportGenerator:
|
|||||||
'planned': planned,
|
'planned': planned,
|
||||||
'actual': actual,
|
'actual': actual,
|
||||||
'unaccounted': unaccounted,
|
'unaccounted': unaccounted,
|
||||||
|
'adjustment_total': total_adjustment_teu,
|
||||||
'completion': completion,
|
'completion': completion,
|
||||||
'daily_totals': daily_totals
|
'daily_totals': daily_totals,
|
||||||
|
'adjustment_details': adjustment_details
|
||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -163,8 +194,10 @@ class DailyReportGenerator:
|
|||||||
'planned': 0,
|
'planned': 0,
|
||||||
'actual': 0,
|
'actual': 0,
|
||||||
'unaccounted': 0,
|
'unaccounted': 0,
|
||||||
|
'adjustment_total': 0,
|
||||||
'completion': 0,
|
'completion': 0,
|
||||||
'daily_totals': {}
|
'daily_totals': {},
|
||||||
|
'adjustment_details': {}
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_shift_personnel(self, date: str) -> Dict[str, str]:
|
def get_shift_personnel(self, date: str) -> Dict[str, str]:
|
||||||
@@ -279,6 +312,7 @@ class DailyReportGenerator:
|
|||||||
ship_lines.append(f"作业量:{teu}TEU({size_str})")
|
ship_lines.append(f"作业量:{teu}TEU({size_str})")
|
||||||
else:
|
else:
|
||||||
ship_lines.append(f"作业量:{teu}TEU")
|
ship_lines.append(f"作业量:{teu}TEU")
|
||||||
|
|
||||||
lines.extend(ship_lines)
|
lines.extend(ship_lines)
|
||||||
lines.append("")
|
lines.append("")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user