dy-dashboard/routes/warehouse.py
2026-10-28 11:14:17 +08:00

246 lines
9.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""仓库路由"""
from flask import Blueprint, request, jsonify, session, redirect, url_for, render_template
from services.db import query
from routes.auth import login_required, role_required
warehouse_bp = Blueprint('warehouse', __name__)
@warehouse_bp.route('/warehouse')
@login_required
def warehouse_page():
if not role_required('warehouse'):
return redirect(url_for('auth.index'))
return render_template('base.html', page='warehouse')
# ─── API: 仓库管理 ───
@warehouse_bp.route('/api/warehouse/list')
@login_required
def warehouse_list():
page = int(request.args.get('page', 1))
size = int(request.args.get('size', 50))
offset = (page - 1) * size
search = request.args.get('search', '')
category_filter = request.args.get('category', '')
where = []
params = []
if search:
where.append("(warehouse_name LIKE %s OR warehouse_code LIKE %s)")
params.extend([f"%{search}%", f"%{search}%"])
if category_filter:
where.append("category=%s")
params.append(category_filter)
where_clause = " AND ".join(where) if where else "1=1"
total = query(f"SELECT COUNT(*) AS cnt FROM warehouses WHERE {where_clause}", params)[0]['cnt']
rows = query(f"SELECT * FROM warehouses WHERE {where_clause} ORDER BY warehouse_code ASC LIMIT {size} OFFSET {offset}", params)
return jsonify({
"code": 0,
"data": {
"total": total,
"page": page,
"size": size,
"rows": rows
}
})
@warehouse_bp.route('/api/warehouse/categories')
@login_required
def warehouse_categories():
cats = query("SELECT DISTINCT category FROM warehouses ORDER BY category")
return jsonify({"code": 0, "data": [c['category'] for c in cats]})
@warehouse_bp.route('/api/warehouse/all')
@login_required
def warehouse_all():
"""获取所有仓库(供其他页面下拉选择使用)"""
rows = query("SELECT warehouse_code, warehouse_name, category FROM warehouses ORDER BY warehouse_code ASC")
return jsonify({"code": 0, "data": rows})
@warehouse_bp.route('/api/warehouse/save', methods=['POST'])
@login_required
def warehouse_save():
"""新增/编辑仓库;仓库属性发生变化时刷新依赖属性的共享缓存。"""
data = request.json
code = data.get('warehouse_code', '').strip()
name = data.get('warehouse_name', '').strip()
category = data.get('category', '').strip()
remark = data.get('remark', '').strip()
warehouse_property = data.get('warehouse_property', '').strip()
disabled = data.get('disabled', '否').strip()
if not code or not name:
return jsonify({"code": 1, "msg": "仓库编号和名称不能为空"})
exists = query("SELECT id, warehouse_property FROM warehouses WHERE warehouse_code=%s", (code,))
property_changed = not exists or (exists[0].get('warehouse_property') or '').strip() != warehouse_property
if exists:
query("UPDATE warehouses SET warehouse_name=%s, category=%s, remark=%s, warehouse_property=%s, disabled=%s WHERE warehouse_code=%s",
(name, category, remark, warehouse_property, disabled, code))
else:
query("INSERT INTO warehouses (warehouse_code, warehouse_name, category, remark, warehouse_property, disabled) VALUES (%s,%s,%s,%s,%s,%s)",
(code, name, category, remark, warehouse_property, disabled))
if property_changed:
from services.cache import refresh_all_caches
refresh_all_caches()
return jsonify({"code": 0, "msg": "保存成功", "cache_refreshed": property_changed})
@warehouse_bp.route('/api/warehouse/delete', methods=['POST'])
@login_required
def warehouse_delete():
"""删除仓库"""
if session.get("role") not in ("admin", "super_admin"):
return jsonify({"code": 403, "msg": "仅管理员可操作"})
data = request.json
code = data.get('warehouse_code', '').strip()
if not code:
return jsonify({"code": 400, "msg": "参数错误"})
try:
query("DELETE FROM warehouses WHERE warehouse_code=%s", (code,))
return jsonify({"code": 0, "msg": "删除成功"})
except Exception as e:
return jsonify({"code": 500, "msg": str(e)})
@warehouse_bp.route('/api/warehouse/update_field', methods=['POST'])
@login_required
def warehouse_update_field():
"""更新仓库单字段(用于内联编辑)。仓库属性发生变化时刷新共享缓存。"""
data = request.json
code = data.get('warehouse_code', '').strip()
field = data.get('field', '')
value = data.get('value', '')
if not code or not field:
return jsonify({"code": 400, "msg": "参数错误"})
allowed = ['warehouse_property', 'disabled']
if field not in allowed:
return jsonify({"code": 400, "msg": "不允许的字段"})
property_changed = False
if field == 'warehouse_property':
old_rows = query("SELECT warehouse_property FROM warehouses WHERE warehouse_code=%s", (code,))
old_value = (old_rows[0].get('warehouse_property') or '').strip() if old_rows else ''
property_changed = old_value != str(value or '').strip()
try:
query(f"UPDATE warehouses SET {field}=%s WHERE warehouse_code=%s", (value, code))
if property_changed:
from services.cache import refresh_all_caches
refresh_all_caches()
return jsonify({"code": 0, "msg": "更新成功", "cache_refreshed": property_changed})
except Exception as e:
return jsonify({"code": 500, "msg": str(e)})
@warehouse_bp.route('/api/warehouse/export_template')
@login_required
def warehouse_export_template():
"""下载仓库导入模板 CSV"""
import csv, io
output = io.StringIO()
writer = csv.writer(output)
writer.writerow(['仓库编号', '仓库名称', '仓库分类', '备注'])
writer.writerow(['01', '示例仓库', '内部', '备注信息'])
csv_data = output.getvalue()
output.close()
from flask import Response
return Response(
csv_data,
mimetype='text/csv',
headers={'Content-Disposition': 'attachment; filename=warehouse_import_template.csv'}
)
@warehouse_bp.route('/api/warehouse/import', methods=['POST'])
@login_required
def warehouse_import():
"""导入仓库数据(直接上传CSV文件)"""
import io, csv, re
if 'file' not in request.files:
return jsonify({"code": 400, "msg": "未上传文件"})
f = request.files['file']
if f.filename == '':
return jsonify({"code": 400, "msg": "未选择文件"})
raw = f.read()
# 编码检测:依次尝试 utf-8-sig → gbk → utf-8
encodings = ['utf-8-sig', 'gbk', 'utf-8']
text = None
for enc in encodings:
try:
text = raw.decode(enc)
break
except (UnicodeDecodeError, LookupError):
continue
if text is None:
return jsonify({"code": 400, "msg": "无法识别CSV文件编码,请确认文件格式正确"})
# 统一换行符
text = text.replace('\r\n', '\n').replace('\r', '\n')
lines = [l.strip() for l in text.split('\n') if l.strip()]
if len(lines) < 2:
return jsonify({"code": 400, "msg": "CSV文件至少需要表头+1行数据"})
# 用 csv.reader 解析(兼容引号内逗号)
reader = csv.reader(io.StringIO('\n'.join(lines)))
rows = list(reader)
if len(rows) < 2:
return jsonify({"code": 400, "msg": "未解析到有效数据"})
# 提取列名
headers = [h.strip() for h in rows[0]]
expected = ['仓库编号', '仓库名称', '仓库分类', '备注']
# 检查是否包含至少两个预期列
matched_cols = sum(1 for h in expected if h in headers)
if matched_cols < 2:
return jsonify({
"code": 400,
"msg": f"CSV列名不匹配\n预期列名:{','.join(expected)}\n实际列名:{','.join(headers)}"
})
# 建立索引映射
idx_code = headers.index('仓库编号') if '仓库编号' in headers else -1
idx_name = headers.index('仓库名称') if '仓库名称' in headers else -1
idx_cat = headers.index('仓库分类') if '仓库分类' in headers else -1
idx_remark = headers.index('备注') if '备注' in headers else -1
success = 0
errors = []
for i, row in enumerate(rows[1:], 2):
if len(row) <= max(idx_code, idx_name):
errors.append(f"第{i}行:列数不足")
continue
code = row[idx_code].strip() if idx_code >= 0 else ''
name = row[idx_name].strip() if idx_name >= 0 else ''
category = row[idx_cat].strip() if idx_cat >= 0 else '内部'
remark = row[idx_remark].strip() if idx_remark >= 0 else ''
if not code or not name:
errors.append(f"第{i}行:仓库编号或名称为空")
continue
try:
exists = query("SELECT id FROM warehouses WHERE warehouse_code=%s", (code,))
if exists:
query("UPDATE warehouses SET warehouse_name=%s, category=%s, remark=%s WHERE warehouse_code=%s",
(name, category, remark, code))
else:
query("INSERT INTO warehouses (warehouse_code, warehouse_name, category, remark) VALUES (%s,%s,%s,%s)",
(code, name, category, remark))
success += 1
except Exception as e:
errors.append(f"第{i}行({code}):{str(e)[:50]}")
msg = f"导入完成,共处理{success}条"
if errors:
msg += f",{len(errors)}条异常(前3条:{';'.join(errors[:3])})"
return jsonify({"code": 0 if success > 0 else 400, "msg": msg})