diff --git a/.env.example b/.env.example
deleted file mode 100755
index 731d04b..0000000
--- a/.env.example
+++ /dev/null
@@ -1,13 +0,0 @@
-# Redis 配置
-# 如果 Redis 在本地运行,使用 127.0.0.1 可以显著提升性能
-REDIS_HOST=127.0.0.1
-REDIS_PORT=6379
-REDIS_DB=0
-REDIS_PASSWORD=Zzh08165511
-
-# 管理员密码
-SUPERADMIN_PASSWORD=Zzh08165511
-
-# 服务器配置
-HOST=0.0.0.0
-PORT=5000
diff --git a/.gitignore b/.gitignore
old mode 100755
new mode 100644
index 143a146..5a86d2a
--- a/.gitignore
+++ b/.gitignore
@@ -1,64 +1,8 @@
-# Python
-__pycache__/
-*.py[cod]
-*$py.class
-*.so
-.Python
-env/
-venv/
-ENV/
+node_modules/
build/
-develop-eggs/
dist/
-downloads/
-eggs/
-.eggs/
-lib/
-lib64/
-parts/
-sdist/
-var/
-wheels/
-*.egg-info/
-.installed.cfg
-*.egg
-
-# Database
-*.db
-!server/data.db
-*.sqlite
-*.sqlite3
-
-# Logs
-*.log
-logs/
-server.log
-
-# Environment variables
-.env
-
-# IDE
-.vscode/
-.idea/
-*.swp
-*.swo
-*~
-
-# OS
+coverage/
.DS_Store
-Thumbs.db
-
-# Uploaded files
-frontend/assets/avatars/*
-!frontend/assets/avatars/.gitkeep
-
-# Redis dump
-dump.rdb
-
-# Temporary files
-*.tmp
-*.bak
-*.cache
-
-# Documentation
-README/
+*.log
+.env*
+!.env.example
diff --git a/.shared/ui-ux-pro-max/scripts/__pycache__/core.cpython-312.pyc b/.shared/ui-ux-pro-max/scripts/__pycache__/core.cpython-312.pyc
new file mode 100755
index 0000000..f23d00e
Binary files /dev/null and b/.shared/ui-ux-pro-max/scripts/__pycache__/core.cpython-312.pyc differ
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100755
index 0000000..7a73a41
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,2 @@
+{
+}
\ No newline at end of file
diff --git a/25年11月份对账单-易泰勒.xlsx b/25年11月份对账单-易泰勒.xlsx
deleted file mode 100755
index c5635a8..0000000
Binary files a/25年11月份对账单-易泰勒.xlsx and /dev/null differ
diff --git a/AI_ANALYSIS_README.md b/AI_ANALYSIS_README.md
deleted file mode 100755
index 31e5163..0000000
--- a/AI_ANALYSIS_README.md
+++ /dev/null
@@ -1,206 +0,0 @@
-# 智能生产报表AI分析功能
-
-## 功能概述
-
-智能报表功能利用AI分析生产数据,自动生成包含以下内容的生产报表:
-- 生产总览和关键指标
-- 平台产量分布
-- 质量问题分析
-- AI洞察和改进建议
-- 产量预测
-
-## 支持的AI提供商
-
-1. **OpenAI** - GPT-3.5/GPT-4
-2. **通义千问** - 阿里云
-3. **文心一言** - 百度
-4. **本地模型** - Ollama等
-
-## 配置步骤
-
-### 1. 后端配置
-
-#### 1.1 安装依赖
-```bash
-cd backend
-pip install aiohttp fastapi python-dotenv
-```
-
-#### 1.2 配置环境变量
-```bash
-# 复制配置文件
-cp .env.example .env
-
-# 编辑配置文件
-nano .env
-```
-
-#### 1.3 配置示例
-
-**使用OpenAI:**
-```
-AI_PROVIDER=openai
-OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx
-```
-
-**使用通义千问:**
-```
-AI_PROVIDER=qwen
-QWEN_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx
-```
-
-**使用文心一言:**
-```
-AI_PROVIDER=wenxin
-WENXIN_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxx
-WENXIN_SECRET_KEY=xxxxxxxxxxxxxxxxxxxxxxxx
-```
-
-**使用本地模型(Ollama):**
-```
-AI_PROVIDER=local
-LOCAL_AI_URL=http://localhost:11434/api/generate
-LOCAL_MODEL=llama2
-```
-
-### 2. 启动本地模型(可选)
-
-如果使用本地模型,需要先启动Ollama:
-
-```bash
-# 安装Ollama
-curl -fsSL https://ollama.ai/install.sh | sh
-
-# 启动Ollama服务
-ollama serve
-
-# 下载模型
-ollama pull llama2
-# 或下载中文模型
-ollama pull qwen:7b
-```
-
-### 3. 集成到后端
-
-在主应用中注册AI API路由:
-
-```python
-# main.py 或 app.py
-from api_ai import router as ai_router
-
-app.include_router(ai_router)
-```
-
-### 4. 前端使用
-
-前端已经集成AI报表组件,位于仪表盘页面。用户可以:
-- 点击"生成智能报表"按钮
-- 查看AI分析结果
-- 导出报表为文本文件
-- 刷新获取最新分析
-
-## API接口
-
-### 1. 分析生产数据
-```
-POST /api/ai/analyze
-```
-
-响应示例:
-```json
-{
- "summary": {
- "totalProduction": 15423,
- "goodRate": "98.5%",
- "trend": "up",
- "insights": [
- "本周产量较上周增长12%,主要得益于圆通订单的增加",
- "良品率保持在98%以上,质量管控效果显著",
- "建议:继续保持当前生产节奏,关注设备维护"
- ]
- },
- "platforms": {
- "pdd": {
- "count": 8934,
- "percentage": 57.9,
- "trend": "+5.2%"
- },
- "yt": {
- "count": 6489,
- "percentage": 42.1,
- "trend": "+18.7%"
- }
- },
- "quality": {
- "topIssues": [
- {"issue": "外观划痕", "count": 23, "percentage": "0.15%"},
- {"issue": "功能异常", "count": 12, "percentage": "0.08%"},
- {"issue": "包装破损", "count": 8, "percentage": "0.05%"}
- ]
- },
- "prediction": {
- "tomorrow": 2250,
- "weekRange": "15500-16500",
- "confidence": "92%"
- },
- "metadata": {
- "generated_at": "2024-01-20T10:30:00",
- "data_period": "最近30天",
- "total_records": 15423,
- "ai_provider": "openai"
- }
-}
-```
-
-### 2. 获取AI配置信息
-```
-GET /api/ai/config
-```
-
-### 3. 测试AI连接
-```
-POST /api/ai/test
-```
-
-### 4. 获取支持的提供商
-```
-GET /api/ai/providers
-```
-
-## 注意事项
-
-1. **API密钥安全**:请妥善保管API密钥,不要提交到版本控制
-2. **成本控制**:使用云端AI服务会产生费用,建议设置使用限额
-3. **响应时间**:AI分析可能需要几秒钟,请耐心等待
-4. **数据隐私**:生产数据会发送给AI服务,请确保符合隐私政策
-
-## 故障排除
-
-### 1. AI分析失败
-- 检查API密钥是否正确
-- 确认网络连接正常
-- 查看后端日志获取详细错误信息
-
-### 2. 本地模型连接失败
-- 确认Ollama服务正在运行
-- 检查模型是否已下载
-- 验证URL配置是否正确
-
-### 3. 分析结果不准确
-- 调整提示词(修改ai_service.py中的prompt)
-- 尝试不同的AI模型
-- 增加数据量或改进数据质量
-
-## 扩展功能
-
-1. **自定义分析维度**:可以修改提示词,添加更多分析维度
-2. **定时生成报表**:可以设置定时任务,自动生成日报/周报
-3. **报表模板**:支持多种报表格式(PDF、Excel等)
-4. **历史记录**:保存历史分析结果,支持对比查看
-
-## 技术支持
-
-如有问题,请查看:
-1. 后端日志:`/logs/ai_service.log`
-2. 浏览器控制台错误信息
-3. API响应状态和错误消息
diff --git a/FIX_SUMMARY.md b/FIX_SUMMARY.md
deleted file mode 100644
index 648cebc..0000000
--- a/FIX_SUMMARY.md
+++ /dev/null
@@ -1,45 +0,0 @@
-# 🎯 问题修复总结
-
-## 问题描述
-计算铨宝采购需求时,期初库存显示的是友辉的数据。例如:
-- 铨宝的"6*6*7.5支架 TS-1166VW"期初库存应该是 0,但显示 995(友辉的库存)
-- 铨宝的"0402B102K500NT"期初库存应该是 0,但显示 1243(友辉的库存)
-
-## 根本原因
-**`/api/purchase-demand/recalculate` 接口**在查询期初库存时,只用了 `material_code`,没有按 `factory` 过滤:
-
-```python
-# 错误的查询(第7408行)
-c.execute('SELECT stock_qty, min_package as stock_min_package FROM initial_stock WHERE material_code=?', (material_code,))
-```
-
-当友辉和铨宝有相同的物料编码时,这个查询会返回第一个匹配的记录(通常是友辉的),导致铨宝的采购需求使用了友辉的库存数据。
-
-## 修复方案
-在查询时添加工厂过滤条件:
-
-```python
-# 正确的查询(第7409行)
-factory = item['factory'] or '友辉' # 从采购需求记录中获取工厂字段
-c.execute('SELECT stock_qty, min_package as stock_min_package FROM initial_stock WHERE material_code=? AND factory=?', (material_code, factory))
-```
-
-## 已修复的接口
-1. ✅ `/api/purchase-demand/calculate` - 计算单个产品采购需求
-2. ✅ `/api/purchase-demand/calculate-all` - 计算所有产品采购需求
-3. ✅ `/api/purchase-demand/calculate-from-orders` - 从客户订单计算采购需求
-4. ✅ `/api/purchase-demand/recalculate` - 重新计算现有采购需求(**这个是你使用的接口**)
-
-## 验证步骤
-1. 重启服务器:`systemctl restart prod-mgmt`
-2. 在前端选择"铨宝"工厂
-3. 选择产品(如"AP05商超")并输入数量
-4. 点击"重新计算"
-5. 检查采购需求列表中的期初库存字段:
- - 铨宝的"6*6*7.5支架 TS-1166VW"应该显示 0 ✅
- - 铨宝的"0402B102K500NT"应该显示 0 ✅
- - 铨宝的"0402B104K160NT"应该显示 30124 ✅
-
-## 其他修复
-- ✅ 期初库存导入:支持同一物料在不同工厂独立存在
-- ✅ 数据备份恢复:导入/删除前自动备份,可一键恢复
diff --git a/RESTART_REQUIRED.md b/RESTART_REQUIRED.md
deleted file mode 100644
index 1e60367..0000000
--- a/RESTART_REQUIRED.md
+++ /dev/null
@@ -1,43 +0,0 @@
-# 🔄 需要重启服务器
-
-## 修复内容
-
-已修复以下问题:
-
-### 1. 期初库存导入
-- ✅ 支持同一物料编码在不同工厂独立存在
-- ✅ 导入时按 `(material_code, factory)` 组合检查是否存在
-
-### 2. 采购需求计算
-- ✅ `calculate_purchase_demand` - 添加工厂参数,按工厂匹配期初库存
-- ✅ `calculate_all_purchase_demand` - 添加工厂参数,按工厂匹配期初库存
-- ✅ `calculate_purchase_demand_from_orders` - 修复INSERT语句字段顺序,factory字段放在最后
-
-### 3. 数据备份恢复
-- ✅ 导入Excel前自动备份
-- ✅ 批量删除前自动备份
-- ✅ 恢复数据前自动备份
-- ✅ 前端添加"📦 数据恢复"功能
-
-## 重启后需要做的事
-
-1. **删除错误的采购需求记录**
- - 需求单 D20260401105945062 及其他使用旧代码生成的记录
- - 这些记录的期初库存字段不正确
-
-2. **重新计算采购需求**
- - 使用修复后的代码重新计算
- - 验证期初库存字段正确匹配工厂
-
-3. **验证修复效果**
- - 铨宝的"6*6*7.5支架 TS-1166VW"期初库存应该是 0
- - 友辉的"6*6*7.5支架 TS-1166VW"期初库存应该是 995
-
-## 如何重启
-
-```bash
-cd /home/hyx/work/生产管理系统
-# 停止当前服务器(Ctrl+C 或 kill 进程)
-# 然后重新启动
-python3 server/app.py
-```
diff --git a/add_customer_name_column.py b/add_customer_name_column.py
deleted file mode 100755
index fe5aaef..0000000
--- a/add_customer_name_column.py
+++ /dev/null
@@ -1,37 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""
-为 customer_orders 表添加 customer_name 列
-"""
-
-import sqlite3
-import os
-
-# 数据库路径
-BASE_DIR = os.path.dirname(os.path.abspath(__file__))
-DB_PATH = os.path.join(BASE_DIR, 'server', 'data.db')
-
-def add_column():
- """添加 customer_name 列"""
- if not os.path.exists(DB_PATH):
- print(f"错误: 数据库文件不存在: {DB_PATH}")
- return
-
- conn = sqlite3.connect(DB_PATH)
- c = conn.cursor()
-
- try:
- # 尝试添加列
- c.execute('ALTER TABLE customer_orders ADD COLUMN customer_name TEXT')
- conn.commit()
- print("成功添加 customer_name 列")
- except Exception as e:
- if 'duplicate column name' in str(e).lower():
- print("customer_name 列已存在")
- else:
- print(f"添加列失败: {e}")
-
- conn.close()
-
-if __name__ == '__main__':
- add_column()
diff --git a/backend/__pycache__/ai_service.cpython-312.pyc b/backend/__pycache__/ai_service.cpython-312.pyc
new file mode 100755
index 0000000..8283f48
Binary files /dev/null and b/backend/__pycache__/ai_service.cpython-312.pyc differ
diff --git a/check_reconciliations.py b/check_reconciliations.py
deleted file mode 100755
index 9b824c8..0000000
--- a/check_reconciliations.py
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""检查对账单数据"""
-
-import sqlite3
-
-conn = sqlite3.connect('server/data.db')
-c = conn.cursor()
-
-# 统计总数
-c.execute('SELECT COUNT(*) FROM reconciliations')
-total = c.fetchone()[0]
-print(f'对账单总数: {total}')
-
-# 查看最新10条记录
-c.execute('''
- SELECT id, order_date, contract_no, material_name, spec_model,
- quantity, unit, unit_price, total_amount, delivery_date
- FROM reconciliations
- ORDER BY id DESC
- LIMIT 10
-''')
-
-print('\n最新10条记录:')
-print('-' * 120)
-for row in c.fetchall():
- print(f"ID: {row[0]}, 下单: {row[1]}, 合同: {row[2]}, 物料: {row[3]}, 规格: {row[4]}")
- print(f" 数量: {row[5]} {row[6]}, 单价: {row[7]}, 金额: {row[8]}, 交货: {row[9]}")
- print('-' * 120)
-
-conn.close()
diff --git a/cookies.txt b/cookies.txt
deleted file mode 100755
index c31d989..0000000
--- a/cookies.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-# Netscape HTTP Cookie File
-# https://curl.se/docs/http-cookies.html
-# This file was generated by libcurl! Edit at your own risk.
-
diff --git a/frontend/assets/login.css.bak b/frontend/assets/login.css.bak
new file mode 100755
index 0000000..5491b52
--- /dev/null
+++ b/frontend/assets/login.css.bak
@@ -0,0 +1,569 @@
+* {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+}
+
+body {
+ font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
+ line-height: 1.5;
+ overflow: hidden;
+}
+
+/* 背景包装器 - 极光效果 */
+.login-wrapper {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: radial-gradient(
+ ellipse at 20% 30%,
+ rgba(138, 43, 226, 0.8) 0%,
+ rgba(138, 43, 226, 0) 60%
+ ),
+ radial-gradient(
+ ellipse at 80% 50%,
+ rgba(0, 191, 255, 0.7) 0%,
+ rgba(0, 191, 255, 0) 70%
+ ),
+ radial-gradient(
+ ellipse at 50% 80%,
+ rgba(50, 205, 50, 0.6) 0%,
+ rgba(50, 205, 50, 0) 65%
+ ),
+ linear-gradient(135deg, #000000 0%, #0a0520 100%);
+ background-blend-mode: overlay, screen, hard-light;
+ overflow: hidden;
+ animation: aurora-drift 25s infinite alternate ease-in-out;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.login-wrapper::before {
+ content: "";
+ position: absolute;
+ width: 200%;
+ height: 200%;
+ top: -50%;
+ left: -50%;
+ background: repeating-linear-gradient(
+ 45deg,
+ rgba(255, 255, 255, 0.02) 0px,
+ rgba(255, 255, 255, 0.02) 1px,
+ transparent 1px,
+ transparent 40px
+ ),
+ repeating-linear-gradient(
+ -45deg,
+ rgba(255, 255, 255, 0.03) 0px,
+ rgba(255, 255, 255, 0.03) 1px,
+ transparent 1px,
+ transparent 60px
+ );
+ animation: grid-shift 20s linear infinite;
+ pointer-events: none;
+}
+
+.login-wrapper::after {
+ content: "";
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ background: radial-gradient(
+ circle at center,
+ transparent 70%,
+ rgba(10, 5, 32, 0.9) 100%
+ );
+ animation: aurora-pulse 8s infinite alternate;
+ pointer-events: none;
+}
+
+@keyframes aurora-drift {
+ 0% {
+ background-position:
+ 0% 0%,
+ 0% 0%,
+ 0% 0%;
+ filter: hue-rotate(0deg) brightness(1);
+ }
+ 50% {
+ background-position:
+ -10% -5%,
+ 5% 10%,
+ 0% 15%;
+ filter: hue-rotate(30deg) brightness(1.2);
+ }
+ 100% {
+ background-position:
+ 5% 10%,
+ -10% -5%,
+ 15% 0%;
+ filter: hue-rotate(60deg) brightness(1);
+ }
+}
+
+@keyframes grid-shift {
+ 0% {
+ transform: translate(0, 0);
+ }
+ 100% {
+ transform: translate(-50%, -50%);
+ }
+}
+
+@keyframes aurora-pulse {
+ 0% {
+ opacity: 0.8;
+ transform: scale(1);
+ }
+ 50% {
+ opacity: 0.5;
+ transform: scale(1.05);
+ }
+ 100% {
+ opacity: 0.8;
+ transform: scale(1);
+ }
+}
+
+/* 主容器 - 适配极光背景 */
+.container {
+ display: flex;
+ width: 620px;
+ height: 520px;
+ max-width: 95%;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+ overflow: hidden;
+ background-color: rgba(10, 5, 32, 0.25);
+ border-radius: 20px;
+ box-shadow: 0px 0px 60px rgba(138, 43, 226, 0.2),
+ 0px 0px 40px rgba(0, 191, 255, 0.1),
+ inset 0 0 30px rgba(255, 255, 255, 0.05);
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ backdrop-filter: blur(20px);
+ animation: slideUp 0.6s ease-out, container-glow 4s ease-in-out infinite alternate;
+}
+
+@keyframes slideUp {
+ from {
+ opacity: 0;
+ transform: translateY(30px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+@keyframes container-glow {
+ 0% {
+ box-shadow: 0px 0px 60px rgba(138, 43, 226, 0.2),
+ 0px 0px 40px rgba(0, 191, 255, 0.1),
+ inset 0 0 30px rgba(255, 255, 255, 0.05);
+ }
+ 50% {
+ box-shadow: 0px 0px 80px rgba(138, 43, 226, 0.3),
+ 0px 0px 60px rgba(0, 191, 255, 0.2),
+ inset 0 0 40px rgba(255, 255, 255, 0.08);
+ }
+ 100% {
+ box-shadow: 0px 0px 60px rgba(50, 205, 50, 0.2),
+ 0px 0px 40px rgba(0, 191, 255, 0.15),
+ inset 0 0 30px rgba(255, 255, 255, 0.05);
+ }
+}
+
+.container::after {
+ position: absolute;
+ content: "";
+ width: 80%;
+ height: 80%;
+ right: -40%;
+ background: rgb(157, 173, 203);
+ background: radial-gradient(
+ circle,
+ rgba(157, 173, 203, 0.6) 61%,
+ rgba(99, 122, 159, 0.4) 100%
+ );
+ border-radius: 50%;
+ z-index: -1;
+ pointer-events: none;
+}
+
+/* 左侧表单区域 */
+.left {
+ width: 60%;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ padding: 40px;
+}
+
+/* 表单头部 */
+.form-header {
+ text-align: center;
+ margin-bottom: 30px;
+}
+
+.system-title {
+ font-size: 22px;
+ font-weight: 700;
+ color: #ffffff;
+ margin: 0 0 6px 0;
+ text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
+}
+
+.system-subtitle {
+ font-size: 12px;
+ color: rgba(255, 255, 255, 0.85);
+ letter-spacing: 1px;
+ text-transform: uppercase;
+ margin: 0;
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
+}
+
+/* 表单样式 */
+.form {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ width: 100%;
+ position: relative;
+}
+
+.form::before {
+ position: absolute;
+ content: "";
+ width: 50%;
+ height: 50%;
+ right: -10%;
+ top: 10%;
+ z-index: -1;
+ pointer-events: none;
+ background: radial-gradient(
+ circle,
+ rgba(194, 13, 170, 0.3) 20%,
+ rgba(26, 186, 235, 0.2) 60%,
+ rgba(26, 186, 235, 0.1) 100%
+ );
+ filter: blur(60px);
+ border-radius: 50%;
+}
+
+/* 输入框块 */
+.input-block {
+ position: relative;
+ margin-bottom: 8px;
+ z-index: 1;
+}
+
+.input,
+button {
+ background: rgba(255, 255, 255, 0.1);
+ outline: none;
+ border: 1px solid rgba(255, 255, 255, 0.2);
+ border-radius: 0.5rem;
+ padding: 12px 14px;
+ margin: 8px auto;
+ width: 100%;
+ display: block;
+ color: #ffffff;
+ font-weight: 500;
+ font-size: 1em;
+ font-family: inherit;
+ transition: all 0.3s ease;
+ backdrop-filter: blur(10px);
+}
+
+.input-block label {
+ position: absolute;
+ left: 14px;
+ top: 37%;
+ pointer-events: none;
+ color: rgba(255, 255, 255, 0.7);
+ font-size: 0.95em;
+ transition: all 0.4s ease;
+}
+
+.input:focus + label,
+.input:valid + label {
+ transform: translateY(-120%) scale(0.9);
+ color: #ffffff;
+ font-weight: 600;
+ text-shadow: 0 1px 3px rgba(138, 43, 226, 0.5);
+}
+
+.input:focus {
+ background: rgba(255, 255, 255, 0.15);
+ border-color: rgba(138, 43, 226, 0.5);
+ box-shadow: 0 0 0 3px rgba(138, 43, 226, 0.2);
+}
+
+.input {
+ box-shadow: inset 2px 2px 4px rgba(0, 0, 0, 0.2),
+ 2px 2px 4px rgba(138, 43, 226, 0.1);
+}
+
+/* 验证码区域 */
+.captcha-block {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ flex-wrap: nowrap;
+}
+
+.captcha-block .captcha-input {
+ flex: 1;
+ width: auto;
+ min-width: 0;
+}
+
+.captcha-block label {
+ left: 14px;
+ top: 37%;
+}
+
+.captcha-block .input:focus + label,
+.captcha-block .input:valid + label {
+ transform: translateY(-120%) scale(0.9);
+}
+
+.captcha-image-wrapper {
+ position: relative;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 110px;
+ height: 48px;
+ background: rgba(255, 255, 255, 0.1);
+ border: 1px solid rgba(255, 255, 255, 0.2);
+ border-radius: 0.5rem;
+ overflow: hidden;
+ flex-shrink: 0;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ margin-top: 0;
+ backdrop-filter: blur(10px);
+ box-shadow: inset 2px 2px 4px rgba(0, 0, 0, 0.2),
+ 2px 2px 6px rgba(138, 43, 226, 0.1);
+}
+
+.captcha-image-wrapper:hover {
+ border-color: rgba(138, 43, 226, 0.5);
+ box-shadow: 0 0 0 3px rgba(138, 43, 226, 0.2),
+ inset 2px 2px 4px rgba(0, 0, 0, 0.1);
+ transform: scale(1.03);
+}
+
+.captcha-image-wrapper:active {
+ transform: scale(0.98);
+}
+
+.captcha-image-wrapper.refreshing {
+ animation: captcha-refresh 0.4s ease;
+}
+
+@keyframes captcha-refresh {
+ 0% { transform: scale(1); opacity: 1; }
+ 50% { transform: scale(0.95); opacity: 0.7; }
+ 100% { transform: scale(1); opacity: 1; }
+}
+
+.captcha-image {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+ display: block;
+ pointer-events: none;
+ background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
+}
+
+/* 底部链接 */
+.forgot {
+ display: block;
+ margin: 5px 0 10px 0;
+ color: rgba(255, 255, 255, 0.9);
+ font-size: 0.85em;
+}
+
+.forgot a {
+ color: rgba(255, 255, 255, 0.9);
+ text-decoration: none;
+ transition: color 0.3s ease;
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
+}
+
+.forgot a:hover {
+ color: #ffffff;
+}
+
+/* 登录按钮 */
+button {
+ background: linear-gradient(135deg, rgba(138, 43, 226, 0.8) 0%, rgba(0, 191, 255, 0.8) 100%);
+ color: white;
+ font-size: 1em;
+ font-weight: 600;
+ box-shadow: 0 4px 20px rgba(138, 43, 226, 0.3),
+ 0 2px 10px rgba(0, 191, 255, 0.2);
+ cursor: pointer;
+ border: none;
+ position: relative;
+ overflow: hidden;
+ backdrop-filter: blur(10px);
+}
+
+button::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: -100%;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
+ transition: left 0.5s ease;
+}
+
+button:hover {
+ background: linear-gradient(135deg, rgba(138, 43, 226, 0.9) 0%, rgba(0, 191, 255, 0.9) 100%);
+ transform: translateY(-2px);
+ box-shadow: 0 6px 30px rgba(138, 43, 226, 0.4),
+ 0 4px 20px rgba(0, 191, 255, 0.3);
+}
+
+button:hover::before {
+ left: 100%;
+}
+
+button:active {
+ transform: translateY(0);
+}
+
+button:disabled {
+ opacity: 0.7;
+ cursor: not-allowed;
+ transform: none;
+}
+
+/* 加载动画 */
+.btn-loader {
+ display: flex;
+ gap: 6px;
+ justify-content: center;
+ align-items: center;
+}
+
+.btn-loader .dot {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background: #ffffff;
+ animation: bounce 0.9s ease infinite;
+}
+
+.btn-loader .dot:nth-child(2) {
+ animation-delay: 0.15s;
+}
+
+.btn-loader .dot:nth-child(3) {
+ animation-delay: 0.3s;
+}
+
+@keyframes bounce {
+ 0%, 100% {
+ transform: translateY(0);
+ opacity: 0.7;
+ }
+ 50% {
+ transform: translateY(-8px);
+ opacity: 1;
+ }
+}
+
+/* 错误消息 */
+.error-message {
+ background: rgba(254, 242, 242, 0.95);
+ border: 1px solid #fecaca;
+ color: #dc2626;
+ padding: 10px 14px;
+ border-radius: 8px;
+ font-size: 13px;
+ margin-bottom: 10px;
+ animation: shake 0.4s ease;
+}
+
+@keyframes shake {
+ 0%, 100% { transform: translateX(0); }
+ 25% { transform: translateX(-8px); }
+ 75% { transform: translateX(8px); }
+}
+
+/* 右侧插图区域 */
+.right {
+ width: 40%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 20px;
+}
+
+.img {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.img svg {
+ max-width: 100%;
+ max-height: 100%;
+ filter: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.1));
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+ .container {
+ flex-direction: column;
+ width: 95%;
+ height: auto;
+ max-height: 90vh;
+ }
+
+ .left {
+ width: 100%;
+ padding: 30px 25px;
+ }
+
+ .right {
+ display: none;
+ }
+
+ .system-title {
+ font-size: 20px;
+ }
+
+ .container::after {
+ display: none;
+ }
+}
+
+@media (max-width: 480px) {
+ .left {
+ padding: 25px 20px;
+ }
+
+ .system-title {
+ font-size: 18px;
+ }
+
+ .input, button {
+ padding: 10px 12px;
+ font-size: 0.95em;
+ }
+}
diff --git a/frontend/assets/styles.css b/frontend/assets/styles.css
index 07f534b..292efb4 100755
--- a/frontend/assets/styles.css
+++ b/frontend/assets/styles.css
@@ -2804,9 +2804,16 @@ input[type="date"]::-webkit-calendar-picker-indicator:hover{
#app.trackit-layout > #sidebar .topnav{flex:1;min-height:0;overflow:visible;padding-right:2px}
#app.trackit-layout > #sidebar .sidebar-footer{margin-top:auto;display:flex;flex-direction:column;gap:12px}
- #app.trackit-layout > #sidebar .sidebar-actions{display:flex;align-items:center;justify-content:flex-end;gap:14px;padding:12px 6px 2px;border-top:1px solid rgba(226,232,240,.85)}
+ #app.trackit-layout > #sidebar .sidebar-user-top{display:flex;align-items:center;padding:10px 6px 8px;border-bottom:1px solid rgba(226,232,240,.85);margin-bottom:4px;width:100%}
+ [data-theme="dark"] #app.trackit-layout > #sidebar .sidebar-user-top{border-bottom-color:rgba(255,255,255,.08)}
+ #app.trackit-layout > #sidebar .sidebar-user-top .user-avatar-btn{display:flex;align-items:center;gap:8px;background:none;border:none;cursor:pointer;padding:4px 6px;border-radius:8px;transition:background 0.2s;width:100%}
+ #app.trackit-layout > #sidebar .sidebar-user-top .user-avatar-btn:hover{background:rgba(79,140,255,0.1)}
+ #app.trackit-layout > #sidebar .sidebar-user-top .user-avatar-img{width:32px;height:32px;border-radius:50%;flex-shrink:0}
+ #app.trackit-layout > #sidebar .sidebar-user-top .user-name-display{color:var(--text);font-size:13px;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
+ #app.trackit-layout > #sidebar .sidebar-user-top .user-dropdown{position:absolute;top:calc(100% + 4px);left:0;bottom:auto;min-width:160px;background:var(--surface);border:1px solid var(--border);border-radius:8px;box-shadow:0 8px 24px rgba(0,0,0,0.3);z-index:1000}
+ #app.trackit-layout > #sidebar .sidebar-actions{display:flex;align-items:center;justify-content:flex-start;gap:14px;padding:12px 6px 2px;border-top:1px solid rgba(226,232,240,.85)}
[data-theme="dark"] #app.trackit-layout > #sidebar .sidebar-actions{border-top:1px solid rgba(255,255,255,.08)}
- #app.trackit-layout > #sidebar .sidebar-actions .notification-bell-btn{position:relative;background:none;border:none;cursor:pointer;padding:6px;border-radius:10px;display:flex;align-items:center;justify-content:center;transition:all 0.2s ease;color:var(--text-2)}
+ #app.trackit-layout > #sidebar .sidebar-actions .notification-bell-btn{position:relative;background:none;border:none;cursor:pointer;padding:6px;border-radius:10px;display:flex;align-items:center;justify-content:center;transition:all 0.2s ease;color:var(--text-2);margin-left:auto}
#app.trackit-layout > #sidebar .sidebar-actions .notification-bell-btn:hover{background:rgba(79,140,255,0.1);color:var(--primary)}
#app.trackit-layout > #sidebar .sidebar-actions .notification-bell-btn .notification-badge{position:absolute;top:-2px;right:-2px;background:var(--danger);color:#fff;border-radius:10px;padding:2px 6px;font-size:10px;font-weight:700;min-width:18px;text-align:center;line-height:1.2;box-shadow:0 2px 4px rgba(239,68,68,0.3)}
#app.trackit-layout > #sidebar .sidebar-actions .user-avatar-btn{padding:0;opacity:1}
diff --git a/frontend/index.html b/frontend/index.html
index 4489079..0b8bf2b 100755
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -266,13 +266,6 @@
-
+
diff --git a/frontend/js/components/ai-report.js.bak b/frontend/js/components/ai-report.js.bak
new file mode 100755
index 0000000..23a7ebd
--- /dev/null
+++ b/frontend/js/components/ai-report.js.bak
@@ -0,0 +1,494 @@
+const AIReport = (() => {
+ // 添加样式
+ const addStyles = () => {
+ if (document.getElementById('ai-report-styles')) return;
+
+ const style = document.createElement('style');
+ style.id = 'ai-report-styles';
+ style.textContent = `
+ @keyframes spin {
+ to { transform: rotate(360deg); }
+ }
+ @keyframes fadeIn {
+ from { opacity: 0; transform: translateY(10px); }
+ to { opacity: 1; transform: translateY(0); }
+ }
+ @keyframes pulse {
+ 0%, 100% { opacity: 1; }
+ 50% { opacity: 0.5; }
+ }
+ @keyframes aiPulse {
+ 0%, 100% { transform: scale(1); opacity: 1; }
+ 50% { transform: scale(1.1); opacity: 0.8; }
+ }
+ .thinking-content > div:last-child::after {
+ content: '';
+ position: absolute;
+ right: 8px;
+ top: 50%;
+ transform: translateY(-50%);
+ width: 6px;
+ height: 6px;
+ background: #667eea;
+ border-radius: 50%;
+ animation: pulse 1.5s infinite;
+ }
+ `;
+ document.head.appendChild(style);
+ };
+ return `
+
+
+
+
+
+
+
+ 🤖 智能报表
+
+
+
+
+
+
+
+
+
点击生成智能报表
+
AI将为您分析生产数据并生成洞察
+
+
+
+
+
+
+
+
+ 🤔 AI思考过程
+ 实时分析中
+
+
+
+
+
+
+
+
+
+
+
+
+
+ —
+
+
+
+
+
+
+
+ `;
+ };
+
+ // 生成报表
+ const generateReport = async () => {
+ const placeholderEl = document.getElementById('ai-report-placeholder');
+ const loadingEl = document.getElementById('ai-report-loading');
+ const resultEl = document.getElementById('ai-report-result');
+ const thinkingEl = document.getElementById('ai-report-thinking');
+ const timeEl = document.getElementById('ai-report-time');
+
+ // 显示加载状态
+ if(placeholderEl) placeholderEl.style.display = 'none';
+ if(loadingEl) loadingEl.style.display = 'flex';
+ if(resultEl) resultEl.style.display = 'none';
+
+ // 显示思考过程容器
+ if(thinkingEl) thinkingEl.style.display = 'block';
+
+ // 模拟思考过程的逐步展示
+ const showThinkingProcess = () => {
+ const thinkingSteps = [
+ { title: '数据概览', content: '正在读取最近30天的生产数据...', delay: 500 },
+ { title: '数据概览', content: '发现总产量数据,正在分析良品率...', delay: 800 },
+ { title: '规律发现', content: '正在分析平台分布规律...', delay: 1200 },
+ { title: '规律发现', content: '发现产量趋势,正在计算增长率...', delay: 1600 },
+ { title: '原因推断', content: '正在分析质量问题和根本原因...', delay: 2000 },
+ { title: '结论形成', content: '正在生成生产建议和预测...', delay: 2400 },
+ { title: '结论形成', content: '整合分析结果,准备输出报告...', delay: 2800 }
+ ];
+
+ let currentStep = 0;
+ const thinkingContent = thinkingEl.querySelector('.thinking-content') || thinkingEl;
+
+ const updateThinking = () => {
+ if (currentStep < thinkingSteps.length) {
+ const step = thinkingSteps[currentStep];
+ const stepHtml = `
+
+
+ ${currentStep + 1}. ${step.title}
+
+
+ ${step.content}
+
+
+ `;
+
+ if (currentStep === 0) {
+ thinkingContent.innerHTML = stepHtml;
+ } else {
+ thinkingContent.innerHTML += stepHtml;
+ }
+
+ currentStep++;
+ setTimeout(updateThinking, step.delay);
+ }
+ };
+
+ updateThinking();
+ };
+
+ // 开始展示思考过程
+ showThinkingProcess();
+
+ try {
+ // 调用后端AI分析API
+ const response = await fetch('/api/ai/analyze', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ }
+ });
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ const reportData = await response.json();
+
+ // 隐藏加载和思考过程
+ if(loadingEl) loadingEl.style.display = 'none';
+ if(thinkingEl) thinkingEl.style.display = 'none';
+
+ // 渲染报表
+ renderReport(reportData);
+
+ // 更新时间
+ const now = new Date();
+ timeEl.textContent = `更新于 ${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`;
+
+ } catch (error) {
+ console.error('生成报表失败:', error);
+
+ // 如果API调用失败,使用模拟数据作为后备
+ try {
+ console.log('使用模拟数据作为后备...');
+ const reportData = await analyzeProductionData();
+
+ // 隐藏加载和思考过程
+ if(loadingEl) loadingEl.style.display = 'none';
+ if(thinkingEl) thinkingEl.style.display = 'none';
+
+ renderReport(reportData);
+
+ const now = new Date();
+ timeEl.textContent = `更新于 ${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')} (模拟)`;
+ } catch (fallbackError) {
+ console.error('模拟数据也失败:', fallbackError);
+ if(loadingEl) loadingEl.style.display = 'none';
+ if(thinkingEl) thinkingEl.style.display = 'none';
+ if(resultEl) {
+ resultEl.style.display = 'block';
+ resultEl.innerHTML = `
+
+
😞
+
生成失败
+
请检查AI服务配置或稍后重试
+
+
+ `;
+ }
+ }
+ }
+ };
+
+ // 模拟AI分析数据
+ const analyzeProductionData = async () => {
+ // 这里应该调用实际的AI API
+ // 现在返回模拟数据
+ return {
+ summary: {
+ totalProduction: 15423,
+ goodRate: '98.5%',
+ trend: 'up',
+ insights: [
+ '本周产量较上周增长12%,主要得益于圆通订单的增加',
+ '良品率保持在98%以上,质量管控效果显著',
+ '建议:继续保持当前生产节奏,关注设备维护'
+ ]
+ },
+ platforms: {
+ pdd: { count: 8934, percentage: 57.9, trend: '+5.2%' },
+ yt: { count: 6489, percentage: 42.1, trend: '+18.7%' }
+ },
+ quality: {
+ topIssues: [
+ { issue: '外观划痕', count: 23, percentage: '0.15%' },
+ { issue: '功能异常', count: 12, percentage: '0.08%' },
+ { issue: '包装破损', count: 8, percentage: '0.05%' }
+ ]
+ },
+ prediction: {
+ tomorrow: 2250,
+ weekRange: '15500-16500',
+ confidence: '92%'
+ }
+ };
+ };
+
+ // 渲染报表内容
+ const renderReport = (data) => {
+ const loadingEl = document.getElementById('ai-report-loading');
+ const resultEl = document.getElementById('ai-report-result');
+
+ if(loadingEl) loadingEl.style.display = 'none';
+
+ // 格式化思考过程
+ const formatThinking = (thinking) => {
+ if (!thinking) return '';
+
+ // 按步骤分割
+ const steps = thinking.split(/第[一二三四五]步[::]/).filter(s => s.trim());
+ const stepTitles = ['数据概览', '规律发现', '原因推断', '结论形成'];
+
+ let html = '';
+ steps.forEach((step, index) => {
+ if (step.trim()) {
+ html += `
+
+
+ ${index + 1}. ${stepTitles[index] || `步骤${index + 1}`}
+
+
+ ${step.trim()}
+
+
+ `;
+ }
+ });
+
+ return html;
+ };
+
+ const html = `
+
+
+ ${data.thinking ? `
+
+
+ 🤔 AI思考过程
+ 了解AI如何分析数据
+
+
+ ${formatThinking(data.thinking)}
+
+
+ ` : ''}
+
+
+
+
📊 生产总览
+
+
+
总产量
+
${data.summary.totalProduction.toLocaleString()}
+
${data.summary.trend === 'up' ? '↑' : '↓'} vs 上周
+
+
+
良品率
+
${data.summary.goodRate}
+
稳定
+
+
+
+
+
+
+
📈 平台分布
+
+
+

+
+
+ 拼多多
+ ${data.platforms.pdd.count.toLocaleString()} (${data.platforms.pdd.percentage}%)
+
+
+
+
+
+

+
+
+ 圆通
+ ${data.platforms.yt.count.toLocaleString()} (${data.platforms.yt.percentage}%)
+
+
+
+
+
+
+
+
+
+
🔍 质量分析
+
主要不良项:
+ ${data.quality.topIssues.map(issue => `
+
+ ${issue.issue}
+ ${issue.count} (${issue.percentage})
+
+ `).join('') || '
暂无不良记录
'}
+
+
+
+
+
💡 AI洞察
+
+ ${data.summary.insights.map(insight => `- ${insight}
`).join('')}
+
+
+
+
+
+
🎯 产量预测
+
+
+
明日预测
+
${data.prediction.tomorrow.toLocaleString()}
+
+
+
本周范围
+
${data.prediction.weekRange}
+
+
+
置信度: ${data.prediction.confidence}
+
+
+ `;
+
+ if(resultEl) {
+ resultEl.innerHTML = html;
+ resultEl.style.display = 'block';
+ }
+ };
+
+ // 导出报表
+ const exportReport = () => {
+ const content = document.getElementById('ai-report-result');
+ if (!content || content.style.display === 'none') {
+ alert('请先生成报表');
+ return;
+ }
+
+ // 创建导出内容
+ const text = content.innerText;
+ const blob = new Blob([`智能生产报表\n\n生成时间: ${new Date().toLocaleString()}\n\n${text}`], { type: 'text/plain' });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = `智能报表_${new Date().toISOString().slice(0, 10)}.txt`;
+ a.click();
+ URL.revokeObjectURL(url);
+ };
+
+ // 刷新报表
+ const refreshReport = () => {
+ generateReport();
+ };
+
+ // 添加CSS动画
+ const addStyles = () => {
+ if (document.getElementById('ai-report-styles')) return;
+
+ const style = document.createElement('style');
+ style.id = 'ai-report-styles';
+ style.textContent = `
+ @keyframes fadeIn {
+ from { opacity: 0; transform: translateY(10px); }
+ to { opacity: 1; transform: translateY(0); }
+ }
+ @keyframes pulse {
+ 0%, 100% { opacity: 1; }
+ 50% { opacity: 0.5; }
+ }
+ .thinking-content > div:last-child {
+ position: relative;
+ }
+ .thinking-content > div:last-child::after {
+ content: '';
+ position: absolute;
+ right: 8px;
+ top: 50%;
+ transform: translateY(-50%);
+ width: 6px;
+ height: 6px;
+ background: #667eea;
+ border-radius: 50%;
+ animation: pulse 1.5s infinite;
+ }
+ @keyframes aiPulse {
+ 0%, 100% { transform: scale(1); opacity: 1; }
+ 50% { transform: scale(1.1); opacity: 0.8; }
+ }
+ @keyframes spin {
+ to { transform: rotate(360deg); }
+ }
+ .ai-pulse {
+ box-shadow: 0 0 0 0 rgba(102, 126, 234, 0.4);
+ }
+ `;
+ document.head.appendChild(style);
+ };
+
+// ...
+ // 初始化
+ const init = () => {
+ addStyles();
+ };
+
+ // 暴露方法
+ return {
+ init,
+ generateReport,
+ exportReport,
+ refreshReport,
+ generateAICard
+ };
+})();
+
+// 初始化
+AIReport.init();
diff --git a/frontend/js/components/upload.js b/frontend/js/components/upload.js
index 7cb5bab..d83db89 100755
--- a/frontend/js/components/upload.js
+++ b/frontend/js/components/upload.js
@@ -224,6 +224,7 @@ const Upload = (() => {
@@ -963,27 +964,44 @@ const Upload = (() => {
// 文件选择后立即验证
addListener(fileEl, 'change', async ()=>{
const file = fileEl.files[0];
- if(!file) return;
+ const statusEl = document.getElementById('mac-file-status');
+ if(!file) {
+ if(statusEl) statusEl.style.display = 'none';
+ return;
+ }
+
+ // 立即显示文件名,避免用户以为文件消失
+ if(statusEl) {
+ statusEl.style.display = 'block';
+ statusEl.style.color = 'var(--text-secondary)';
+ statusEl.textContent = `已选择: ${file.name}(验证中...)`;
+ }
try{
const formData = new FormData();
formData.append('file', file);
const res = await fetch('/api/validate/mac-file', {
method: 'POST',
+ credentials: 'include',
body: formData
});
+ if(!res.ok){
+ if(statusEl) { statusEl.style.color = 'var(--warning, #f59e0b)'; statusEl.textContent = `已选择: ${file.name}(验证接口异常,可直接上传)`; }
+ return;
+ }
const result = await res.json();
if(!result.valid){
+ if(statusEl) { statusEl.style.color = '#ef4444'; statusEl.textContent = `格式错误: ${result.message || '文件格式不正确'}`; }
API.toast(result.message || '文件格式不正确');
fileEl.value = '';
return;
}
+ if(statusEl) { statusEl.style.color = '#10b981'; statusEl.textContent = `✓ ${file.name}(${result.message || '验证通过'})`; }
API.toast(result.message || '文件验证通过');
}catch(e){
- API.toast('文件验证失败');
- fileEl.value = '';
+ if(statusEl) { statusEl.style.color = 'var(--warning, #f59e0b)'; statusEl.textContent = `已选择: ${file.name}(验证异常,可直接上传)`; }
}
});
diff --git a/frontend/login.html.bak b/frontend/login.html.bak
new file mode 100755
index 0000000..ad1999e
--- /dev/null
+++ b/frontend/login.html.bak
@@ -0,0 +1,268 @@
+
+
+
+
+
+登录 - 韬智生产管理系统
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/import_reconciliation_excel.py b/import_reconciliation_excel.py
deleted file mode 100755
index 867940a..0000000
--- a/import_reconciliation_excel.py
+++ /dev/null
@@ -1,144 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""从Excel导入对账单数据"""
-
-import pandas as pd
-import sqlite3
-import os
-from datetime import datetime, timezone, timedelta
-
-# 数据库路径
-DB_PATH = os.path.join(os.path.dirname(__file__), 'server', 'data.db')
-EXCEL_FILE = '25年11月份对账单-易泰勒.xlsx'
-
-def get_beijing_time():
- """获取北京时间(UTC+8)的ISO格式字符串"""
- beijing_tz = timezone(timedelta(hours=8))
- return datetime.now(beijing_tz).isoformat()
-
-def import_from_excel():
- """从Excel导入对账单数据"""
- # 读取Excel文件
- df = pd.read_excel(EXCEL_FILE)
-
- # 打印前20行查看结构
- print("Excel文件结构:")
- print("=" * 80)
- for i in range(min(20, len(df))):
- print(f"第{i}行: {df.iloc[i].tolist()}")
- print("=" * 80)
-
- # 查找表头行(包含"序号"的行)
- header_row = None
- for i in range(len(df)):
- row_values = df.iloc[i].tolist()
- if any(str(val).strip() == '序号' for val in row_values if pd.notna(val)):
- header_row = i
- print(f"\n找到表头行: 第{i}行")
- print(f"表头内容: {row_values}")
- break
-
- if header_row is None:
- print("❌ 未找到表头行(包含'序号'的行)")
- return
-
- # 重新读取,跳过前面的行,使用找到的行作为表头
- df = pd.read_excel(EXCEL_FILE, skiprows=header_row)
-
- # 设置第一行为列名
- df.columns = df.iloc[0]
- df = df[1:] # 删除第一行(已经作为列名)
- df = df.reset_index(drop=True)
-
- print(f"\n数据形状: {df.shape}")
- print(f"列名: {df.columns.tolist()}")
- print(f"\n前5行数据:")
- print(df.head())
-
- # 连接数据库
- conn = sqlite3.connect(DB_PATH)
- c = conn.cursor()
-
- now = get_beijing_time()
- imported_count = 0
-
- # 遍历数据行
- for idx, row in df.iterrows():
- # 跳过空行或无效行
- if pd.isna(row.get('序号')):
- continue
-
- try:
- # 提取数据
- # 处理日期格式
- def format_date(date_val):
- if pd.isna(date_val):
- return ''
- if isinstance(date_val, datetime):
- return date_val.strftime('%Y/%m/%d')
- date_str = str(date_val).strip()
- # 如果已经是 YYYY/MM/DD 格式,保持不变
- if '/' in date_str:
- return date_str
- # 如果是 YYYY-MM-DD 格式,转换为 YYYY/MM/DD
- if '-' in date_str:
- return date_str.split()[0].replace('-', '/')
- return date_str
-
- order_date = format_date(row.get('下单时间'))
- contract_no = str(row.get('合同编号', '')).strip() if pd.notna(row.get('合同编号')) else ''
- material_name = str(row.get('物料名称', '')).strip() if pd.notna(row.get('物料名称')) else ''
- spec_model = str(row.get('规格型号', '')).strip() if pd.notna(row.get('规格型号')) else ''
- transport_no = str(row.get('运输单号', '')).strip() if pd.notna(row.get('运输单号')) else ''
- quantity = int(row.get('数量', 0)) if pd.notna(row.get('数量')) else 0
- unit = str(row.get('单位', 'pcs')).strip() if pd.notna(row.get('单位')) else 'pcs'
- unit_price = float(row.get('含税单价', 0)) if pd.notna(row.get('含税单价')) else 0.0
- total_amount = float(row.get('含税金额', 0)) if pd.notna(row.get('含税金额')) else 0.0
- delivery_date = format_date(row.get('交货日期'))
- shipment_date = format_date(row.get('出货日期'))
-
- # 验证必填字段
- if not all([order_date, contract_no, material_name, spec_model, quantity, unit, unit_price]):
- print(f"⚠️ 跳过第{idx+1}行: 缺少必填字段")
- continue
-
- # 插入数据库
- c.execute('''
- INSERT INTO reconciliations(
- order_date, contract_no, material_name, spec_model, transport_no,
- quantity, unit, unit_price, total_amount, delivery_date, shipment_date,
- created_by, created_at, updated_at
- ) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)
- ''', (
- order_date,
- contract_no,
- material_name,
- spec_model,
- transport_no,
- quantity,
- unit,
- unit_price,
- total_amount,
- delivery_date,
- shipment_date,
- 'admin',
- now,
- now
- ))
-
- imported_count += 1
- print(f"✅ 导入第{idx+1}行: {contract_no} - {material_name}")
-
- except Exception as e:
- print(f"❌ 导入第{idx+1}行失败: {e}")
- continue
-
- conn.commit()
- conn.close()
-
- print(f"\n{'='*80}")
- print(f"✅ 成功导入 {imported_count} 条对账单数据")
- print(f"{'='*80}")
-
-if __name__ == '__main__':
- import_from_excel()
diff --git a/init_customer_orders.py b/init_customer_orders.py
deleted file mode 100755
index 86829f7..0000000
--- a/init_customer_orders.py
+++ /dev/null
@@ -1,97 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""
-初始化客户订单数据
-根据图片中的数据填充客户订单表
-"""
-
-import sqlite3
-import os
-from datetime import datetime, timezone, timedelta
-
-# 数据库路径
-BASE_DIR = os.path.dirname(os.path.abspath(__file__))
-DB_PATH = os.path.join(BASE_DIR, 'server', 'data.db')
-
-def get_beijing_time():
- """获取北京时间(UTC+8)的ISO格式字符串"""
- beijing_tz = timezone(timedelta(hours=8))
- return datetime.now(beijing_tz).isoformat()
-
-# 从图片中提取的订单数据 - 客户:易泰勒
-orders_data = [
- # 2025/4/28 - CGDD001695
- {'order_date': '2025-04-28', 'order_no': 'CGDD001695', 'customer_name': '易泰勒', 'material': 'ETAP05\n基站-5.0\nETAP05', 'quantity': 950, 'unit_price': 315.19},
- {'order_date': '2025-04-28', 'order_no': 'CGDD001695', 'customer_name': '易泰勒', 'material': 'WD1MK0SMD0551\n蓝牙模块\nPCBCOMPONENT_1_-_DUPLICATE', 'quantity': 950, 'unit_price': 1.3},
- {'order_date': '2025-04-28', 'order_no': 'CGDD001695', 'customer_name': '易泰勒', 'material': 'WA0000000040\nPCBA\nCH6121-MODULE-V14', 'quantity': 4750, 'unit_price': 8.05},
-
- # 2025/9/4 - CGDD002429
- {'order_date': '2025-09-04', 'order_no': 'CGDD002429', 'customer_name': '易泰勒', 'material': 'ETAP05\n基站-5.0\nETAP05', 'quantity': 1500, 'unit_price': 315.19},
- {'order_date': '2025-09-04', 'order_no': 'CGDD002429', 'customer_name': '易泰勒', 'material': 'WD1MK0SMD0551\n蓝牙模块\nPCBCOMPONENT_1_-_DUPLICATE', 'quantity': 1500, 'unit_price': 1.3},
- {'order_date': '2025-09-04', 'order_no': 'CGDD002429', 'customer_name': '易泰勒', 'material': 'WA0000000040\nPCBA\nCH6121-MODULE-V14', 'quantity': 7500, 'unit_price': 8.05},
- {'order_date': '2025-09-04', 'order_no': 'CGDD002429', 'customer_name': '易泰勒', 'material': 'AP-ET010\n基站-5.0\nETAP05', 'quantity': 500, 'unit_price': 315.19},
- {'order_date': '2025-09-04', 'order_no': 'CGDD002429', 'customer_name': '易泰勒', 'material': 'WD1MK0SMD0551\n蓝牙模块\nPCBCOMPONENT_1_-_DUPLICATE', 'quantity': 500, 'unit_price': 1.3},
- {'order_date': '2025-09-04', 'order_no': 'CGDD002429', 'customer_name': '易泰勒', 'material': 'WA0000000040\nPCBA\nCH6121-MODULE-V14', 'quantity': 2500, 'unit_price': 8.05},
-
- # 2025/10/23 - CGDD002878
- {'order_date': '2025-10-23', 'order_no': 'CGDD002878', 'customer_name': '易泰勒', 'material': 'AP-DZ006\n智能灯条基站\nETAP05-D1', 'quantity': 4000, 'unit_price': 239.2},
- {'order_date': '2025-10-23', 'order_no': 'CGDD002878', 'customer_name': '易泰勒', 'material': 'WD1MK0SMD0551\n蓝牙模块\nPCBCOMPONENT_1_-_DUPLICATE', 'quantity': 12000, 'unit_price': 1.1},
-
- # 2025/11/13 - CGDD003037
- {'order_date': '2025-11-13', 'order_no': 'CGDD003037', 'customer_name': '易泰勒', 'material': 'AP-DZ009\n智能灯条基站\nETAP05-D1', 'quantity': 500, 'unit_price': 229.61},
- {'order_date': '2025-11-13', 'order_no': 'CGDD003037', 'customer_name': '易泰勒', 'material': 'WD1MK0SMD0551\n蓝牙模块\nPCBCOMPONENT_1_-_DUPLICAT', 'quantity': 1500, 'unit_price': 1.1},
- {'order_date': '2025-11-13', 'order_no': 'CGDD003037', 'customer_name': '易泰勒', 'material': 'AP-DZ006\n智能灯条基站\nETAP05-D1', 'quantity': 4000, 'unit_price': 239.2},
- {'order_date': '2025-11-13', 'order_no': 'CGDD003037', 'customer_name': '易泰勒', 'material': 'WD1MK0SMD0551\n蓝牙模块\nPCBCOMPONENT_1_-_DUPLICATE', 'quantity': 12000, 'unit_price': 1.1},
-]
-
-def init_orders():
- """初始化客户订单数据"""
- if not os.path.exists(DB_PATH):
- print(f"错误: 数据库文件不存在: {DB_PATH}")
- return
-
- conn = sqlite3.connect(DB_PATH)
- c = conn.cursor()
-
- # 检查表是否存在
- c.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='customer_orders'")
- if not c.fetchone():
- print("错误: customer_orders 表不存在,请先运行服务器以创建表")
- conn.close()
- return
-
- # 清空现有数据(可选)
- c.execute('DELETE FROM customer_orders')
- print("已清空现有订单数据")
-
- # 插入新数据
- now = get_beijing_time()
- inserted_count = 0
-
- for order in orders_data:
- try:
- c.execute('''INSERT INTO customer_orders(
- order_date, order_no, customer_name, material, quantity, unit_price,
- created_by, created_at, updated_at
- ) VALUES(?,?,?,?,?,?,?,?,?)''', (
- order['order_date'],
- order['order_no'],
- order['customer_name'],
- order['material'],
- order['quantity'],
- order['unit_price'],
- 'admin', # 创建者
- now,
- now
- ))
- inserted_count += 1
- except Exception as e:
- print(f"插入订单失败: {order['order_no']} - {e}")
-
- conn.commit()
- conn.close()
-
- print(f"成功插入 {inserted_count} 条订单数据")
-
-if __name__ == '__main__':
- init_orders()
diff --git a/init_reconciliations.py b/init_reconciliations.py
deleted file mode 100755
index ad6fa77..0000000
--- a/init_reconciliations.py
+++ /dev/null
@@ -1,124 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""初始化对账单示例数据"""
-
-import sqlite3
-import os
-from datetime import datetime, timezone, timedelta
-
-# 数据库路径
-DB_PATH = os.path.join(os.path.dirname(__file__), 'server', 'data.db')
-
-def get_beijing_time():
- """获取北京时间(UTC+8)的ISO格式字符串"""
- beijing_tz = timezone(timedelta(hours=8))
- return datetime.now(beijing_tz).isoformat()
-
-def init_reconciliations():
- """初始化对账单示例数据"""
- conn = sqlite3.connect(DB_PATH)
- c = conn.cursor()
-
- # 示例数据(根据图片中的数据)
- sample_data = [
- {
- 'order_date': '2025/10/31',
- 'contract_no': 'CGDD002876',
- 'material_name': '扩产-9988 红黑线',
- 'spec_model': 'PCXK0P0NSNT_1_2.54*1C14TE',
- 'transport_no': '快递上门',
- 'quantity': 45,
- 'unit': 'pcs',
- 'unit_price': 239.2,
- 'total_amount': 10764,
- 'delivery_date': '2025/11/3',
- 'shipment_date': '2025/11/3'
- },
- {
- 'order_date': '2025/9/20',
- 'contract_no': 'CGDD004562',
- 'material_name': '扩产-9988 红黑线',
- 'spec_model': 'M1H0EM0N511 PCXK0P0NSNT_1_2.54*1C14TE',
- 'transport_no': '快递上门',
- 'quantity': 355,
- 'unit': 'pcs',
- 'unit_price': 1.1,
- 'total_amount': 390.5,
- 'delivery_date': '2025/11/3',
- 'shipment_date': '2025/11/3'
- },
- {
- 'order_date': '2025/9/20',
- 'contract_no': 'CGDD004562',
- 'material_name': '扩产-9988 红黑线',
- 'spec_model': 'ETAP05-01',
- 'transport_no': '快递上门',
- 'quantity': 2,
- 'unit': 'pcs',
- 'unit_price': 245.46,
- 'total_amount': 490.92,
- 'delivery_date': '2025/11/3',
- 'shipment_date': '2025/11/3'
- },
- {
- 'order_date': '2025/9/20',
- 'contract_no': 'CGDD004562',
- 'material_name': 'M1H0EM0N511 红黑线',
- 'spec_model': 'PCXK0P0NSNT_1_2.54*1C14TE',
- 'transport_no': '快递上门',
- 'quantity': 6,
- 'unit': 'pcs',
- 'unit_price': 1.1,
- 'total_amount': 6.6,
- 'delivery_date': '2025/11/3',
- 'shipment_date': '2025/11/3'
- },
- {
- 'order_date': '2025/10/11',
- 'contract_no': 'CGDD002717',
- 'material_name': '扩产-9988 红黑线',
- 'spec_model': 'ETAP05-01',
- 'transport_no': '快递上门',
- 'quantity': 500,
- 'unit': 'pcs',
- 'unit_price': 228.45,
- 'total_amount': 114225,
- 'delivery_date': '2025/11/3',
- 'shipment_date': '2025/11/3'
- }
- ]
-
- now = get_beijing_time()
-
- for data in sample_data:
- c.execute('''
- INSERT INTO reconciliations(
- order_date, contract_no, material_name, spec_model, transport_no,
- quantity, unit, unit_price, total_amount, delivery_date, shipment_date,
- created_by, created_at, updated_at
- ) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)
- ''', (
- data['order_date'],
- data['contract_no'],
- data['material_name'],
- data['spec_model'],
- data['transport_no'],
- data['quantity'],
- data['unit'],
- data['unit_price'],
- data['total_amount'],
- data['delivery_date'],
- data['shipment_date'],
- 'admin',
- now,
- now
- ))
-
- conn.commit()
- count = len(sample_data)
- conn.close()
-
- print(f'✅ 成功初始化 {count} 条对账单示例数据')
-
-if __name__ == '__main__':
- init_reconciliations()
diff --git a/server/__pycache__/ai_routes.cpython-312.pyc b/server/__pycache__/ai_routes.cpython-312.pyc
new file mode 100755
index 0000000..7286403
Binary files /dev/null and b/server/__pycache__/ai_routes.cpython-312.pyc differ
diff --git a/server/__pycache__/app.cpython-312.pyc b/server/__pycache__/app.cpython-312.pyc
new file mode 100755
index 0000000..e4bb1a8
Binary files /dev/null and b/server/__pycache__/app.cpython-312.pyc differ
diff --git a/server/__pycache__/finance_routes.cpython-312.pyc b/server/__pycache__/finance_routes.cpython-312.pyc
new file mode 100644
index 0000000..12365ff
Binary files /dev/null and b/server/__pycache__/finance_routes.cpython-312.pyc differ
diff --git a/server/__pycache__/gunicorn_config.cpython-312.pyc b/server/__pycache__/gunicorn_config.cpython-312.pyc
new file mode 100755
index 0000000..458b408
Binary files /dev/null and b/server/__pycache__/gunicorn_config.cpython-312.pyc differ
diff --git a/server/app.py b/server/app.py
index bf40b2b..9f69965 100755
--- a/server/app.py
+++ b/server/app.py
@@ -3728,8 +3728,8 @@ def upload_mac_file():
f.save(temp_path)
# 调用batch_import.py脚本
- script_path = '/home/hyx/work/生产管理系统/test_py/batch_import.py'
- python_path = '/home/hyx/work/.venv/bin/python'
+ script_path = '/opt/生产管理系统/test_py/batch_import.py'
+ python_path = '/usr/bin/python3'
try:
result = subprocess.run(
[python_path, script_path, upload_type],
diff --git a/server/app.pyc b/server/app.pyc
new file mode 100755
index 0000000..4c6e7a7
Binary files /dev/null and b/server/app.pyc differ
diff --git a/server/data.db b/server/data.db
index f63767c..237d9dc 100644
Binary files a/server/data.db and b/server/data.db differ
diff --git a/test_py/test_login.html b/server/database.db
similarity index 100%
rename from test_py/test_login.html
rename to server/database.db
diff --git a/test-meituan-api.html b/test-meituan-api.html
deleted file mode 100755
index 5bfee9d..0000000
--- a/test-meituan-api.html
+++ /dev/null
@@ -1,145 +0,0 @@
-
-
-
-
- 美团基站测试 - API调试
-
-
-
- 美团基站测试 - API调试
-
-
-
服务器状态
-
-
点击按钮检查
-
服务器日志:
-
-
-
-
-
基站列表
-
-
点击按钮加载
-
-
-
-
-
-
-
diff --git a/test-xlsx.html b/test-xlsx.html
deleted file mode 100755
index dbbc0a1..0000000
--- a/test-xlsx.html
+++ /dev/null
@@ -1,37 +0,0 @@
-
-
-
-
- 测试XLSX库
-
-
- XLSX库测试
-
-
-
-
-
-
-
diff --git a/test_fix_total_amount.py b/test_fix_total_amount.py
deleted file mode 100755
index c64c2b4..0000000
--- a/test_fix_total_amount.py
+++ /dev/null
@@ -1,60 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""
-测试修复含税金额功能
-"""
-
-import requests
-import json
-
-def test_fix_total_amount():
- """测试修复含税金额API"""
-
- # API地址
- url = "http://localhost:5000/api/reconciliations/fix-total-amount"
-
- # 模拟登录(需要先获取session)
- login_url = "http://localhost:5000/api/login"
-
- try:
- # 创建会话
- session = requests.Session()
-
- # 登录
- login_data = {
- "username": "admin",
- "password": "admin"
- }
-
- print("正在登录...")
- login_response = session.post(login_url, json=login_data)
-
- if login_response.status_code == 200:
- print("登录成功!")
-
- # 调用修复含税金额API
- print("\n正在调用修复含税金额API...")
- response = session.post(url)
-
- if response.status_code == 200:
- result = response.json()
- print(f"\n修复结果:")
- print(f"- 状态: {'成功' if result.get('ok') else '失败'}")
- print(f"- 修复记录数: {result.get('fixed_count', 0)}")
- print(f"- 消息: {result.get('message', '')}")
- else:
- print(f"API调用失败,状态码: {response.status_code}")
- print(f"错误信息: {response.text}")
- else:
- print(f"登录失败,状态码: {login_response.status_code}")
- print(f"错误信息: {login_response.text}")
-
- except requests.exceptions.ConnectionError:
- print("\n错误:无法连接到服务器")
- print("请确保生产管理系统正在运行(systemctl start prod-mgmt)")
- except Exception as e:
- print(f"\n发生错误: {str(e)}")
-
-if __name__ == '__main__':
- print("=== 测试修复含税金额功能 ===\n")
- test_fix_total_amount()
diff --git a/test_outsourcing_system.py b/test_outsourcing_system.py
deleted file mode 100755
index eccfbf5..0000000
--- a/test_outsourcing_system.py
+++ /dev/null
@@ -1,232 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""
-委外管理系统测试脚本
-用于验证委外工单、发料、入库等功能是否正常工作
-"""
-
-import sys
-import os
-
-# 添加server目录到路径
-sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'server'))
-
-from app import init_db, get_db
-
-def test_database_tables():
- """测试数据库表是否正确创建"""
- print("=" * 60)
- print("测试1: 检查数据库表结构")
- print("=" * 60)
-
- # 初始化数据库
- init_db()
-
- conn = get_db()
- c = conn.cursor()
-
- # 检查委外工单表
- c.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='outsourcing_orders'")
- if c.fetchone():
- print("✓ 委外工单表 (outsourcing_orders) 创建成功")
- else:
- print("✗ 委外工单表创建失败")
- return False
-
- # 检查委外发料表
- c.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='outsourcing_material_issue'")
- if c.fetchone():
- print("✓ 委外发料表 (outsourcing_material_issue) 创建成功")
- else:
- print("✗ 委外发料表创建失败")
- return False
-
- # 检查委外在制库存表
- c.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='outsourcing_wip_stock'")
- if c.fetchone():
- print("✓ 委外在制库存表 (outsourcing_wip_stock) 创建成功")
- else:
- print("✗ 委外在制库存表创建失败")
- return False
-
- # 检查成品入库表
- c.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='finished_goods_receipt'")
- if c.fetchone():
- print("✓ 成品入库表 (finished_goods_receipt) 创建成功")
- else:
- print("✗ 成品入库表创建失败")
- return False
-
- conn.close()
- print("\n所有数据库表创建成功!\n")
- return True
-
-def test_table_structure():
- """测试表结构是否正确"""
- print("=" * 60)
- print("测试2: 检查表结构字段")
- print("=" * 60)
-
- conn = get_db()
- c = conn.cursor()
-
- # 检查委外工单表字段
- c.execute("PRAGMA table_info(outsourcing_orders)")
- columns = [row[1] for row in c.fetchall()]
- required_columns = ['id', 'order_no', 'customer_order_no', 'product_code',
- 'product_name', 'production_qty', 'outsourcing_factory',
- 'delivery_date', 'status', 'created_by', 'created_at', 'updated_at']
-
- print("\n委外工单表字段:")
- for col in required_columns:
- if col in columns:
- print(f" ✓ {col}")
- else:
- print(f" ✗ {col} (缺失)")
-
- # 检查委外发料表字段
- c.execute("PRAGMA table_info(outsourcing_material_issue)")
- columns = [row[1] for row in c.fetchall()]
- required_columns = ['id', 'issue_no', 'outsourcing_order_no', 'material_code',
- 'material_name', 'issue_qty', 'unit', 'issue_date',
- 'created_by', 'created_at', 'updated_at']
-
- print("\n委外发料表字段:")
- for col in required_columns:
- if col in columns:
- print(f" ✓ {col}")
- else:
- print(f" ✗ {col} (缺失)")
-
- # 检查成品入库表字段
- c.execute("PRAGMA table_info(finished_goods_receipt)")
- columns = [row[1] for row in c.fetchall()]
- required_columns = ['id', 'receipt_no', 'outsourcing_order_no', 'product_code',
- 'product_name', 'receipt_qty', 'receipt_date',
- 'created_by', 'created_at', 'updated_at']
-
- print("\n成品入库表字段:")
- for col in required_columns:
- if col in columns:
- print(f" ✓ {col}")
- else:
- print(f" ✗ {col} (缺失)")
-
- # 检查委外在制库存表字段
- c.execute("PRAGMA table_info(outsourcing_wip_stock)")
- columns = [row[1] for row in c.fetchall()]
- required_columns = ['id', 'outsourcing_order_no', 'material_code',
- 'material_name', 'wip_qty', 'unit', 'updated_at']
-
- print("\n委外在制库存表字段:")
- for col in required_columns:
- if col in columns:
- print(f" ✓ {col}")
- else:
- print(f" ✗ {col} (缺失)")
-
- conn.close()
- print("\n表结构检查完成!\n")
- return True
-
-def create_test_data():
- """创建测试数据"""
- print("=" * 60)
- print("测试3: 创建测试数据")
- print("=" * 60)
-
- conn = get_db()
- c = conn.cursor()
-
- try:
- # 创建测试BOM数据
- print("\n创建测试BOM数据...")
- test_bom = [
- ('AP05', 'AP05物流版', 'R001', '电阻-10K', 1, 'pcs', 100, '供应商A', None, 'system', '2026-03-13', '2026-03-13'),
- ('AP05', 'AP05物流版', 'C001', '电容-100uF', 2, 'pcs', 50, '供应商B', None, 'system', '2026-03-13', '2026-03-13'),
- ('AP05', 'AP05物流版', 'IC001', '芯片-主控', 1, 'pcs', 1, '供应商C', None, 'system', '2026-03-13', '2026-03-13'),
- ]
-
- for bom in test_bom:
- c.execute('''INSERT OR IGNORE INTO bom (
- product_code, product_name, material_code, material_name,
- unit_qty, unit, min_package, supplier, remark,
- created_by, created_at, updated_at
- ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)''', bom)
-
- print("✓ BOM测试数据创建成功")
-
- # 创建测试期初库存数据
- print("\n创建测试期初库存数据...")
- test_stock = [
- ('R001', '电阻-10K', 500, 'pcs', 100, '供应商A', None, 'system', '2026-03-13', '2026-03-13'),
- ('C001', '电容-100uF', 1000, 'pcs', 50, '供应商B', None, 'system', '2026-03-13', '2026-03-13'),
- ('IC001', '芯片-主控', 200, 'pcs', 1, '供应商C', None, 'system', '2026-03-13', '2026-03-13'),
- ]
-
- for stock in test_stock:
- c.execute('''INSERT OR IGNORE INTO initial_stock (
- material_code, material_name, stock_qty, unit, min_package,
- supplier, remark, created_by, created_at, updated_at
- ) VALUES (?,?,?,?,?,?,?,?,?,?)''', stock)
-
- print("✓ 期初库存测试数据创建成功")
-
- # 创建测试客户订单数据
- print("\n创建测试客户订单数据...")
- c.execute('''INSERT OR IGNORE INTO customer_orders (
- order_date, order_no, customer_name, material, quantity, unit_price,
- created_by, created_at, updated_at
- ) VALUES (?,?,?,?,?,?,?,?,?)''',
- ('2026-03-13', 'CO20260313001', '测试客户', 'AP05物流版', 100, 1000.00,
- 'system', '2026-03-13', '2026-03-13'))
-
- print("✓ 客户订单测试数据创建成功")
-
- conn.commit()
- print("\n所有测试数据创建成功!\n")
-
- except Exception as e:
- print(f"\n✗ 创建测试数据失败: {str(e)}\n")
- conn.rollback()
- return False
- finally:
- conn.close()
-
- return True
-
-def main():
- """主测试函数"""
- print("\n")
- print("╔" + "=" * 58 + "╗")
- print("║" + " " * 15 + "委外管理系统测试脚本" + " " * 15 + "║")
- print("╚" + "=" * 58 + "╝")
- print("\n")
-
- # 运行测试
- success = True
-
- if not test_database_tables():
- success = False
-
- if not test_table_structure():
- success = False
-
- if not create_test_data():
- success = False
-
- # 输出测试结果
- print("=" * 60)
- if success:
- print("✓ 所有测试通过!委外管理系统已准备就绪。")
- print("\n下一步操作:")
- print("1. 启动服务器: cd server && python app.py")
- print("2. 访问系统: http://localhost:5000")
- print("3. 进入\"委外管理\"菜单开始使用")
- else:
- print("✗ 部分测试失败,请检查错误信息。")
- print("=" * 60)
- print("\n")
-
-if __name__ == '__main__':
- main()
diff --git a/test_price_matching.py b/test_price_matching.py
deleted file mode 100755
index a2e0f19..0000000
--- a/test_price_matching.py
+++ /dev/null
@@ -1,148 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""
-测试对账单单价匹配功能 - 详细版本
-"""
-
-import sqlite3
-import sys
-import os
-
-def test_price_matching():
- """测试价格匹配逻辑"""
-
- # 连接数据库
- conn = sqlite3.connect('server/data.db')
- c = conn.cursor()
-
- print("=== 对账单单价匹配测试 ===\n")
-
- # 获取所有客户订单
- print("1. 获取所有客户订单数据...")
- c.execute('SELECT order_no, material, unit_price FROM customer_orders')
- customer_orders = c.fetchall()
-
- # 构建订单单价字典
- order_prices = {}
- for order in customer_orders:
- order_no = order[0]
- if order_no not in order_prices:
- order_prices[order_no] = {}
- # 处理物料名称(支持换行符分割的多个物料)
- materials = str(order[1]).split('\n')
- for material in materials:
- material = material.strip()
- if material:
- order_prices[order_no][material] = order[2]
-
- print(f" 共找到 {len(order_prices)} 个不同的合同号")
-
- # 显示每个合同的物料列表
- print("\n2. 客户订单中的合同号及物料:")
- for order_no, materials in sorted(order_prices.items()):
- print(f"\n 合同号: {order_no}")
- for material, price in sorted(materials.items()):
- print(f" - 物料: {material[:50]}... , 单价: {price}")
-
- # 获取所有对账单
- print("\n\n3. 检查对账单匹配情况...")
- c.execute('SELECT id, contract_no, material_name, unit_price FROM reconciliations ORDER BY contract_no')
- reconciliations = c.fetchall()
-
- # 统计匹配情况
- matched = 0
- not_matched = []
-
- for recon in reconciliations:
- recon_id, contract_no, material_name, current_price = recon
- new_price = None
- match_type = ""
-
- # 精确匹配合同号
- if contract_no in order_prices:
- # 尝试精确匹配物料名
- if material_name in order_prices[contract_no]:
- new_price = order_prices[contract_no][material_name]
- match_type = "精确匹配"
- else:
- # 尝试部分匹配(物料名包含关系)
- for order_material, price in order_prices[contract_no].items():
- if material_name in order_material or order_material in material_name:
- new_price = price
- match_type = f"部分匹配(订单物料: {order_material[:30]}...)"
- break
-
- # 如果精确匹配没找到,尝试模糊匹配合同号
- if new_price is None:
- for order_no, materials in order_prices.items():
- if contract_no in order_no or order_no in contract_no:
- if material_name in materials:
- new_price = materials[material_name]
- match_type = f"模糊匹配合同号({order_no})"
- break
- else:
- # 尝试部分匹配物料名
- for order_material, price in materials.items():
- if material_name in order_material or order_material in material_name:
- new_price = price
- match_type = f"模糊匹配(订单: {order_no}, 物料: {order_material[:30]}...)"
- break
- if new_price is not None:
- break
-
- if new_price is not None:
- matched += 1
- print(f" ✓ ID={recon_id}, 合同={contract_no}, 匹配类型: {match_type}")
- else:
- not_matched.append((recon_id, contract_no, material_name))
-
- print(f"\n\n4. 匹配结果统计:")
- print(f" 总对账单记录数: {len(reconciliations)}")
- print(f" 成功匹配: {matched}")
- print(f" 未匹配: {len(not_matched)}")
-
- if not_matched:
- print(f"\n5. 未匹配的记录(前10条):")
- for recon_id, contract_no, material_name in not_matched[:10]:
- print(f" ✗ ID={recon_id}, 合同号={contract_no}, 物料={material_name[:50]}...")
-
- # 检查是否有相似的合同号
- similar_orders = [o for o in order_prices.keys() if contract_no[:10] in o or o[:10] in contract_no]
- if similar_orders:
- print(f" → 相似合同号: {', '.join(similar_orders)}")
-
- # 测试特定匹配案例
- print(f"\n6. 测试特定匹配案例:")
- test_cases = [
- ("CGDD002878", "AP-DZ006灯条基站"),
- ("CGDD002878", "WD1MK0SMD0551"),
- ("CGDD001850", "AP-DZ009灯条基站"),
- ]
-
- for contract_no, material_name in test_cases:
- print(f"\n 测试: 合同号={contract_no}, 物料={material_name}")
- if contract_no in order_prices:
- print(f" 找到合同号,包含 {len(order_prices[contract_no])} 个物料")
- for mat, price in order_prices[contract_no].items():
- if material_name in mat or mat in material_name:
- print(f" ✓ 匹配成功: {mat[:50]}... -> {price}")
- break
- else:
- print(f" ✗ 未找到匹配的物料")
- else:
- print(f" ✗ 未找到合同号")
- # 查找相似合同号
- similar = [o for o in order_prices.keys() if contract_no in o or o in contract_no]
- if similar:
- print(f" → 相似合同号: {similar}")
-
- conn.close()
-
- print("\n=== 优化建议 ===")
- print("1. 确保对账单的合同号与客户订单的订单号完全一致")
- print("2. 物料名称应该标准化,避免使用简写或别名")
- print("3. 可以在上传发货单时自动匹配客户订单单价")
- print("4. 对于无法匹配的记录,可以手动设置默认单价")
-
-if __name__ == '__main__':
- test_price_matching()
diff --git a/test_purchase_demand_ordered.py b/test_purchase_demand_ordered.py
deleted file mode 100755
index 4b1690e..0000000
--- a/test_purchase_demand_ordered.py
+++ /dev/null
@@ -1,97 +0,0 @@
-#!/usr/bin/env python3
-"""
-测试采购需求状态更新为"已下单"后期初库存的更新功能
-"""
-
-import sqlite3
-import sys
-import os
-
-# 添加项目路径
-sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
-
-DB_PATH = 'server/data.db'
-
-def test_purchase_demand_ordered_update():
- """测试采购需求状态更新为已下单后期初库存自动更新"""
-
- # 连接数据库
- conn = sqlite3.connect(DB_PATH)
- c = conn.cursor()
-
- try:
- # 1. 创建测试物料
- material_code = 'TEST001'
- material_name = '测试物料'
-
- # 插入期初库存记录
- c.execute('''INSERT OR REPLACE INTO initial_stock
- (material_code, material_name, stock_qty, created_at, updated_at)
- VALUES (?, ?, ?, datetime('now', 'localtime'), datetime('now', 'localtime'))''',
- (material_code, material_name, 100))
-
- # 2. 创建采购需求记录
- c.execute('''INSERT INTO purchase_demand
- (demand_no, material_code, material_name, order_qty, bom_unit_qty,
- total_demand, initial_stock, net_demand, min_package, actual_purchase_qty,
- status, created_at, updated_at)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now', 'localtime'), datetime('now', 'localtime'))''',
- ('TEST001', material_code, material_name, 10, 5, 50, 100, 0, 10, 50, 'pending'))
-
- demand_id = c.lastrowid
-
- # 3. 更新状态为已下单
- c.execute('''UPDATE purchase_demand
- SET status = 'ordered', updated_at = datetime('now', 'localtime')
- WHERE id = ?''', (demand_id,))
-
- # 4. 模拟计算新的期初库存:实际采购 - 总需求
- actual_purchase_qty = 50
- total_demand = 50
- expected_stock = actual_purchase_qty - total_demand
-
- # 更新期初库存
- c.execute('''INSERT OR REPLACE INTO initial_stock
- (material_code, material_name, stock_qty, updated_at)
- VALUES (?, ?, ?, datetime('now', 'localtime'))''',
- (material_code, material_name, expected_stock))
-
- conn.commit()
-
- # 5. 验证结果
- c.execute('SELECT stock_qty FROM initial_stock WHERE material_code = ?', (material_code,))
- result = c.fetchone()
-
- if result:
- actual_stock = result[0]
- print(f"✅ 测试通过!")
- print(f" - 物料编码: {material_code}")
- print(f" - 实际采购数量: {actual_purchase_qty}")
- print(f" - 总需求: {total_demand}")
- print(f" - 期望期初库存: {expected_stock}")
- print(f" - 实际期初库存: {actual_stock}")
-
- if actual_stock == expected_stock:
- print(f" - 期初库存更新正确!")
- else:
- print(f" - ❌ 期初库存更新错误!")
- else:
- print("❌ 测试失败:未找到期初库存记录")
-
- # 6. 清理测试数据
- c.execute('DELETE FROM purchase_demand WHERE id = ?', (demand_id,))
- c.execute('DELETE FROM initial_stock WHERE material_code = ?', (material_code,))
- conn.commit()
-
- except Exception as e:
- print(f"❌ 测试出错: {e}")
- conn.rollback()
- finally:
- conn.close()
-
-if __name__ == '__main__':
- print("开始测试采购需求状态更新功能...")
- print("=" * 50)
- test_purchase_demand_ordered_update()
- print("=" * 50)
- print("测试完成!")
diff --git a/test_py/check_login.py b/test_py/check_login.py
deleted file mode 100755
index 4632d61..0000000
--- a/test_py/check_login.py
+++ /dev/null
@@ -1,222 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""
-登录问题诊断脚本
-"""
-import sqlite3
-import os
-
-DB_PATH = 'server/data.db'
-
-def check_database():
- """检查数据库和用户"""
- print("=" * 60)
- print("🔍 检查数据库...")
- print("=" * 60)
-
- if not os.path.exists(DB_PATH):
- print("❌ 数据库文件不存在:", DB_PATH)
- return False
-
- print("✅ 数据库文件存在")
-
- try:
- conn = sqlite3.connect(DB_PATH)
- c = conn.cursor()
-
- # 检查用户表
- users = c.execute('SELECT username, role FROM users').fetchall()
-
- if not users:
- print("❌ 没有找到任何用户")
- return False
-
- print(f"\n✅ 找到 {len(users)} 个用户:")
- print("-" * 60)
- for username, role in users:
- print(f" 👤 用户名: {username:15s} | 角色: {role}")
-
- conn.close()
- return True
-
- except Exception as e:
- print(f"❌ 数据库错误: {e}")
- return False
-
-def check_server():
- """检查服务器状态"""
- print("\n" + "=" * 60)
- print("🔍 检查服务器...")
- print("=" * 60)
-
- import subprocess
-
- # 检查进程
- try:
- result = subprocess.run(
- ['ps', 'aux'],
- capture_output=True,
- text=True
- )
-
- if 'python' in result.stdout and 'app.py' in result.stdout:
- print("✅ 服务器正在运行")
-
- # 提取进程信息
- for line in result.stdout.split('\n'):
- if 'app.py' in line:
- print(f" 📋 进程: {' '.join(line.split()[10:])}")
- return True
- else:
- print("❌ 服务器未运行")
- print("\n💡 启动服务器:")
- print(" cd server && python3 app.py")
- return False
-
- except Exception as e:
- print(f"⚠️ 无法检查进程: {e}")
- return None
-
-def check_files():
- """检查关键文件"""
- print("\n" + "=" * 60)
- print("🔍 检查关键文件...")
- print("=" * 60)
-
- files = {
- 'frontend/login.html': '登录页面',
- 'frontend/assets/login.css': '登录样式',
- 'frontend/js/api.js': 'API 接口',
- 'server/app.py': '后端服务',
- }
-
- all_exist = True
- for path, desc in files.items():
- if os.path.exists(path):
- size = os.path.getsize(path)
- print(f"✅ {desc:20s} - {path} ({size} bytes)")
- else:
- print(f"❌ {desc:20s} - {path} (不存在)")
- all_exist = False
-
- return all_exist
-
-def show_instructions():
- """显示使用说明"""
- print("\n" + "=" * 60)
- print("📖 使用说明")
- print("=" * 60)
-
- print("\n1️⃣ 启动后端服务:")
- print(" cd server")
- print(" python3 app.py")
-
- print("\n2️⃣ 访问登录页面:")
- print(" http://localhost:5000/login.html")
- print(" ⚠️ 注意:不是 login-preview.html")
-
- print("\n3️⃣ 使用以下账号登录:")
- print(" - tz (超级管理员)")
- print(" - 张正浩 (超级管理员)")
- print(" - admin (管理员)")
- print(" - 黄有想 (管理员)")
-
- print("\n4️⃣ 如果忘记密码,重置密码:")
- print(" python3 reset_password.py <用户名> <新密码>")
-
- print("\n5️⃣ 清除浏览器缓存:")
- print(" Chrome/Edge: Ctrl+Shift+Delete")
- print(" 或者使用无痕模式: Ctrl+Shift+N")
-
-def create_reset_script():
- """创建密码重置脚本"""
- script = '''#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""
-密码重置脚本
-用法: python3 reset_password.py <用户名> <新密码>
-"""
-import sys
-import sqlite3
-from werkzeug.security import generate_password_hash
-
-if len(sys.argv) != 3:
- print("用法: python3 reset_password.py <用户名> <新密码>")
- sys.exit(1)
-
-username = sys.argv[1]
-new_password = sys.argv[2]
-
-DB_PATH = 'server/data.db'
-
-try:
- conn = sqlite3.connect(DB_PATH)
- c = conn.cursor()
-
- # 检查用户是否存在
- user = c.execute('SELECT id FROM users WHERE username = ?', (username,)).fetchone()
-
- if not user:
- print(f"❌ 用户 '{username}' 不存在")
- print("\\n现有用户:")
- users = c.execute('SELECT username FROM users').fetchall()
- for u in users:
- print(f" - {u[0]}")
- sys.exit(1)
-
- # 更新密码
- password_hash = generate_password_hash(new_password)
- c.execute('UPDATE users SET password_hash = ? WHERE username = ?', (password_hash, username))
- conn.commit()
-
- print(f"✅ 用户 '{username}' 的密码已重置")
- print(f" 新密码: {new_password}")
-
- conn.close()
-
-except Exception as e:
- print(f"❌ 错误: {e}")
- sys.exit(1)
-'''
-
- with open('reset_password.py', 'w', encoding='utf-8') as f:
- f.write(script)
-
- os.chmod('reset_password.py', 0o755)
- print("\n✅ 已创建密码重置脚本: reset_password.py")
-
-def main():
- print("\n" + "🔐 登录问题诊断工具".center(60, "="))
- print()
-
- db_ok = check_database()
- files_ok = check_files()
- server_ok = check_server()
-
- print("\n" + "=" * 60)
- print("📊 诊断结果")
- print("=" * 60)
-
- if db_ok and files_ok:
- print("✅ 数据库和文件都正常")
-
- if server_ok:
- print("✅ 服务器正在运行")
- print("\n💡 如果仍然无法登录,请尝试:")
- print(" 1. 清除浏览器缓存")
- print(" 2. 使用无痕模式")
- print(" 3. 检查浏览器控制台的错误信息")
- print(" 4. 确认访问的是 login.html 而不是 login-preview.html")
- else:
- print("❌ 服务器未运行,请先启动服务器")
- else:
- print("❌ 发现问题,请检查上述错误信息")
-
- show_instructions()
- create_reset_script()
-
- print("\n" + "=" * 60)
- print()
-
-if __name__ == '__main__':
- main()
diff --git a/test_py/test-theme.html b/test_py/test-theme.html
deleted file mode 100755
index a31c76b..0000000
--- a/test_py/test-theme.html
+++ /dev/null
@@ -1,71 +0,0 @@
-
-
-
-
-主题测试
-
-
-
-
-
-
-
-
-
-
-
上传日志测试
-
-
上传日志
-
-[2024-01-01 10:00:00] 开始上传文件...
-[2024-01-01 10:00:01] 验证文件格式...
-[2024-01-01 10:00:02] 处理数据中...
-[2024-01-01 10:00:03] 成功上传 100 条记录
-[2024-01-01 10:00:04] 完成!
-
-
-
-
-
-
日期选择器测试
-
-
-
-
-
卡片测试
-
-
测试卡片
-
这是一个测试卡片,用于验证主题颜色是否正确。
-
-
-
-
-
-
-
diff --git a/test_py/test_captcha.png b/test_py/test_captcha.png
deleted file mode 100755
index 9dc10b0..0000000
Binary files a/test_py/test_captcha.png and /dev/null differ
diff --git a/test_py/test_captcha.py b/test_py/test_captcha.py
deleted file mode 100755
index 19efc98..0000000
--- a/test_py/test_captcha.py
+++ /dev/null
@@ -1,88 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""测试验证码生成功能"""
-
-try:
- from PIL import Image, ImageDraw, ImageFont
- import random
- import io
- import base64
-
- print("✓ Pillow 库已安装")
-
- # 生成4位随机数字
- code = ''.join([str(random.randint(0, 9)) for _ in range(4)])
- print(f"✓ 生成验证码: {code}")
-
- # 创建图片
- width, height = 120, 40
- image = Image.new('RGB', (width, height), color='#f0f4f8')
- draw = ImageDraw.Draw(image)
- print("✓ 创建图片成功")
-
- # 尝试使用系统字体
- font = None
- font_paths = [
- '/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf',
- '/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf',
- '/System/Library/Fonts/Helvetica.ttc',
- 'C:\\Windows\\Fonts\\arial.ttf'
- ]
-
- for font_path in font_paths:
- try:
- font = ImageFont.truetype(font_path, 28)
- print(f"✓ 使用字体: {font_path}")
- break
- except:
- continue
-
- if not font:
- font = ImageFont.load_default()
- print("⚠ 使用默认字体")
-
- # 绘制干扰线
- for _ in range(3):
- x1 = random.randint(0, width)
- y1 = random.randint(0, height)
- x2 = random.randint(0, width)
- y2 = random.randint(0, height)
- draw.line([(x1, y1), (x2, y2)], fill='#cbd5e1', width=1)
-
- # 绘制验证码文字
- colors = ['#3b82f6', '#2563eb', '#1e40af', '#1e3a8a']
- for i, char in enumerate(code):
- x = 20 + i * 25 + random.randint(-3, 3)
- y = 5 + random.randint(-3, 3)
- color = random.choice(colors)
- draw.text((x, y), char, font=font, fill=color)
-
- # 绘制干扰点
- for _ in range(50):
- x = random.randint(0, width)
- y = random.randint(0, height)
- draw.point((x, y), fill='#94a3b8')
-
- print("✓ 绘制验证码成功")
-
- # 转换为base64
- buffer = io.BytesIO()
- image.save(buffer, format='PNG')
- buffer.seek(0)
- img_base64 = base64.b64encode(buffer.getvalue()).decode()
-
- print(f"✓ 转换为base64成功 (长度: {len(img_base64)})")
- print("\n验证码功能测试通过!")
-
- # 保存测试图片
- image.save('test_captcha.png')
- print("✓ 测试图片已保存为 test_captcha.png")
-
-except ImportError as e:
- print(f"✗ 缺少依赖库: {e}")
- print("\n请安装 Pillow 库:")
- print(" pip install Pillow")
-except Exception as e:
- print(f"✗ 测试失败: {e}")
- import traceback
- traceback.print_exc()
diff --git a/test_py/test_import.py b/test_py/test_import.py
deleted file mode 100755
index 8cd0c8e..0000000
--- a/test_py/test_import.py
+++ /dev/null
@@ -1,59 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-import sys
-sys.path.insert(0, 'server')
-
-# 测试导入函数的逻辑
-def test_parse():
- # 模拟辅助函数
- def parse_percentage(value):
- if value is None:
- return 0
- if isinstance(value, (int, float)):
- return float(value)
- value_str = str(value).strip()
- if value_str.endswith('%'):
- value_str = value_str[:-1]
- try:
- return float(value_str)
- except:
- return 0
-
- def safe_int(value, default=0):
- if value is None:
- return default
- try:
- return int(float(value))
- except:
- return default
-
- # 测试数据
- test_cases = [
- ('62.28%', 62.28),
- ('62.28', 62.28),
- (62.28, 62.28),
- ('', 0),
- (None, 0),
- ]
-
- print("测试 parse_percentage:")
- for input_val, expected in test_cases:
- result = parse_percentage(input_val)
- status = "✓" if result == expected else "✗"
- print(f" {status} parse_percentage({repr(input_val)}) = {result} (期望: {expected})")
-
- print("\n测试 safe_int:")
- test_int_cases = [
- (100, 100),
- (100.5, 100),
- ('100', 100),
- ('', 0),
- (None, 0),
- ]
- for input_val, expected in test_int_cases:
- result = safe_int(input_val)
- status = "✓" if result == expected else "✗"
- print(f" {status} safe_int({repr(input_val)}) = {result} (期望: {expected})")
-
-if __name__ == '__main__':
- test_parse()
diff --git a/test_py/test_sop_feature.py b/test_py/test_sop_feature.py
deleted file mode 100755
index 0e24d0d..0000000
--- a/test_py/test_sop_feature.py
+++ /dev/null
@@ -1,173 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""
-SOP 功能测试脚本
-用于验证 SOP 文件管理功能是否正常工作
-"""
-
-import os
-import sys
-import sqlite3
-
-# 添加 server 目录到路径
-sys.path.insert(0, 'server')
-
-def test_database_table():
- """测试数据库表是否创建成功"""
- print("测试 1: 检查数据库表...")
-
- db_path = 'server/data.db'
- if not os.path.exists(db_path):
- print(" ❌ 数据库文件不存在")
- return False
-
- conn = sqlite3.connect(db_path)
- cursor = conn.cursor()
-
- # 检查 sop_files 表是否存在
- cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='sop_files'")
- result = cursor.fetchone()
-
- if result:
- print(" ✅ sop_files 表已创建")
-
- # 检查表结构
- cursor.execute("PRAGMA table_info(sop_files)")
- columns = cursor.fetchall()
- print(f" ✅ 表结构包含 {len(columns)} 列:")
- for col in columns:
- print(f" - {col[1]} ({col[2]})")
-
- conn.close()
- return True
- else:
- print(" ❌ sop_files 表不存在")
- conn.close()
- return False
-
-def test_sop_directory():
- """测试 SOP 文件存储目录"""
- print("\n测试 2: 检查 SOP 文件目录...")
-
- sop_dir = 'frontend/sop_files'
-
- if os.path.exists(sop_dir):
- print(f" ✅ 目录已存在: {sop_dir}")
-
- # 检查目录权限
- if os.access(sop_dir, os.W_OK):
- print(" ✅ 目录可写")
- else:
- print(" ⚠️ 目录不可写,可能需要调整权限")
-
- # 列出现有文件
- files = os.listdir(sop_dir)
- if files:
- print(f" 📁 目录中已有 {len(files)} 个文件:")
- for f in files[:5]: # 只显示前5个
- print(f" - {f}")
- else:
- print(" 📁 目录为空")
-
- return True
- else:
- print(f" ⚠️ 目录不存在: {sop_dir}")
- print(" 💡 系统会在首次上传时自动创建")
- return True
-
-def test_api_routes():
- """测试 API 路由是否注册"""
- print("\n测试 3: 检查 API 路由...")
-
- try:
- from app import app
-
- # 获取所有路由
- routes = []
- for rule in app.url_map.iter_rules():
- if 'sop' in rule.rule:
- routes.append(f"{rule.rule} [{', '.join(rule.methods - {'HEAD', 'OPTIONS'})}]")
-
- if routes:
- print(" ✅ SOP API 路由已注册:")
- for route in routes:
- print(f" - {route}")
- return True
- else:
- print(" ❌ 未找到 SOP API 路由")
- return False
- except Exception as e:
- print(f" ❌ 导入失败: {e}")
- return False
-
-def test_frontend_files():
- """测试前端文件是否更新"""
- print("\n测试 4: 检查前端文件...")
-
- files_to_check = {
- 'frontend/js/api.js': ['listSopFiles', 'uploadSopFile', 'deleteSopFile'],
- 'frontend/js/components/upload.js': ['renderSop', 'bindSopEvents', 'loadSopList'],
- 'frontend/js/router.js': ['sop'],
- 'frontend/index.html': ['upload/sop']
- }
-
- all_ok = True
- for filepath, keywords in files_to_check.items():
- if not os.path.exists(filepath):
- print(f" ❌ 文件不存在: {filepath}")
- all_ok = False
- continue
-
- with open(filepath, 'r', encoding='utf-8') as f:
- content = f.read()
-
- missing = [kw for kw in keywords if kw not in content]
- if missing:
- print(f" ⚠️ {filepath} 缺少关键字: {', '.join(missing)}")
- all_ok = False
- else:
- print(f" ✅ {filepath} 已更新")
-
- return all_ok
-
-def main():
- """运行所有测试"""
- print("=" * 60)
- print("SOP 功能测试")
- print("=" * 60)
-
- results = []
-
- # 运行测试
- results.append(("数据库表", test_database_table()))
- results.append(("文件目录", test_sop_directory()))
- results.append(("API 路由", test_api_routes()))
- results.append(("前端文件", test_frontend_files()))
-
- # 汇总结果
- print("\n" + "=" * 60)
- print("测试结果汇总")
- print("=" * 60)
-
- for name, result in results:
- status = "✅ 通过" if result else "❌ 失败"
- print(f"{name}: {status}")
-
- all_passed = all(r[1] for r in results)
-
- print("\n" + "=" * 60)
- if all_passed:
- print("🎉 所有测试通过!SOP 功能已准备就绪。")
- print("\n下一步:")
- print("1. 重启服务器")
- print("2. 登录系统(使用管理员账号)")
- print("3. 进入 '上传' → 'SOP' 菜单")
- print("4. 尝试上传测试文件: sop_template_example.csv")
- else:
- print("⚠️ 部分测试未通过,请检查上述错误信息。")
- print("=" * 60)
-
- return 0 if all_passed else 1
-
-if __name__ == '__main__':
- sys.exit(main())
diff --git a/test_shipment_upload.py b/test_shipment_upload.py
deleted file mode 100755
index 3e26f43..0000000
--- a/test_shipment_upload.py
+++ /dev/null
@@ -1,162 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""
-测试发货单上传解析功能
-"""
-import pandas as pd
-import numpy as np
-
-def test_parse_shipment():
- """测试解析发货单"""
- file_path = '/home/hyx/work/生产管理系统/发货单-20251121.xls'
-
- # 读取Excel文件
- df = pd.read_excel(file_path, header=None)
-
- print("=== 解析发货单头部信息 ===")
-
- # 提取头部信息
- shipment_date = None
- transport_method = None
-
- # 解析发货日期(第1行,索引2)
- if len(df) > 1 and len(df.columns) > 2:
- shipment_date_raw = df.iloc[1, 2]
- if pd.notna(shipment_date_raw):
- if isinstance(shipment_date_raw, pd.Timestamp):
- shipment_date = shipment_date_raw.strftime('%Y-%m-%d')
- else:
- shipment_date = str(shipment_date_raw)
-
- print(f"发货日期: {shipment_date}")
-
- # 解析供货方式(第2行,索引2)
- if len(df) > 2 and len(df.columns) > 2:
- transport_method_raw = df.iloc[2, 2]
- if pd.notna(transport_method_raw):
- transport_method = str(transport_method_raw)
-
- print(f"供货方式(运输单号): {transport_method}")
-
- # 找到表格数据的起始行(序号、采购单号、物料编码...)
- header_row = None
- for i in range(len(df)):
- if df.iloc[i, 0] == '序号':
- header_row = i
- break
-
- if header_row is None:
- print("错误:无法识别发货单格式,未找到表格头部")
- return
-
- print(f"\n表格起始行: {header_row}")
-
- # 从表格起始行读取数据
- data_df = pd.read_excel(file_path, header=header_row)
-
- print(f"表格列名: {data_df.columns.tolist()}")
-
- # 过滤掉合计行和备注行(只保留序号为数字的行)
- valid_data = data_df[data_df['序号'].apply(lambda x: isinstance(x, (int, float)) and not pd.isna(x))]
-
- print(f"\n有效数据行数: {len(valid_data)}")
-
- # 模拟客户订单数据
- customer_orders = {
- 'CGDD002878': {
- 'order_date': '2025-11-15',
- 'material': 'AP-DZ006 灯条基站',
- 'unit_price': 150.5
- },
- 'CGDD003082': {
- 'order_date': '2025-11-18',
- 'material': '飞机盒',
- 'unit_price': 5.0
- }
- }
-
- print("\n=== 解析数据行 ===")
- last_contract_no = None
-
- for idx, row in valid_data.iterrows():
- print(f"\n序号: {int(row['序号'])}")
-
- # 提取数据
- contract_no = row.get('采购单号')
- if pd.isna(contract_no):
- if last_contract_no:
- contract_no = last_contract_no
- print(f" 采购单号: {contract_no} (继承上一行)")
- else:
- print(f" 采购单号: 空 (错误)")
- continue
- else:
- contract_no = str(contract_no).strip()
- last_contract_no = contract_no
- print(f" 采购单号(合同编号): {contract_no}")
-
- material_code = row.get('物料编码')
- if pd.isna(material_code):
- print(f" 物料编码: 空 (错误)")
- continue
- material_code = str(material_code).strip().replace('\n', ' ')
- print(f" 物料编码(物料名称): {material_code}")
-
- spec_model = row.get('规格型号')
- if pd.isna(spec_model):
- spec_model = ''
- else:
- spec_model = str(spec_model).strip()
- print(f" 规格型号: {spec_model}")
-
- quantity = row.get('实送数量')
- if pd.isna(quantity):
- print(f" 实送数量: 空 (错误)")
- continue
- quantity = int(float(quantity))
- print(f" 实送数量(数量): {quantity}")
-
- unit = row.get('单位')
- if pd.isna(unit):
- unit = 'pcs'
- else:
- unit = str(unit).strip()
- print(f" 单位: {unit}")
-
- # 从备注中提取运输单号(如果有)
- remark = row.get('备注')
- transport_no = transport_method or ''
- if pd.notna(remark):
- remark_str = str(remark).strip()
- if remark_str:
- transport_no = remark_str
- print(f" 运输单号: {transport_no}")
-
- # 从客户订单中查找单价和下单时间
- unit_price = 0
- order_date = shipment_date or ''
-
- if contract_no in customer_orders:
- order_info = customer_orders[contract_no]
- # 匹配物料名称
- if material_code in order_info['material']:
- unit_price = order_info['unit_price']
- order_date = order_info['order_date']
- print(f" 含税单价: {unit_price} (从客户订单查找)")
- print(f" 下单时间: {order_date} (从客户订单查找)")
- else:
- print(f" 含税单价: {unit_price} (未找到匹配的客户订单)")
- print(f" 下单时间: {order_date} (使用发货日期)")
- else:
- print(f" 含税单价: {unit_price} (未找到对应的采购单号)")
- print(f" 下单时间: {order_date} (使用发货日期)")
-
- # 计算含税金额
- total_amount = quantity * unit_price
- print(f" 含税金额: {total_amount}")
-
- print(f" 交货日期: {shipment_date}")
- print(f" 出货日期: {shipment_date}")
-
-if __name__ == '__main__':
- test_parse_shipment()
diff --git a/test_toast.html b/test_toast.html
deleted file mode 100755
index 282a677..0000000
--- a/test_toast.html
+++ /dev/null
@@ -1,120 +0,0 @@
-
-
-
-
-
- Toast 测试
-
-
-
- Toast 测试页面
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/update_factory.py b/update_factory.py
deleted file mode 100755
index 1d828d7..0000000
--- a/update_factory.py
+++ /dev/null
@@ -1,49 +0,0 @@
-#!/usr/bin/env python3
-"""
-将现有期初库存和采购需求数据的工厂字段更新为"友辉"
-"""
-
-import sqlite3
-
-DB_PATH = 'server/data.db'
-
-def update_factory():
- """更新现有数据的工厂字段为友辉"""
- conn = sqlite3.connect(DB_PATH)
- c = conn.cursor()
-
- try:
- # 更新期初库存表
- c.execute("UPDATE initial_stock SET factory='友辉' WHERE factory IS NULL OR factory=''")
- initial_stock_rows = c.rowcount
-
- # 更新采购需求表
- c.execute("UPDATE purchase_demand SET factory='友辉' WHERE factory IS NULL OR factory=''")
- purchase_demand_rows = c.rowcount
-
- conn.commit()
-
- print(f"✅ 期初库存表:成功更新 {initial_stock_rows} 条记录的工厂字段为'友辉'")
- print(f"✅ 采购需求表:成功更新 {purchase_demand_rows} 条记录的工厂字段为'友辉'")
-
- # 查询验证
- c.execute("SELECT COUNT(*) FROM initial_stock WHERE factory='友辉'")
- initial_count = c.fetchone()[0]
- c.execute("SELECT COUNT(*) FROM purchase_demand WHERE factory='友辉'")
- demand_count = c.fetchone()[0]
-
- print(f"📊 当前友辉工厂的期初库存记录数: {initial_count}")
- print(f"📊 当前友辉工厂的采购需求记录数: {demand_count}")
-
- except Exception as e:
- print(f"❌ 更新失败: {e}")
- conn.rollback()
- finally:
- conn.close()
-
-if __name__ == '__main__':
- print("开始更新数据的工厂字段...")
- print("=" * 50)
- update_factory()
- print("=" * 50)
- print("更新完成!")
diff --git a/发货单-20251121.xls b/发货单-20251121.xls
deleted file mode 100755
index 5701199..0000000
Binary files a/发货单-20251121.xls and /dev/null differ