优化细节

This commit is contained in:
zzh 2025-11-24 16:21:13 +08:00
parent 8a1eda390a
commit 3751854815
6 changed files with 428 additions and 30 deletions

View File

@ -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>

View File

@ -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) {

View File

@ -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');

View File

@ -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';

View File

@ -85,6 +85,7 @@ const Router = (() => {
'work-order': '生产工单下发中心',
'plan-mgmt': '计划管理',
'material-purchase': '物料清单-采购',
'customer-order': '客户订单',
export: '导出',
settings: '设置'
};

View File

@ -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