From 00781422eb52e8e64c2fbf5d66c3c14bc766bdc7 Mon Sep 17 00:00:00 2001 From: DXC Date: Wed, 18 Mar 2026 14:22:01 +0800 Subject: [PATCH] fix: correct excel export formatting (timezone, spec, user, location) and add auto-polling for collaborative stocktake --- inventory-backend/app/api/v1/inbound/stock.py | 83 +++++++++++++++---- .../src/views/stock/stocktake/index.vue | 59 ++++++++++++- 2 files changed, 124 insertions(+), 18 deletions(-) diff --git a/inventory-backend/app/api/v1/inbound/stock.py b/inventory-backend/app/api/v1/inbound/stock.py index c230d0a..54f871d 100644 --- a/inventory-backend/app/api/v1/inbound/stock.py +++ b/inventory-backend/app/api/v1/inbound/stock.py @@ -1,7 +1,7 @@ from flask import Blueprint, jsonify, request, send_file from app.extensions import db # ★★★ 修复点:必须引入 datetime,否则下方更新时间时会报错 500 ★★★ -from datetime import datetime +from datetime import datetime, timedelta from app.utils.decorators import permission_required import uuid as uuid_module import io @@ -14,6 +14,12 @@ from app.models.inbound.stocktake import StocktakeDraft from app.models.transaction import TransBorrow from app.models.base import MaterialBase +# 尝试导入用户模型 +try: + from app.models.sys.user import User +except ImportError: + User = None + # 尝试导入半成品和成品 try: from app.models.inbound.semi import StockSemi @@ -553,10 +559,10 @@ def export_stocktake(): elif source_table == 'stock_product': stock = StockProduct.query.get(stock_id) if StockProduct else None else: - return {'name': '-', 'sku': '-', 'spec': '-', 'unit': '-'} + return {'name': '-', 'sku': '-', 'spec': '-', 'unit': '-', 'location': '-'} if not stock: - return {'name': '-', 'sku': '-', 'spec': '-', 'unit': '-'} + return {'name': '-', 'sku': '-', 'spec': '-', 'unit': '-', 'location': '-'} # 安全获取 sku stock_sku = getattr(stock, 'sku', None) or getattr(stock, 'SKU', None) or '-' @@ -571,12 +577,55 @@ def export_stocktake(): if hasattr(MaterialBase, 'code') and stock_sku != '-': material = MaterialBase.query.filter_by(code=stock_sku).first() + # 规格型号:优先从 material 取,再 fallback 到 stock + spec = ( + getattr(material, 'specification', None) or + getattr(material, 'spec', None) or + getattr(stock, 'spec_model', None) or + getattr(stock, 'standard', None) or + '-' + ) + + # 库位:获取真实物理库位 + location = getattr(stock, 'location', None) or getattr(stock, 'position', None) or '-' + return { 'name': material.name if material else stock_sku, 'sku': stock_sku, - 'spec': getattr(stock, 'spec_model', None) or getattr(stock, 'standard', None) or '-', - 'unit': getattr(stock, 'unit', None) or '个' + 'spec': spec, + 'unit': getattr(stock, 'unit', None) or '个', + 'location': location } + + def get_user_name(user_id): + """获取用户真实姓名或昵称""" + if not User or not user_id: + return str(user_id) if user_id else '-' + try: + user = None + # 尝试通过ID或用户名查找 + if str(user_id).isdigit(): + user = User.query.get(int(user_id)) + if not user: + user = User.query.filter_by(username=str(user_id)).first() + if not user: + user = User.query.filter_by(username=str(user_id).upper()).first() + if not user: + user = User.query.filter_by(username=str(user_id).lower()).first() + return getattr(user, 'real_name', None) or getattr(user, 'nickname', None) or str(user_id) + except: + return str(user_id) + + def to_beijing_time(dt): + """转换为北京时间(+8小时)""" + if not dt: + return '' + try: + if isinstance(dt, str): + return dt[:19] + return (dt + timedelta(hours=8)).strftime('%Y-%m-%d %H:%M:%S') + except: + return str(dt)[:19] def set_header_row(ws, headers): for col, header in enumerate(headers, 1): @@ -590,37 +639,37 @@ def export_stocktake(): ws1 = wb.create_sheet("盘点差异明细") diff_headers = ["物料名称", "SKU", "规格型号", "库位", "调整后账面数", "实盘数", "差异数", "盘点人", "盘点时间"] set_header_row(ws1, diff_headers) - + diff_drafts = StocktakeDraft.query.filter(StocktakeDraft.diff_qty != 0).all() for row_idx, draft in enumerate(diff_drafts, 2): mat_info = get_material_info(draft.source_table, draft.stock_id) ws1.cell(row=row_idx, column=1, value=mat_info['name']).border = thin_border ws1.cell(row=row_idx, column=2, value=mat_info['sku']).border = thin_border ws1.cell(row=row_idx, column=3, value=mat_info['spec']).border = thin_border - ws1.cell(row=row_idx, column=4, value=draft.source_table).border = thin_border + ws1.cell(row=row_idx, column=4, value=mat_info['location']).border = thin_border ws1.cell(row=row_idx, column=5, value=float(draft.stock_qty or 0)).border = thin_border ws1.cell(row=row_idx, column=6, value=float(draft.quantity or 0)).border = thin_border ws1.cell(row=row_idx, column=7, value=float(draft.diff_qty or 0)).border = thin_border - ws1.cell(row=row_idx, column=8, value=draft.user_id or '').border = thin_border - ws1.cell(row=row_idx, column=9, value=str(draft.scan_time)[:19] if draft.scan_time else '').border = thin_border - + ws1.cell(row=row_idx, column=8, value=get_user_name(draft.user_id)).border = thin_border + ws1.cell(row=row_idx, column=9, value=to_beijing_time(draft.scan_time)).border = thin_border + # ===== Sheet 2: 账实相符明细 ===== ws2 = wb.create_sheet("账实相符明细") normal_headers = ["物料名称", "SKU", "规格型号", "库位", "调整后账面数", "实盘数", "差异数", "盘点人", "盘点时间"] set_header_row(ws2, normal_headers) - + normal_drafts = StocktakeDraft.query.filter(StocktakeDraft.diff_qty == 0).all() for row_idx, draft in enumerate(normal_drafts, 2): mat_info = get_material_info(draft.source_table, draft.stock_id) ws2.cell(row=row_idx, column=1, value=mat_info['name']).border = thin_border ws2.cell(row=row_idx, column=2, value=mat_info['sku']).border = thin_border ws2.cell(row=row_idx, column=3, value=mat_info['spec']).border = thin_border - ws2.cell(row=row_idx, column=4, value=draft.source_table).border = thin_border + ws2.cell(row=row_idx, column=4, value=mat_info['location']).border = thin_border ws2.cell(row=row_idx, column=5, value=float(draft.stock_qty or 0)).border = thin_border ws2.cell(row=row_idx, column=6, value=float(draft.quantity or 0)).border = thin_border ws2.cell(row=row_idx, column=7, value=float(draft.diff_qty or 0)).border = thin_border - ws2.cell(row=row_idx, column=8, value=draft.user_id or '').border = thin_border - ws2.cell(row=row_idx, column=9, value=str(draft.scan_time)[:19] if draft.scan_time else '').border = thin_border + ws2.cell(row=row_idx, column=8, value=get_user_name(draft.user_id)).border = thin_border + ws2.cell(row=row_idx, column=9, value=to_beijing_time(draft.scan_time)).border = thin_border # ===== Sheet 3: 外借在用资产明细 ===== ws3 = wb.create_sheet("外借在用资产明细") @@ -634,7 +683,7 @@ def export_stocktake(): total_qty = float(borrow.quantity or 0) returned_qty = float(borrow.returned_quantity or 0) pending_qty = total_qty - returned_qty - + ws3.cell(row=row_idx, column=1, value=borrow.borrow_no or '').border = thin_border ws3.cell(row=row_idx, column=2, value=borrow.borrower_name or '').border = thin_border ws3.cell(row=row_idx, column=3, value=mat_info['name']).border = thin_border @@ -643,8 +692,8 @@ def export_stocktake(): ws3.cell(row=row_idx, column=6, value=total_qty).border = thin_border ws3.cell(row=row_idx, column=7, value=returned_qty).border = thin_border ws3.cell(row=row_idx, column=8, value=pending_qty).border = thin_border - ws3.cell(row=row_idx, column=9, value=str(borrow.borrow_time)[:19] if borrow.borrow_time else '').border = thin_border - ws3.cell(row=row_idx, column=10, value='无限期' if not borrow.expected_return_time else str(borrow.expected_return_time)[:10]).border = thin_border + ws3.cell(row=row_idx, column=9, value=to_beijing_time(borrow.borrow_time)).border = thin_border + ws3.cell(row=row_idx, column=10, value='无限期' if not borrow.expected_return_time else to_beijing_time(borrow.expected_return_time)).border = thin_border # 调整列宽 for ws in [ws1, ws2, ws3]: diff --git a/inventory-web/src/views/stock/stocktake/index.vue b/inventory-web/src/views/stock/stocktake/index.vue index 141fff8..37b20ed 100644 --- a/inventory-web/src/views/stock/stocktake/index.vue +++ b/inventory-web/src/views/stock/stocktake/index.vue @@ -349,7 +349,7 @@