diff --git a/inventory-backend/app/api/v1/inbound/product.py b/inventory-backend/app/api/v1/inbound/product.py index 8f9810a..d2c69d7 100644 --- a/inventory-backend/app/api/v1/inbound/product.py +++ b/inventory-backend/app/api/v1/inbound/product.py @@ -19,7 +19,7 @@ def search_base(): # ------------------------------------------------------------------ -# 1. 获取列表 (修改:支持状态筛选) +# 1. 获取列表 (修改:接收 status 参数) # ------------------------------------------------------------------ @inbound_product_bp.route('/list', methods=['GET']) def get_list(): @@ -27,8 +27,7 @@ def get_list(): page = request.args.get('page', 1, type=int) limit = request.args.get('pageSize', 15, type=int) keyword = request.args.get('keyword', '') - - # 获取状态列表参数 + # 接收状态参数 statuses_str = request.args.get('statuses', '') statuses = statuses_str.split(',') if statuses_str else [] @@ -78,5 +77,17 @@ def delete(id): try: ProductInboundService.delete_inbound(id) return jsonify({"code": 200, "msg": "删除成功"}) + except Exception as e: + return jsonify({"code": 500, "msg": str(e)}), 500 + + +# ------------------------------------------------------------------ +# 5. [新增] 获取出库历史 +# ------------------------------------------------------------------ +@inbound_product_bp.route('//history', methods=['GET']) +def get_history(id): + try: + data = ProductInboundService.get_outbound_history(id) + return jsonify({"code": 200, "msg": "success", "data": data}) except Exception as e: return jsonify({"code": 500, "msg": str(e)}), 500 \ No newline at end of file diff --git a/inventory-backend/app/api/v1/inbound/semi.py b/inventory-backend/app/api/v1/inbound/semi.py index ebd88a5..33f2bb9 100644 --- a/inventory-backend/app/api/v1/inbound/semi.py +++ b/inventory-backend/app/api/v1/inbound/semi.py @@ -8,7 +8,7 @@ inbound_semi_bp = Blueprint('inbound_semi', __name__) # ------------------------------------------------------------------ -# 0. 基础物料搜索 +# 0. 基础物料搜索 (复用逻辑) # ------------------------------------------------------------------ @inbound_semi_bp.route('/search-base', methods=['GET']) def search_base(): @@ -18,6 +18,7 @@ def search_base(): """ try: keyword = request.args.get('keyword', '') + # 这里复用 Service 中的搜索逻辑 data = SemiInboundService.search_base_material(keyword) return jsonify({ "code": 200, @@ -37,9 +38,10 @@ def get_list(): try: page = request.args.get('page', 1, type=int) limit = request.args.get('pageSize', 15, type=int) + # 支持按关键字搜索:BOM号、工单号、SN、批号等 keyword = request.args.get('keyword', '') - # 获取状态列表参数 + # [修改] 获取状态列表参数 statuses_str = request.args.get('statuses', '') statuses = statuses_str.split(',') if statuses_str else [] @@ -51,7 +53,7 @@ def get_list(): # ------------------------------------------------------------------ -# 2. 新增半成品入库 +# 2. 新增半成品入库 (修改:返回创建的对象数据) # ------------------------------------------------------------------ @inbound_semi_bp.route('/submit', methods=['POST']) def submit(): @@ -60,8 +62,10 @@ def submit(): if not data: return jsonify({"code": 400, "msg": "No data"}), 400 + # 修改:调用 Service 处理入库,获取新创建的对象 new_stock = SemiInboundService.handle_inbound(data) + # 修改:返回成功信息以及新创建的数据(包含生成的ID和SKU),供前端打印使用 return jsonify({ "code": 200, "msg": "入库成功", @@ -94,6 +98,23 @@ def delete_semi(id): try: SemiInboundService.delete_inbound(id) return jsonify({"code": 200, "msg": "删除成功"}) + except Exception as e: + traceback.print_exc() + return jsonify({"code": 500, "msg": str(e)}), 500 + + +# ------------------------------------------------------------------ +# 5. [新增] 获取关联出库历史 +# ------------------------------------------------------------------ +@inbound_semi_bp.route('//history', methods=['GET']) +def get_history(id): + try: + data = SemiInboundService.get_outbound_history(id) + return jsonify({ + "code": 200, + "msg": "success", + "data": data + }) except Exception as e: traceback.print_exc() return jsonify({"code": 500, "msg": str(e)}), 500 \ No newline at end of file diff --git a/inventory-backend/app/services/inbound/product_service.py b/inventory-backend/app/services/inbound/product_service.py index 0221996..507e507 100644 --- a/inventory-backend/app/services/inbound/product_service.py +++ b/inventory-backend/app/services/inbound/product_service.py @@ -1,8 +1,9 @@ # app/services/inbound/product_service.py from app.extensions import db from app.models.base import MaterialBase +from app.models.outbound import TransOutbound from datetime import datetime -from sqlalchemy import or_, func, text, and_ # Added and_ +from sqlalchemy import or_, func, text, and_ import traceback import json @@ -64,7 +65,7 @@ class ProductInboundService: generated_sku = str(next_global_id).zfill(10) final_barcode = data.get('barcode') or generated_sku - # [核心修改] 处理三个图片/链接列表 + # 处理三个图片/链接列表 photo_list = data.get('product_photo', []) quality_list = data.get('quality_report_link', []) inspection_list = data.get('inspection_report_link', []) @@ -135,7 +136,7 @@ class ProductInboundService: for f in fields: if f in data: setattr(stock, f, data[f]) - # [核心修改] 更新 JSON 字段 + # 更新 JSON 字段 if 'product_photo' in data: imgs = data['product_photo'] if isinstance(imgs, list): stock.product_photo = json.dumps(imgs) @@ -155,10 +156,9 @@ class ProductInboundService: if 'in_quantity' in data: new_qty = float(data['in_quantity']) diff = new_qty - float(stock.in_quantity) - if diff != 0: - stock.in_quantity = new_qty - stock.stock_quantity = float(stock.stock_quantity) + diff - stock.available_quantity = float(stock.available_quantity) + diff + stock.in_quantity = new_qty + stock.stock_quantity = float(stock.stock_quantity) + diff + stock.available_quantity = float(stock.available_quantity) + diff if 'production_start_time' in data or 'production_end_time' in data: old_range = stock.production_time_range or " ~ " @@ -188,6 +188,23 @@ class ProductInboundService: db.session.rollback() raise e + # ============================================================ + # 获取出库流转历史 (与 Buy 逻辑一致,关联 TransOutbound 表) + # ============================================================ + @staticmethod + def get_outbound_history(stock_id): + """获取出库历史""" + try: + records = TransOutbound.query.filter_by( + source_table='stock_product', stock_id=stock_id + ).order_by(TransOutbound.outbound_time.desc()).all() + return [r.to_dict() for r in records] + except: + return [] + + # ============================================================ + # 获取列表 (包含状态筛选与零库存隐藏逻辑) + # ============================================================ @staticmethod def get_list(page, limit, keyword=None, statuses=None): from app.models.inbound.product import StockProduct @@ -222,11 +239,8 @@ class ProductInboundService: pagination = query.order_by(StockProduct.id.desc()).paginate(page=page, per_page=limit, error_out=False) - # 3. 数据组装 (移除聚合逻辑,改为单行数据) current_items = pagination.items - # 移除聚合查询代码... - def parse_img(json_str): if not json_str: return [] try: diff --git a/inventory-backend/app/services/inbound/semi_service.py b/inventory-backend/app/services/inbound/semi_service.py index 43e1fd4..fd1a93c 100644 --- a/inventory-backend/app/services/inbound/semi_service.py +++ b/inventory-backend/app/services/inbound/semi_service.py @@ -1,8 +1,9 @@ # app/services/inbound/semi_service.py from app.extensions import db from app.models.base import MaterialBase +from app.models.outbound import TransOutbound from datetime import datetime -from sqlalchemy import or_, func, text, and_ # Added and_ +from sqlalchemy import or_, func, text, and_ import traceback import json @@ -130,7 +131,7 @@ class SemiInboundService: batch_number=data.get('batch_number'), barcode=final_barcode, - status=data.get('status', '在库'), # 默认在库 + status='在库', quality_status=data.get('quality_status', '合格'), in_quantity=in_qty, stock_quantity=in_qty, @@ -150,6 +151,7 @@ class SemiInboundService: manual_cost=manual_cost, total_price=total_value, + # [核心修改] 将列表转为 JSON 字符串存储 arrival_photo=json.dumps(arrival_list), quality_report_link=json.dumps(quality_report_list), @@ -198,6 +200,7 @@ class SemiInboundService: if frontend_key in data: setattr(stock, db_attr, data[frontend_key]) + # [核心修改] 图片字段更新 (List -> JSON String) if 'arrival_photo' in data: imgs = data['arrival_photo'] if isinstance(imgs, list): @@ -284,13 +287,29 @@ class SemiInboundService: db.session.rollback() raise e + # ------------------------------------------------------------------ + # [核心修改] 获取关联出库历史 (跟 Buy 保持一致) + # ------------------------------------------------------------------ + @staticmethod + def get_outbound_history(stock_id): + """获取出库历史""" + try: + records = TransOutbound.query.filter_by( + source_table='stock_semi', stock_id=stock_id + ).order_by(TransOutbound.outbound_time.desc()).all() + return [r.to_dict() for r in records] + except: + return [] + + # ------------------------------------------------------------------ + # [核心修改] 列表查询:支持状态筛选、默认隐藏0库存、去除聚合 + # ------------------------------------------------------------------ @staticmethod def get_list(page, limit, keyword=None, statuses=None): from app.models.inbound.semi import StockSemi try: query = db.session.query(StockSemi).outerjoin(MaterialBase, StockSemi.base_id == MaterialBase.id) - # 1. 关键词搜索 if keyword: kw = f'%{keyword}%' query = query.filter( @@ -305,7 +324,7 @@ class SemiInboundService: ) ) - # 2. 状态筛选与零库存隐藏逻辑 + # [新增] 状态筛选与零库存隐藏逻辑 if not statuses: statuses = ['在库', '借库'] @@ -321,9 +340,9 @@ class SemiInboundService: ) pagination = query.order_by(StockSemi.id.desc()).paginate(page=page, per_page=limit, error_out=False) + current_items = pagination.items - # 3. 数据组装 (移除 aggregation map,使用单行数据) def parse_img(json_str): if not json_str: return [] try: @@ -339,7 +358,7 @@ class SemiInboundService: d['qty_stock'] = float(item.stock_quantity or 0) d['qty_available'] = float(item.available_quantity or 0) - # 兼容前端字段 + # 兼容前端字段名 d['sum_stock'] = d['qty_stock'] d['sum_available'] = d['qty_available'] @@ -347,7 +366,7 @@ class SemiInboundService: d['arrival_photo'] = parse_img(item.arrival_photo) d['quality_report_link'] = parse_img(item.quality_report_link) - # 打印相关 + # 打印ID d['global_print_id'] = item.global_print_id items.append(d) diff --git a/inventory-web/src/views/stock/inbound/product.vue b/inventory-web/src/views/stock/inbound/product.vue index 4f10be1..3e03ea2 100644 --- a/inventory-web/src/views/stock/inbound/product.vue +++ b/inventory-web/src/views/stock/inbound/product.vue @@ -403,6 +403,7 @@ const dialogStatus = ref<'create' | 'update'>('create') const tableData = ref([]) const total = ref(0) const formRef = ref() +// [修改] 增加 statuses 参数 const queryParams = reactive({ page: 1, pageSize: 15, @@ -511,6 +512,7 @@ const handleManagerSelect = (item: any) => saveToHistory(HISTORY_KEYS.PRODUCTION const fetchData = async () => { loading.value = true; try { + // [修改] 传递 statuses const params = { ...queryParams, statuses: queryParams.statuses.join(',') } const res: any = await getProductList(params); tableData.value = res.data.items || [];