"""仓库路由""" 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})