修改半成品与成品出入库相关联逻辑

This commit is contained in:
dxc
2026-02-05 13:17:39 +08:00
parent aa40d4a6da
commit 10e53cab23
5 changed files with 90 additions and 23 deletions

View File

@ -19,7 +19,7 @@ def search_base():
# ------------------------------------------------------------------ # ------------------------------------------------------------------
# 1. 获取列表 (修改:支持状态筛选) # 1. 获取列表 (修改:接收 status 参数)
# ------------------------------------------------------------------ # ------------------------------------------------------------------
@inbound_product_bp.route('/list', methods=['GET']) @inbound_product_bp.route('/list', methods=['GET'])
def get_list(): def get_list():
@ -27,8 +27,7 @@ def get_list():
page = request.args.get('page', 1, type=int) page = request.args.get('page', 1, type=int)
limit = request.args.get('pageSize', 15, type=int) limit = request.args.get('pageSize', 15, type=int)
keyword = request.args.get('keyword', '') keyword = request.args.get('keyword', '')
# 接收状态参数
# 获取状态列表参数
statuses_str = request.args.get('statuses', '') statuses_str = request.args.get('statuses', '')
statuses = statuses_str.split(',') if statuses_str else [] statuses = statuses_str.split(',') if statuses_str else []
@ -80,3 +79,15 @@ def delete(id):
return jsonify({"code": 200, "msg": "删除成功"}) return jsonify({"code": 200, "msg": "删除成功"})
except Exception as e: except Exception as e:
return jsonify({"code": 500, "msg": str(e)}), 500 return jsonify({"code": 500, "msg": str(e)}), 500
# ------------------------------------------------------------------
# 5. [新增] 获取出库历史
# ------------------------------------------------------------------
@inbound_product_bp.route('/<int:id>/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

View File

@ -8,7 +8,7 @@ inbound_semi_bp = Blueprint('inbound_semi', __name__)
# ------------------------------------------------------------------ # ------------------------------------------------------------------
# 0. 基础物料搜索 # 0. 基础物料搜索 (复用逻辑)
# ------------------------------------------------------------------ # ------------------------------------------------------------------
@inbound_semi_bp.route('/search-base', methods=['GET']) @inbound_semi_bp.route('/search-base', methods=['GET'])
def search_base(): def search_base():
@ -18,6 +18,7 @@ def search_base():
""" """
try: try:
keyword = request.args.get('keyword', '') keyword = request.args.get('keyword', '')
# 这里复用 Service 中的搜索逻辑
data = SemiInboundService.search_base_material(keyword) data = SemiInboundService.search_base_material(keyword)
return jsonify({ return jsonify({
"code": 200, "code": 200,
@ -37,9 +38,10 @@ def get_list():
try: try:
page = request.args.get('page', 1, type=int) page = request.args.get('page', 1, type=int)
limit = request.args.get('pageSize', 15, type=int) limit = request.args.get('pageSize', 15, type=int)
# 支持按关键字搜索BOM号、工单号、SN、批号等
keyword = request.args.get('keyword', '') keyword = request.args.get('keyword', '')
# 获取状态列表参数 # [修改] 获取状态列表参数
statuses_str = request.args.get('statuses', '') statuses_str = request.args.get('statuses', '')
statuses = statuses_str.split(',') if statuses_str else [] statuses = statuses_str.split(',') if statuses_str else []
@ -51,7 +53,7 @@ def get_list():
# ------------------------------------------------------------------ # ------------------------------------------------------------------
# 2. 新增半成品入库 # 2. 新增半成品入库 (修改:返回创建的对象数据)
# ------------------------------------------------------------------ # ------------------------------------------------------------------
@inbound_semi_bp.route('/submit', methods=['POST']) @inbound_semi_bp.route('/submit', methods=['POST'])
def submit(): def submit():
@ -60,8 +62,10 @@ def submit():
if not data: if not data:
return jsonify({"code": 400, "msg": "No data"}), 400 return jsonify({"code": 400, "msg": "No data"}), 400
# 修改:调用 Service 处理入库,获取新创建的对象
new_stock = SemiInboundService.handle_inbound(data) new_stock = SemiInboundService.handle_inbound(data)
# 修改返回成功信息以及新创建的数据包含生成的ID和SKU供前端打印使用
return jsonify({ return jsonify({
"code": 200, "code": 200,
"msg": "入库成功", "msg": "入库成功",
@ -97,3 +101,20 @@ def delete_semi(id):
except Exception as e: except Exception as e:
traceback.print_exc() traceback.print_exc()
return jsonify({"code": 500, "msg": str(e)}), 500 return jsonify({"code": 500, "msg": str(e)}), 500
# ------------------------------------------------------------------
# 5. [新增] 获取关联出库历史
# ------------------------------------------------------------------
@inbound_semi_bp.route('/<int:id>/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

View File

@ -1,8 +1,9 @@
# app/services/inbound/product_service.py # app/services/inbound/product_service.py
from app.extensions import db from app.extensions import db
from app.models.base import MaterialBase from app.models.base import MaterialBase
from app.models.outbound import TransOutbound
from datetime import datetime from datetime import datetime
from sqlalchemy import or_, func, text, and_ # Added and_ from sqlalchemy import or_, func, text, and_
import traceback import traceback
import json import json
@ -64,7 +65,7 @@ class ProductInboundService:
generated_sku = str(next_global_id).zfill(10) generated_sku = str(next_global_id).zfill(10)
final_barcode = data.get('barcode') or generated_sku final_barcode = data.get('barcode') or generated_sku
# [核心修改] 处理三个图片/链接列表 # 处理三个图片/链接列表
photo_list = data.get('product_photo', []) photo_list = data.get('product_photo', [])
quality_list = data.get('quality_report_link', []) quality_list = data.get('quality_report_link', [])
inspection_list = data.get('inspection_report_link', []) inspection_list = data.get('inspection_report_link', [])
@ -135,7 +136,7 @@ class ProductInboundService:
for f in fields: for f in fields:
if f in data: setattr(stock, f, data[f]) if f in data: setattr(stock, f, data[f])
# [核心修改] 更新 JSON 字段 # 更新 JSON 字段
if 'product_photo' in data: if 'product_photo' in data:
imgs = data['product_photo'] imgs = data['product_photo']
if isinstance(imgs, list): stock.product_photo = json.dumps(imgs) if isinstance(imgs, list): stock.product_photo = json.dumps(imgs)
@ -155,10 +156,9 @@ class ProductInboundService:
if 'in_quantity' in data: if 'in_quantity' in data:
new_qty = float(data['in_quantity']) new_qty = float(data['in_quantity'])
diff = new_qty - float(stock.in_quantity) diff = new_qty - float(stock.in_quantity)
if diff != 0: stock.in_quantity = new_qty
stock.in_quantity = new_qty stock.stock_quantity = float(stock.stock_quantity) + diff
stock.stock_quantity = float(stock.stock_quantity) + diff stock.available_quantity = float(stock.available_quantity) + diff
stock.available_quantity = float(stock.available_quantity) + diff
if 'production_start_time' in data or 'production_end_time' in data: if 'production_start_time' in data or 'production_end_time' in data:
old_range = stock.production_time_range or " ~ " old_range = stock.production_time_range or " ~ "
@ -188,6 +188,23 @@ class ProductInboundService:
db.session.rollback() db.session.rollback()
raise e 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 @staticmethod
def get_list(page, limit, keyword=None, statuses=None): def get_list(page, limit, keyword=None, statuses=None):
from app.models.inbound.product import StockProduct 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) pagination = query.order_by(StockProduct.id.desc()).paginate(page=page, per_page=limit, error_out=False)
# 3. 数据组装 (移除聚合逻辑,改为单行数据)
current_items = pagination.items current_items = pagination.items
# 移除聚合查询代码...
def parse_img(json_str): def parse_img(json_str):
if not json_str: return [] if not json_str: return []
try: try:

View File

@ -1,8 +1,9 @@
# app/services/inbound/semi_service.py # app/services/inbound/semi_service.py
from app.extensions import db from app.extensions import db
from app.models.base import MaterialBase from app.models.base import MaterialBase
from app.models.outbound import TransOutbound
from datetime import datetime from datetime import datetime
from sqlalchemy import or_, func, text, and_ # Added and_ from sqlalchemy import or_, func, text, and_
import traceback import traceback
import json import json
@ -130,7 +131,7 @@ class SemiInboundService:
batch_number=data.get('batch_number'), batch_number=data.get('batch_number'),
barcode=final_barcode, barcode=final_barcode,
status=data.get('status', '在库'), # 默认在库 status='在库',
quality_status=data.get('quality_status', '合格'), quality_status=data.get('quality_status', '合格'),
in_quantity=in_qty, in_quantity=in_qty,
stock_quantity=in_qty, stock_quantity=in_qty,
@ -150,6 +151,7 @@ class SemiInboundService:
manual_cost=manual_cost, manual_cost=manual_cost,
total_price=total_value, total_price=total_value,
# [核心修改] 将列表转为 JSON 字符串存储
arrival_photo=json.dumps(arrival_list), arrival_photo=json.dumps(arrival_list),
quality_report_link=json.dumps(quality_report_list), quality_report_link=json.dumps(quality_report_list),
@ -198,6 +200,7 @@ class SemiInboundService:
if frontend_key in data: if frontend_key in data:
setattr(stock, db_attr, data[frontend_key]) setattr(stock, db_attr, data[frontend_key])
# [核心修改] 图片字段更新 (List -> JSON String)
if 'arrival_photo' in data: if 'arrival_photo' in data:
imgs = data['arrival_photo'] imgs = data['arrival_photo']
if isinstance(imgs, list): if isinstance(imgs, list):
@ -284,13 +287,29 @@ class SemiInboundService:
db.session.rollback() db.session.rollback()
raise e 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 @staticmethod
def get_list(page, limit, keyword=None, statuses=None): def get_list(page, limit, keyword=None, statuses=None):
from app.models.inbound.semi import StockSemi from app.models.inbound.semi import StockSemi
try: try:
query = db.session.query(StockSemi).outerjoin(MaterialBase, StockSemi.base_id == MaterialBase.id) query = db.session.query(StockSemi).outerjoin(MaterialBase, StockSemi.base_id == MaterialBase.id)
# 1. 关键词搜索
if keyword: if keyword:
kw = f'%{keyword}%' kw = f'%{keyword}%'
query = query.filter( query = query.filter(
@ -305,7 +324,7 @@ class SemiInboundService:
) )
) )
# 2. 状态筛选与零库存隐藏逻辑 # [新增] 状态筛选与零库存隐藏逻辑
if not statuses: if not statuses:
statuses = ['在库', '借库'] statuses = ['在库', '借库']
@ -321,9 +340,9 @@ class SemiInboundService:
) )
pagination = query.order_by(StockSemi.id.desc()).paginate(page=page, per_page=limit, error_out=False) pagination = query.order_by(StockSemi.id.desc()).paginate(page=page, per_page=limit, error_out=False)
current_items = pagination.items current_items = pagination.items
# 3. 数据组装 (移除 aggregation map使用单行数据)
def parse_img(json_str): def parse_img(json_str):
if not json_str: return [] if not json_str: return []
try: try:
@ -339,7 +358,7 @@ class SemiInboundService:
d['qty_stock'] = float(item.stock_quantity or 0) d['qty_stock'] = float(item.stock_quantity or 0)
d['qty_available'] = float(item.available_quantity or 0) d['qty_available'] = float(item.available_quantity or 0)
# 兼容前端字段 # 兼容前端字段
d['sum_stock'] = d['qty_stock'] d['sum_stock'] = d['qty_stock']
d['sum_available'] = d['qty_available'] d['sum_available'] = d['qty_available']
@ -347,7 +366,7 @@ class SemiInboundService:
d['arrival_photo'] = parse_img(item.arrival_photo) d['arrival_photo'] = parse_img(item.arrival_photo)
d['quality_report_link'] = parse_img(item.quality_report_link) d['quality_report_link'] = parse_img(item.quality_report_link)
# 打印相关 # 打印ID
d['global_print_id'] = item.global_print_id d['global_print_id'] = item.global_print_id
items.append(d) items.append(d)

View File

@ -403,6 +403,7 @@ const dialogStatus = ref<'create' | 'update'>('create')
const tableData = ref([]) const tableData = ref([])
const total = ref(0) const total = ref(0)
const formRef = ref() const formRef = ref()
// [修改] 增加 statuses 参数
const queryParams = reactive({ const queryParams = reactive({
page: 1, page: 1,
pageSize: 15, pageSize: 15,
@ -511,6 +512,7 @@ const handleManagerSelect = (item: any) => saveToHistory(HISTORY_KEYS.PRODUCTION
const fetchData = async () => { const fetchData = async () => {
loading.value = true; loading.value = true;
try { try {
// [修改] 传递 statuses
const params = { ...queryParams, statuses: queryParams.statuses.join(',') } const params = { ...queryParams, statuses: queryParams.statuses.join(',') }
const res: any = await getProductList(params); const res: any = await getProductList(params);
tableData.value = res.data.items || []; tableData.value = res.data.items || [];