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将为您分析生产数据并生成洞察
+
+ + + + + + + + + +
+ + +
+
+ +
+
+ + +
+
+
+ `; + }; + + // 生成报表 + 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.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 @@ + + + + + +登录 - 韬智生产管理系统 + + + + + + + +
+
+
+
+

韬智生产管理系统

+

Production Management System

+
+
+
+ + +
+
+ + +
+
+ + +
+ 验证码 +
+
+ +
+ © 2025 韬智科技 + +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + + 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