优化细节
This commit is contained in:
parent
8a1eda390a
commit
3751854815
@ -103,6 +103,10 @@
|
||||
<span class="child-icon">🛒</span>
|
||||
<span>物料清单-采购</span>
|
||||
</a>
|
||||
<a href="#/plan-mgmt/customer-order" class="nav-child" data-route="plan-mgmt-customer-order">
|
||||
<span class="child-icon">📋</span>
|
||||
<span>客户订单</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -221,6 +225,7 @@
|
||||
<script src="./js/components/production.js"></script>
|
||||
<script src="./js/components/work-order.js"></script>
|
||||
<script src="./js/components/material-purchase.js"></script>
|
||||
<script src="./js/components/customer-order.js"></script>
|
||||
<script src="./js/components/export.js"></script>
|
||||
<script src="./js/components/settings.js"></script>
|
||||
<script src="./js/components/notifications.js"></script>
|
||||
|
||||
@ -30,6 +30,29 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 根据用户角色控制菜单显示
|
||||
function updateMenuVisibility(user) {
|
||||
// 查找计划管理菜单组(包含标题"计划管理"的nav-group)
|
||||
const navGroups = document.querySelectorAll('.nav-group');
|
||||
let planMgmtGroup = null;
|
||||
|
||||
navGroups.forEach(group => {
|
||||
const title = group.querySelector('.nav-group-title');
|
||||
if (title && title.textContent.trim() === '计划管理') {
|
||||
planMgmtGroup = group;
|
||||
}
|
||||
});
|
||||
|
||||
if (planMgmtGroup) {
|
||||
// 只有超级管理员可以看到计划管理菜单
|
||||
if (user && user.role === 'superadmin') {
|
||||
planMgmtGroup.style.display = '';
|
||||
} else {
|
||||
planMgmtGroup.style.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 创建水印
|
||||
function createWatermark(username) {
|
||||
// 检查水印是否启用
|
||||
@ -91,6 +114,9 @@
|
||||
document.getElementById('overlay').classList.add('hidden');
|
||||
updateUserDisplay(currentUser);
|
||||
|
||||
// 根据用户角色控制菜单显示
|
||||
updateMenuVisibility(currentUser);
|
||||
|
||||
// 初始化通知系统(超级管理员和管理员)
|
||||
if (currentUser && (currentUser.role === 'superadmin' || currentUser.role === 'admin') && window.NotificationSystem) {
|
||||
window.NotificationSystem.init();
|
||||
@ -102,6 +128,7 @@
|
||||
API.me().then(user => {
|
||||
currentUser = user;
|
||||
updateUserDisplay(user);
|
||||
updateMenuVisibility(user);
|
||||
|
||||
// 初始化通知系统(超级管理员和管理员)
|
||||
if (user && (user.role === 'superadmin' || user.role === 'admin') && window.NotificationSystem) {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
Router.register('/settings', async () => {
|
||||
const me = await API.me().catch(()=>({}));
|
||||
const users = (me && me.role === 'superadmin') ? await API.adminUsers().catch(()=>({list:[]})) : {list:[]};
|
||||
const userList = (users.list||[]).map(u=>`<li style="display:flex;justify-content:space-between;align-items:center"><div><span style="margin-right:8px">${u.username}</span><span class="badge">${u.role}</span></div><button class="btn btn-secondary" data-delete-user="${u.username}" style="padding:4px 8px;font-size:12px">删除</button></li>`).join('') || '<li>暂无用户</li>';
|
||||
const userList = (users.list||[]).map(u=>`<li style="display:flex;justify-content:space-between;align-items:center"><div><span style="margin-right:8px">${u.username}</span><span class="badge">${u.role}</span>${u.factory ? `<span class="badge" style="margin-left:8px;background:var(--info)">${u.factory}</span>` : ''}</div><button class="btn btn-secondary" data-delete-user="${u.username}" style="padding:4px 8px;font-size:12px">删除</button></li>`).join('') || '<li>暂无用户</li>';
|
||||
const html = `
|
||||
<div class="page-container">
|
||||
<div class="page-header">
|
||||
@ -14,7 +14,10 @@ Router.register('/settings', async () => {
|
||||
|
||||
<div style="margin-bottom:24px">
|
||||
<div style="font-weight:500;margin-bottom:8px;color:var(--text-2)">当前登录用户</div>
|
||||
<div id="user-card" class="badge" style="font-size:16px;padding:8px 16px">${(me && me.username) ? me.username : '未登录'}</div>
|
||||
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">
|
||||
<div id="user-card" class="badge" style="font-size:16px;padding:8px 16px">${(me && me.username) ? me.username : '未登录'}</div>
|
||||
${(me && me.factory) ? `<div class="badge" style="font-size:14px;padding:6px 12px;background:var(--info)">🏭 ${me.factory}</div>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="padding:20px;background:var(--surface);border-radius:8px;border:1px solid var(--border)">
|
||||
@ -58,6 +61,10 @@ Router.register('/settings', async () => {
|
||||
<label>密码</label>
|
||||
<input id="new-password" type="password" class="input" placeholder="输入密码(至少6位)" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>所属工厂</label>
|
||||
<input id="new-factory" class="input" placeholder="输入所属工厂" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>角色</label>
|
||||
<select id="new-role" class="input">
|
||||
@ -87,6 +94,24 @@ Router.register('/settings', async () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 修改用户工厂 -->
|
||||
<div style="padding:20px;background:var(--surface);border-radius:8px;border:1px solid var(--border);margin-bottom:24px">
|
||||
<div style="font-weight:600;margin-bottom:16px;font-size:15px">🏭 修改用户所属工厂</div>
|
||||
<div class="grid cols-2" style="gap:16px">
|
||||
<div class="field">
|
||||
<label>选择用户</label>
|
||||
<select id="factory-user" class="input">${(users.list||[]).map(u=>`<option value="${u.username}" data-factory="${u.factory || ''}">${u.username}${u.factory ? ` (${u.factory})` : ''}</option>`).join('')}</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>新工厂</label>
|
||||
<input id="factory-name" class="input" placeholder="输入新的工厂名称" value="${(users.list||[])[0]?.factory || ''}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button id="update-factory-btn" class="btn btn-primary" style="width:100%">更新工厂</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 用户列表 -->
|
||||
<div style="padding:20px;background:var(--surface);border-radius:8px;border:1px solid var(--border)">
|
||||
<div style="font-weight:600;margin-bottom:12px;font-size:15px">📋 用户列表</div>
|
||||
@ -277,16 +302,22 @@ Router.register('/settings', async () => {
|
||||
addUserBtn?.addEventListener('click', async () => {
|
||||
const usernameEl = document.getElementById('new-username');
|
||||
const passwordEl = document.getElementById('new-password');
|
||||
const factoryEl = document.getElementById('new-factory');
|
||||
const roleEl = document.getElementById('new-role');
|
||||
|
||||
const username = usernameEl?.value?.trim();
|
||||
const password = passwordEl?.value;
|
||||
const factory = factoryEl?.value?.trim();
|
||||
const role = roleEl?.value || 'admin';
|
||||
|
||||
if (!username || !password) {
|
||||
return API.toast('请输入用户名和密码');
|
||||
}
|
||||
|
||||
if (!factory) {
|
||||
return API.toast('请输入所属工厂');
|
||||
}
|
||||
|
||||
if (password.length < 6) {
|
||||
return API.toast('密码长度至少6位');
|
||||
}
|
||||
@ -297,7 +328,7 @@ Router.register('/settings', async () => {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ username, password, role })
|
||||
body: JSON.stringify({ username, password, factory, role })
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
@ -307,6 +338,7 @@ Router.register('/settings', async () => {
|
||||
// 清空输入框
|
||||
if (usernameEl) usernameEl.value = '';
|
||||
if (passwordEl) passwordEl.value = '';
|
||||
if (factoryEl) factoryEl.value = '';
|
||||
// 刷新页面以更新用户列表
|
||||
setTimeout(() => Router.navigate('/settings'), 1000);
|
||||
} else {
|
||||
@ -337,6 +369,57 @@ Router.register('/settings', async () => {
|
||||
change.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
// 更新用户工厂
|
||||
const factoryUserSelect = document.getElementById('factory-user');
|
||||
const factoryNameInput = document.getElementById('factory-name');
|
||||
|
||||
// 当选择用户时,自动填充当前工厂
|
||||
factoryUserSelect?.addEventListener('change', () => {
|
||||
const selectedOption = factoryUserSelect.options[factoryUserSelect.selectedIndex];
|
||||
const currentFactory = selectedOption.getAttribute('data-factory') || '';
|
||||
if (factoryNameInput) {
|
||||
factoryNameInput.value = currentFactory;
|
||||
}
|
||||
});
|
||||
|
||||
const updateFactoryBtn = document.getElementById('update-factory-btn');
|
||||
updateFactoryBtn?.addEventListener('click', async () => {
|
||||
const username = factoryUserSelect?.value;
|
||||
const factory = factoryNameInput?.value?.trim();
|
||||
|
||||
if (!username) {
|
||||
return API.toast('请选择用户');
|
||||
}
|
||||
|
||||
if (!factory) {
|
||||
return API.toast('请输入工厂名称');
|
||||
}
|
||||
|
||||
updateFactoryBtn.disabled = true;
|
||||
try {
|
||||
const res = await fetch('/api/admin/update-user-factory', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ username, factory })
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
if (res.ok && data.ok) {
|
||||
API.toast(data.message || '工厂更新成功');
|
||||
// 刷新页面以更新用户列表
|
||||
setTimeout(() => Router.navigate('/settings'), 1000);
|
||||
} else {
|
||||
API.toast(data.error || '更新失败');
|
||||
}
|
||||
} catch(e) {
|
||||
API.toast('更新失败:' + e.message);
|
||||
} finally {
|
||||
updateFactoryBtn.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
// 水印开关
|
||||
const watermarkToggle = document.getElementById('watermark-toggle');
|
||||
|
||||
@ -116,6 +116,8 @@
|
||||
Router.register('/production-mgmt/work-order', async () => {
|
||||
const html = await render();
|
||||
setTimeout(async () => {
|
||||
// 等待DOM完全渲染
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
await initPage();
|
||||
}, 0);
|
||||
return html;
|
||||
@ -249,6 +251,12 @@
|
||||
const prevBtn = document.getElementById('prev-page');
|
||||
const nextBtn = document.getElementById('next-page');
|
||||
|
||||
// 检查DOM元素是否存在
|
||||
if (!tbody || !pageInfo || !prevBtn || !nextBtn) {
|
||||
console.warn('工单页面DOM元素未就绪,跳过加载');
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取筛选条件
|
||||
const factory = document.getElementById('factory-filter')?.value || '';
|
||||
const order = document.getElementById('order-filter')?.value || '';
|
||||
@ -346,6 +354,7 @@
|
||||
const modal = document.getElementById('work-order-modal');
|
||||
const modalTitle = document.getElementById('modal-title');
|
||||
const form = document.getElementById('work-order-form');
|
||||
const factoryInput = document.getElementById('form-factory');
|
||||
|
||||
form.reset();
|
||||
|
||||
@ -353,7 +362,7 @@
|
||||
// 编辑模式
|
||||
modalTitle.textContent = '编辑工单';
|
||||
document.getElementById('form-order-id').value = order.id;
|
||||
document.getElementById('form-factory').value = order.factory;
|
||||
factoryInput.value = order.factory;
|
||||
document.getElementById('form-order-no').value = order.orderNo;
|
||||
document.getElementById('form-product-model').value = order.productModel || '';
|
||||
document.getElementById('form-order-qty').value = order.orderQty;
|
||||
@ -364,6 +373,18 @@
|
||||
// 添加模式
|
||||
modalTitle.textContent = '添加工单';
|
||||
document.getElementById('form-order-id').value = '';
|
||||
|
||||
// 如果是管理员,自动填充工厂并设为只读
|
||||
if (currentUser && currentUser.role === 'admin' && currentUser.factory) {
|
||||
factoryInput.value = currentUser.factory;
|
||||
factoryInput.readOnly = true;
|
||||
factoryInput.style.backgroundColor = 'var(--surface)';
|
||||
factoryInput.style.cursor = 'not-allowed';
|
||||
} else {
|
||||
factoryInput.readOnly = false;
|
||||
factoryInput.style.backgroundColor = '';
|
||||
factoryInput.style.cursor = '';
|
||||
}
|
||||
}
|
||||
|
||||
modal.style.display = 'flex';
|
||||
|
||||
@ -85,6 +85,7 @@ const Router = (() => {
|
||||
'work-order': '生产工单下发中心',
|
||||
'plan-mgmt': '计划管理',
|
||||
'material-purchase': '物料清单-采购',
|
||||
'customer-order': '客户订单',
|
||||
export: '导出',
|
||||
settings: '设置'
|
||||
};
|
||||
|
||||
313
server/app.py
313
server/app.py
@ -81,6 +81,10 @@ def init_db():
|
||||
c.execute('ALTER TABLE users ADD COLUMN avatar TEXT')
|
||||
except Exception:
|
||||
pass # 列已存在
|
||||
try:
|
||||
c.execute('ALTER TABLE users ADD COLUMN factory TEXT')
|
||||
except Exception:
|
||||
pass # 列已存在
|
||||
try:
|
||||
c.execute('ALTER TABLE mac_batches ADD COLUMN platform TEXT DEFAULT "pdd"')
|
||||
except Exception:
|
||||
@ -184,6 +188,23 @@ def init_db():
|
||||
deleted INTEGER DEFAULT 0,
|
||||
deleted_at TEXT
|
||||
)''')
|
||||
c.execute('''CREATE TABLE IF NOT EXISTS customer_orders(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
order_date TEXT NOT NULL,
|
||||
order_no TEXT NOT NULL,
|
||||
customer_name TEXT NOT NULL,
|
||||
material TEXT NOT NULL,
|
||||
quantity INTEGER NOT NULL,
|
||||
unit_price REAL NOT NULL,
|
||||
created_by TEXT,
|
||||
created_at TEXT,
|
||||
updated_at TEXT
|
||||
)''')
|
||||
# 为已存在的表添加列(如果不存在)
|
||||
try:
|
||||
c.execute('ALTER TABLE customer_orders ADD COLUMN customer_name TEXT')
|
||||
except Exception:
|
||||
pass # 列已存在
|
||||
# 为已存在的表添加列(如果不存在)
|
||||
try:
|
||||
c.execute('ALTER TABLE work_orders ADD COLUMN product_model TEXT')
|
||||
@ -308,6 +329,52 @@ def notify_admins(action, detail=''):
|
||||
pass
|
||||
|
||||
|
||||
def notify_admins_by_factory(action, detail='', factory=None):
|
||||
"""为指定工厂的管理员创建通知(超级管理员操作时使用)"""
|
||||
try:
|
||||
user_id = session.get('user_id')
|
||||
if not user_id:
|
||||
return
|
||||
|
||||
conn = get_db()
|
||||
c = conn.cursor()
|
||||
|
||||
# 获取当前用户信息
|
||||
c.execute('SELECT username, role FROM users WHERE id=?', (user_id,))
|
||||
user = c.fetchone()
|
||||
if not user:
|
||||
conn.close()
|
||||
return
|
||||
|
||||
# 只有超级管理员的操作才通知管理员
|
||||
if user['role'] != 'superadmin':
|
||||
conn.close()
|
||||
return
|
||||
|
||||
# 如果指定了工厂,只通知该工厂的管理员;否则通知所有管理员
|
||||
if factory:
|
||||
c.execute('SELECT id FROM users WHERE role=? AND factory=?', ('admin', factory))
|
||||
else:
|
||||
c.execute('SELECT id FROM users WHERE role=?', ('admin',))
|
||||
|
||||
admins = c.fetchall()
|
||||
|
||||
# 使用北京时间(UTC+8)
|
||||
from datetime import timezone, timedelta
|
||||
beijing_tz = timezone(timedelta(hours=8))
|
||||
now = datetime.now(beijing_tz).isoformat()
|
||||
|
||||
for admin in admins:
|
||||
c.execute('INSERT INTO notifications(user_id, username, action, detail, ts, read) VALUES(?,?,?,?,?,?)', (
|
||||
admin['id'], user['username'], action, detail, now, 0
|
||||
))
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def get_redis():
|
||||
global _redis_client
|
||||
if not redis:
|
||||
@ -547,13 +614,18 @@ def login():
|
||||
def me():
|
||||
uid = session.get('user_id')
|
||||
if not uid:
|
||||
return jsonify({'username': None, 'role': None, 'avatar': None})
|
||||
return jsonify({'username': None, 'role': None, 'avatar': None, 'factory': None})
|
||||
conn = get_db()
|
||||
c = conn.cursor()
|
||||
c.execute('SELECT username, role, avatar FROM users WHERE id=?', (uid,))
|
||||
c.execute('SELECT username, role, avatar, factory FROM users WHERE id=?', (uid,))
|
||||
row = c.fetchone()
|
||||
conn.close()
|
||||
return jsonify({'username': row['username'], 'role': row['role'], 'avatar': row['avatar'] if row['avatar'] else None})
|
||||
return jsonify({
|
||||
'username': row['username'],
|
||||
'role': row['role'],
|
||||
'avatar': row['avatar'] if row['avatar'] else None,
|
||||
'factory': row['factory'] if row['factory'] else None
|
||||
})
|
||||
|
||||
|
||||
@app.post('/api/auth/logout')
|
||||
@ -1623,7 +1695,7 @@ def list_shipments():
|
||||
def list_users():
|
||||
conn = get_db()
|
||||
c = conn.cursor()
|
||||
c.execute('SELECT username, role FROM users ORDER BY id ASC')
|
||||
c.execute('SELECT username, role, factory FROM users ORDER BY id ASC')
|
||||
rows = [dict(r) for r in c.fetchall()]
|
||||
conn.close()
|
||||
return jsonify({'list': rows})
|
||||
@ -1675,6 +1747,37 @@ def change_password():
|
||||
return jsonify({'ok': True})
|
||||
|
||||
|
||||
@app.post('/api/admin/update-user-factory')
|
||||
@require_login
|
||||
@require_any_role('superadmin')
|
||||
def update_user_factory():
|
||||
"""更新用户所属工厂"""
|
||||
data = request.get_json() or {}
|
||||
username = data.get('username')
|
||||
factory = (data.get('factory') or '').strip()
|
||||
|
||||
if not username:
|
||||
return jsonify({'error': '用户名不能为空'}), 400
|
||||
|
||||
if not factory:
|
||||
return jsonify({'error': '工厂名称不能为空'}), 400
|
||||
|
||||
conn = get_db()
|
||||
c = conn.cursor()
|
||||
c.execute('SELECT id FROM users WHERE username=?', (username,))
|
||||
row = c.fetchone()
|
||||
if not row:
|
||||
conn.close()
|
||||
return jsonify({'error': '用户不存在'}), 404
|
||||
|
||||
c.execute('UPDATE users SET factory=? WHERE id=?', (factory, row['id']))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
log('update_user_factory', f'username={username}, factory={factory}')
|
||||
return jsonify({'ok': True, 'message': f'已更新用户 {username} 的所属工厂为 {factory}'})
|
||||
|
||||
|
||||
@app.post('/api/admin/add-user')
|
||||
@require_login
|
||||
@require_any_role('superadmin')
|
||||
@ -1684,10 +1787,14 @@ def add_user():
|
||||
username = (data.get('username') or '').strip()
|
||||
password = data.get('password')
|
||||
role = (data.get('role') or 'admin').strip()
|
||||
factory = (data.get('factory') or '').strip()
|
||||
|
||||
if not username or not password:
|
||||
return jsonify({'error': '用户名和密码不能为空'}), 400
|
||||
|
||||
if not factory:
|
||||
return jsonify({'error': '所属工厂不能为空'}), 400
|
||||
|
||||
if role not in ['admin', 'superadmin']:
|
||||
return jsonify({'error': '角色必须是 admin 或 superadmin'}), 400
|
||||
|
||||
@ -1702,12 +1809,12 @@ def add_user():
|
||||
|
||||
# 创建新用户
|
||||
try:
|
||||
c.execute('INSERT INTO users(username, password_hash, role) VALUES(?,?,?)',
|
||||
(username, generate_password_hash(password), role))
|
||||
c.execute('INSERT INTO users(username, password_hash, role, factory) VALUES(?,?,?,?)',
|
||||
(username, generate_password_hash(password), role, factory))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
log('add_user', f'username={username}, role={role}')
|
||||
log('add_user', f'username={username}, role={role}, factory={factory}')
|
||||
return jsonify({'ok': True, 'message': f'用户 {username} 创建成功'})
|
||||
except Exception as e:
|
||||
conn.close()
|
||||
@ -2780,12 +2887,26 @@ def get_work_orders():
|
||||
order_no = request.args.get('order', '')
|
||||
date = request.args.get('date', '')
|
||||
|
||||
# 获取当前用户信息
|
||||
user_id = session.get('user_id')
|
||||
user_role = session.get('role')
|
||||
|
||||
conn = get_db()
|
||||
c = conn.cursor()
|
||||
|
||||
# 获取用户所属工厂
|
||||
c.execute('SELECT factory FROM users WHERE id=?', (user_id,))
|
||||
user_row = c.fetchone()
|
||||
user_factory = user_row['factory'] if user_row and user_row['factory'] else None
|
||||
|
||||
query = 'SELECT * FROM work_orders WHERE 1=1'
|
||||
params = []
|
||||
|
||||
# 如果是管理员(非超级管理员),只能看到自己工厂的订单
|
||||
if user_role == 'admin' and user_factory:
|
||||
query += ' AND factory = ?'
|
||||
params.append(user_factory)
|
||||
|
||||
if factory:
|
||||
query += ' AND factory LIKE ?'
|
||||
params.append(f'%{factory}%')
|
||||
@ -2840,13 +2961,28 @@ def create_work_order():
|
||||
production_end_time = data.get('productionEndTime', '')
|
||||
remark = data.get('remark', '').strip()
|
||||
|
||||
if not factory or not order_no or not order_qty:
|
||||
return jsonify({'error': '请填写所有必填项'}), 400
|
||||
# 获取当前用户信息
|
||||
user_id = session.get('user_id')
|
||||
user_role = session.get('role')
|
||||
username = session.get('username', '')
|
||||
|
||||
conn = get_db()
|
||||
c = conn.cursor()
|
||||
|
||||
username = session.get('username', '')
|
||||
# 如果是管理员(非超级管理员),强制使用用户自己的工厂
|
||||
if user_role == 'admin':
|
||||
c.execute('SELECT factory FROM users WHERE id=?', (user_id,))
|
||||
user_row = c.fetchone()
|
||||
if user_row and user_row['factory']:
|
||||
factory = user_row['factory']
|
||||
else:
|
||||
conn.close()
|
||||
return jsonify({'error': '您的账户未设置所属工厂,请联系超级管理员'}), 400
|
||||
|
||||
if not factory or not order_no or not order_qty:
|
||||
conn.close()
|
||||
return jsonify({'error': '请填写所有必填项'}), 400
|
||||
|
||||
now = get_beijing_time()
|
||||
|
||||
c.execute('''INSERT INTO work_orders(
|
||||
@ -2863,17 +2999,17 @@ def create_work_order():
|
||||
|
||||
log('create_work_order', f'工单号: {order_no}, 工厂: {factory}, 型号: {product_model}')
|
||||
|
||||
# 如果是超级管理员添加工单,通知所有管理员
|
||||
notify_admins('添加工单', f'工单号: {order_no}, 工厂: {factory}, 数量: {order_qty}')
|
||||
# 如果是超级管理员添加工单,通知该工厂的管理员
|
||||
notify_admins_by_factory('添加工单', f'工单号: {order_no}, 工厂: {factory}, 数量: {order_qty}', factory=factory)
|
||||
|
||||
return jsonify({'ok': True, 'id': order_id, 'message': '工单创建成功'})
|
||||
|
||||
|
||||
@app.put('/api/work-orders/<int:order_id>')
|
||||
@require_login
|
||||
@require_any_role('admin','superadmin')
|
||||
@require_any_role('superadmin')
|
||||
def update_work_order(order_id):
|
||||
"""更新工单"""
|
||||
"""更新工单(仅超级管理员)"""
|
||||
data = request.get_json()
|
||||
|
||||
factory = data.get('factory', '').strip()
|
||||
@ -2910,16 +3046,16 @@ def update_work_order(order_id):
|
||||
conn.close()
|
||||
|
||||
log('update_work_order', f'工单ID: {order_id}, 工单号: {order_no}, 型号: {product_model}')
|
||||
notify_superadmin('更新工单', f'工单号: {order_no}, 工厂: {factory}, 型号: {product_model}')
|
||||
notify_admins_by_factory('更新工单', f'工单号: {order_no}, 工厂: {factory}, 型号: {product_model}', factory=factory)
|
||||
|
||||
return jsonify({'ok': True, 'message': '工单更新成功'})
|
||||
|
||||
|
||||
@app.delete('/api/work-orders/<int:order_id>')
|
||||
@require_login
|
||||
@require_any_role('admin','superadmin')
|
||||
@require_any_role('superadmin')
|
||||
def delete_work_order(order_id):
|
||||
"""删除工单"""
|
||||
"""删除工单(仅超级管理员)"""
|
||||
conn = get_db()
|
||||
c = conn.cursor()
|
||||
|
||||
@ -2939,7 +3075,7 @@ def delete_work_order(order_id):
|
||||
conn.close()
|
||||
|
||||
log('delete_work_order', f'工单ID: {order_id}, 工单号: {order_no}')
|
||||
notify_superadmin('删除工单', f'工单号: {order_no}, 工厂: {factory}')
|
||||
notify_admins_by_factory('删除工单', f'工单号: {order_no}, 工厂: {factory}', factory=factory)
|
||||
|
||||
return jsonify({'ok': True, 'message': '工单删除成功'})
|
||||
|
||||
@ -2949,6 +3085,10 @@ def delete_work_order(order_id):
|
||||
@require_any_role('admin','superadmin')
|
||||
def confirm_work_order(order_id):
|
||||
"""确认工单"""
|
||||
# 获取当前用户信息
|
||||
user_id = session.get('user_id')
|
||||
user_role = session.get('role')
|
||||
|
||||
conn = get_db()
|
||||
c = conn.cursor()
|
||||
|
||||
@ -2964,6 +3104,16 @@ def confirm_work_order(order_id):
|
||||
factory = row['factory']
|
||||
current_status = row['status']
|
||||
|
||||
# 如果是管理员(非超级管理员),检查工单是否属于自己的工厂
|
||||
if user_role == 'admin':
|
||||
c.execute('SELECT factory FROM users WHERE id=?', (user_id,))
|
||||
user_row = c.fetchone()
|
||||
user_factory = user_row['factory'] if user_row and user_row['factory'] else None
|
||||
|
||||
if not user_factory or factory != user_factory:
|
||||
conn.close()
|
||||
return jsonify({'error': '您只能确认自己工厂的工单'}), 403
|
||||
|
||||
# 如果已经确认,返回提示
|
||||
if current_status == 'confirmed':
|
||||
conn.close()
|
||||
@ -3020,6 +3170,7 @@ def convert_material_purchase_to_camel(row):
|
||||
|
||||
@app.get('/api/material-purchase/list')
|
||||
@require_login
|
||||
@require_any_role('superadmin')
|
||||
def list_material_purchase():
|
||||
"""获取物料清单列表(不包括已删除的)"""
|
||||
conn = get_db()
|
||||
@ -3034,6 +3185,7 @@ def list_material_purchase():
|
||||
|
||||
@app.get('/api/material-purchase/recycle-bin')
|
||||
@require_login
|
||||
@require_any_role('superadmin')
|
||||
def list_material_purchase_recycle_bin():
|
||||
"""获取回收站列表"""
|
||||
conn = get_db()
|
||||
@ -3048,7 +3200,7 @@ def list_material_purchase_recycle_bin():
|
||||
|
||||
@app.post('/api/material-purchase/add')
|
||||
@require_login
|
||||
@require_any_role('admin', 'superadmin')
|
||||
@require_any_role('superadmin')
|
||||
def add_material_purchase():
|
||||
"""新增物料需求"""
|
||||
data = request.get_json() or {}
|
||||
@ -3089,7 +3241,7 @@ def add_material_purchase():
|
||||
|
||||
@app.post('/api/material-purchase/update')
|
||||
@require_login
|
||||
@require_any_role('admin', 'superadmin')
|
||||
@require_any_role('superadmin')
|
||||
def update_material_purchase():
|
||||
"""更新物料需求"""
|
||||
data = request.get_json() or {}
|
||||
@ -3126,7 +3278,7 @@ def update_material_purchase():
|
||||
|
||||
@app.post('/api/material-purchase/delete')
|
||||
@require_login
|
||||
@require_any_role('admin', 'superadmin')
|
||||
@require_any_role('superadmin')
|
||||
def delete_material_purchase():
|
||||
"""删除物料需求(移到回收站)"""
|
||||
data = request.get_json() or {}
|
||||
@ -3153,7 +3305,7 @@ def delete_material_purchase():
|
||||
|
||||
@app.post('/api/material-purchase/restore')
|
||||
@require_login
|
||||
@require_any_role('admin', 'superadmin')
|
||||
@require_any_role('superadmin')
|
||||
def restore_material_purchase():
|
||||
"""从回收站恢复"""
|
||||
data = request.get_json() or {}
|
||||
@ -3178,7 +3330,7 @@ def restore_material_purchase():
|
||||
|
||||
@app.post('/api/material-purchase/permanent-delete')
|
||||
@require_login
|
||||
@require_any_role('admin', 'superadmin')
|
||||
@require_any_role('superadmin')
|
||||
def permanent_delete_material_purchase():
|
||||
"""永久删除"""
|
||||
data = request.get_json() or {}
|
||||
@ -3201,7 +3353,7 @@ def permanent_delete_material_purchase():
|
||||
|
||||
@app.post('/api/material-purchase/empty-recycle-bin')
|
||||
@require_login
|
||||
@require_any_role('admin', 'superadmin')
|
||||
@require_any_role('superadmin')
|
||||
def empty_recycle_bin():
|
||||
"""清空回收站"""
|
||||
conn = get_db()
|
||||
@ -3219,7 +3371,7 @@ def empty_recycle_bin():
|
||||
|
||||
@app.route('/api/validate/material-purchase-file', methods=['POST'])
|
||||
@require_login
|
||||
@require_any_role('admin', 'superadmin')
|
||||
@require_any_role('superadmin')
|
||||
def validate_material_purchase_file():
|
||||
"""验证物料清单Excel文件格式"""
|
||||
f = request.files.get('file')
|
||||
@ -3274,7 +3426,7 @@ def validate_material_purchase_file():
|
||||
|
||||
@app.post('/api/upload/material-purchase-file')
|
||||
@require_login
|
||||
@require_any_role('admin', 'superadmin')
|
||||
@require_any_role('superadmin')
|
||||
def upload_material_purchase_file():
|
||||
"""上传物料清单Excel文件"""
|
||||
f = request.files.get('file')
|
||||
@ -3398,6 +3550,115 @@ def upload_material_purchase_file():
|
||||
return jsonify({'error': f'导入失败:{str(e)}'}), 500
|
||||
|
||||
|
||||
# ==================== 客户订单 API ====================
|
||||
|
||||
@app.get('/api/customer-orders')
|
||||
@require_login
|
||||
@require_any_role('superadmin')
|
||||
def get_customer_orders():
|
||||
"""获取客户订单列表"""
|
||||
conn = get_db()
|
||||
c = conn.cursor()
|
||||
c.execute('''SELECT id, order_date, order_no, customer_name, material, quantity, unit_price,
|
||||
created_by, created_at, updated_at
|
||||
FROM customer_orders
|
||||
ORDER BY order_date DESC, id DESC''')
|
||||
rows = c.fetchall()
|
||||
conn.close()
|
||||
|
||||
orders = []
|
||||
for row in rows:
|
||||
orders.append({
|
||||
'id': row['id'],
|
||||
'order_date': row['order_date'],
|
||||
'order_no': row['order_no'],
|
||||
'customer_name': row['customer_name'] if 'customer_name' in row.keys() else '',
|
||||
'material': row['material'],
|
||||
'quantity': row['quantity'],
|
||||
'unit_price': row['unit_price'],
|
||||
'created_by': row['created_by'],
|
||||
'created_at': row['created_at'],
|
||||
'updated_at': row['updated_at']
|
||||
})
|
||||
|
||||
return jsonify({'list': orders})
|
||||
|
||||
|
||||
@app.post('/api/customer-orders')
|
||||
@require_login
|
||||
@require_any_role('superadmin')
|
||||
def create_customer_order():
|
||||
"""创建客户订单"""
|
||||
data = request.get_json() or {}
|
||||
|
||||
order_date = data.get('order_date', '').strip()
|
||||
order_no = data.get('order_no', '').strip()
|
||||
customer_name = data.get('customer_name', '').strip()
|
||||
material = data.get('material', '').strip()
|
||||
quantity = data.get('quantity', 0)
|
||||
unit_price = data.get('unit_price', 0)
|
||||
|
||||
if not order_date or not order_no or not customer_name or not material:
|
||||
return jsonify({'error': '请填写所有必填项'}), 400
|
||||
|
||||
if quantity <= 0:
|
||||
return jsonify({'error': '订单数量必须大于0'}), 400
|
||||
|
||||
if unit_price < 0:
|
||||
return jsonify({'error': '单价不能为负数'}), 400
|
||||
|
||||
username = session.get('username', '')
|
||||
now = get_beijing_time()
|
||||
|
||||
conn = get_db()
|
||||
c = conn.cursor()
|
||||
|
||||
c.execute('''INSERT INTO customer_orders(
|
||||
order_date, order_no, customer_name, material, quantity, unit_price,
|
||||
created_by, created_at, updated_at
|
||||
) VALUES(?,?,?,?,?,?,?,?,?)''', (
|
||||
order_date, order_no, customer_name, material, quantity, unit_price,
|
||||
username, now, now
|
||||
))
|
||||
|
||||
order_id = c.lastrowid
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
log('create_customer_order', f'客户: {customer_name}, 订单号: {order_no}, 物料: {material}, 数量: {quantity}')
|
||||
|
||||
return jsonify({'ok': True, 'id': order_id, 'message': '订单创建成功'})
|
||||
|
||||
|
||||
@app.delete('/api/customer-orders/<int:order_id>')
|
||||
@require_login
|
||||
@require_any_role('superadmin')
|
||||
def delete_customer_order(order_id):
|
||||
"""删除客户订单"""
|
||||
conn = get_db()
|
||||
c = conn.cursor()
|
||||
|
||||
# 获取订单信息用于日志
|
||||
c.execute('SELECT order_no, customer_name, material FROM customer_orders WHERE id=?', (order_id,))
|
||||
row = c.fetchone()
|
||||
|
||||
if not row:
|
||||
conn.close()
|
||||
return jsonify({'error': '订单不存在'}), 404
|
||||
|
||||
order_no = row['order_no']
|
||||
customer_name = row['customer_name'] if 'customer_name' in row.keys() else ''
|
||||
material = row['material']
|
||||
|
||||
c.execute('DELETE FROM customer_orders WHERE id=?', (order_id,))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
log('delete_customer_order', f'订单ID: {order_id}, 客户: {customer_name}, 订单号: {order_no}')
|
||||
|
||||
return jsonify({'ok': True, 'message': '订单删除成功'})
|
||||
|
||||
|
||||
@app.errorhandler(404)
|
||||
def not_found(e):
|
||||
# 如果请求的是 HTML 页面(通过 Accept header 判断),返回 index.html
|
||||
|
||||
Loading…
Reference in New Issue
Block a user