Compare commits
2 Commits
4eb6bd792b
...
bcfc668bfb
| Author | SHA1 | Date | |
|---|---|---|---|
| bcfc668bfb | |||
| 9380508a89 |
@ -218,7 +218,13 @@ def submit():
|
|||||||
if not location:
|
if not location:
|
||||||
return jsonify({"code": 400, "msg": "入库失败:库位为必填项,不能为空!"}), 400
|
return jsonify({"code": 400, "msg": "入库失败:库位为必填项,不能为空!"}), 400
|
||||||
|
|
||||||
new_stock = BuyInboundService.handle_inbound(data)
|
result = BuyInboundService.handle_inbound(data)
|
||||||
|
|
||||||
|
# 检查是否返回了业务校验错误
|
||||||
|
if isinstance(result, dict) and result.get('error'):
|
||||||
|
return jsonify({"code": 400, "msg": result['error']}), 400
|
||||||
|
|
||||||
|
new_stock = result
|
||||||
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
"code": 200,
|
"code": 200,
|
||||||
@ -390,21 +396,3 @@ def get_location_suggestions():
|
|||||||
return jsonify({"code": 400, "msg": "base_id required"}), 400
|
return jsonify({"code": 400, "msg": "base_id required"}), 400
|
||||||
data = BuyInboundService.get_history_locations(base_id)
|
data = BuyInboundService.get_history_locations(base_id)
|
||||||
return jsonify({"code": 200, "msg": "success", "data": data})
|
return jsonify({"code": 200, "msg": "success", "data": data})
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
# 11. 获取最近一次入库的库位(跨表查询)
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
@inbound_buy_bp.route('/last-location', methods=['GET'])
|
|
||||||
@permission_required('inbound_buy')
|
|
||||||
def get_last_location():
|
|
||||||
"""
|
|
||||||
获取指定物料最近一次入库的库位
|
|
||||||
查询顺序:采购入库 -> 成品入库 -> 半成品入库,返回最新入库的库位
|
|
||||||
"""
|
|
||||||
base_id = request.args.get('base_id', type=int)
|
|
||||||
if not base_id:
|
|
||||||
return jsonify({"code": 400, "msg": "base_id required"}), 400
|
|
||||||
|
|
||||||
location = BuyInboundService.get_last_location_by_base_id(base_id)
|
|
||||||
return jsonify({"code": 200, "msg": "success", "data": {"location": location}})
|
|
||||||
|
|||||||
@ -145,7 +145,15 @@ def submit():
|
|||||||
for field in list(data.keys()):
|
for field in list(data.keys()):
|
||||||
perm_code = field_to_perm.get(field)
|
perm_code = field_to_perm.get(field)
|
||||||
if perm_code and perm_code not in user_permissions: data.pop(field, None)
|
if perm_code and perm_code not in user_permissions: data.pop(field, None)
|
||||||
new_stock = ProductInboundService.handle_inbound(data)
|
|
||||||
|
result = ProductInboundService.handle_inbound(data)
|
||||||
|
|
||||||
|
# 检查是否返回了业务校验错误
|
||||||
|
if isinstance(result, dict) and result.get('error'):
|
||||||
|
return jsonify({"code": 400, "msg": result['error']}), 400
|
||||||
|
|
||||||
|
new_stock = result
|
||||||
|
|
||||||
return jsonify({"code": 200, "msg": "入库成功", "data": new_stock.to_dict()})
|
return jsonify({"code": 200, "msg": "入库成功", "data": new_stock.to_dict()})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
@ -245,21 +253,3 @@ def calculate_bom_cost():
|
|||||||
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
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
# 获取最近一次入库的库位(跨表查询)
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
@inbound_product_bp.route('/last-location', methods=['GET'])
|
|
||||||
@permission_required('inbound_product')
|
|
||||||
def get_last_location():
|
|
||||||
"""
|
|
||||||
获取指定物料最近一次入库的库位
|
|
||||||
查询顺序:成品入库 -> 采购入库 -> 半成品入库,返回最新入库的库位
|
|
||||||
"""
|
|
||||||
base_id = request.args.get('base_id', type=int)
|
|
||||||
if not base_id:
|
|
||||||
return jsonify({"code": 400, "msg": "base_id required"}), 400
|
|
||||||
|
|
||||||
location = ProductInboundService.get_last_location_by_base_id(base_id)
|
|
||||||
return jsonify({"code": 200, "msg": "success", "data": {"location": location}})
|
|
||||||
|
|||||||
@ -140,7 +140,15 @@ def submit():
|
|||||||
for field in list(data.keys()):
|
for field in list(data.keys()):
|
||||||
perm_code = field_to_perm.get(field)
|
perm_code = field_to_perm.get(field)
|
||||||
if perm_code and perm_code not in user_permissions: data.pop(field, None)
|
if perm_code and perm_code not in user_permissions: data.pop(field, None)
|
||||||
new_stock = SemiInboundService.handle_inbound(data)
|
|
||||||
|
result = SemiInboundService.handle_inbound(data)
|
||||||
|
|
||||||
|
# 检查是否返回了业务校验错误
|
||||||
|
if isinstance(result, dict) and result.get('error'):
|
||||||
|
return jsonify({"code": 400, "msg": result['error']}), 400
|
||||||
|
|
||||||
|
new_stock = result
|
||||||
|
|
||||||
return jsonify({"code": 200, "msg": "入库成功", "data": new_stock.to_dict()})
|
return jsonify({"code": 200, "msg": "入库成功", "data": new_stock.to_dict()})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
@ -240,21 +248,3 @@ def calculate_bom_cost():
|
|||||||
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
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
# 获取最近一次入库的库位(跨表查询)
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
@inbound_semi_bp.route('/last-location', methods=['GET'])
|
|
||||||
@permission_required('inbound_semi')
|
|
||||||
def get_last_location():
|
|
||||||
"""
|
|
||||||
获取指定物料最近一次入库的库位
|
|
||||||
查询顺序:半成品入库 -> 采购入库 -> 成品入库,返回最新入库的库位
|
|
||||||
"""
|
|
||||||
base_id = request.args.get('base_id', type=int)
|
|
||||||
if not base_id:
|
|
||||||
return jsonify({"code": 400, "msg": "base_id required"}), 400
|
|
||||||
|
|
||||||
location = SemiInboundService.get_last_location_by_base_id(base_id)
|
|
||||||
return jsonify({"code": 200, "msg": "success", "data": {"location": location}})
|
|
||||||
|
|||||||
@ -18,15 +18,11 @@ def build_tree(nodes, parent_id=None):
|
|||||||
children = build_tree(nodes, node.id)
|
children = build_tree(nodes, node.id)
|
||||||
node_dict = node.to_dict()
|
node_dict = node.to_dict()
|
||||||
if children:
|
if children:
|
||||||
# 子节点按 name 升序排序
|
node_dict['children'] = children
|
||||||
children_sorted = sorted(children, key=lambda x: x.get('name', ''))
|
|
||||||
node_dict['children'] = children_sorted
|
|
||||||
else:
|
else:
|
||||||
node_dict['children'] = []
|
node_dict['children'] = []
|
||||||
tree.append(node_dict)
|
tree.append(node_dict)
|
||||||
# 当前层级按 name 升序排序
|
return tree
|
||||||
tree_sorted = sorted(tree, key=lambda x: x.get('name', ''))
|
|
||||||
return tree_sorted
|
|
||||||
|
|
||||||
|
|
||||||
@warehouse_bp.route('/tree', methods=['GET'])
|
@warehouse_bp.route('/tree', methods=['GET'])
|
||||||
@ -35,8 +31,8 @@ def get_tree():
|
|||||||
获取库位树形结构
|
获取库位树形结构
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# 查询所有库位,按 name 升序排序
|
# 查询所有库位
|
||||||
all_locations = SysWarehouseLocation.query.order_by(SysWarehouseLocation.name.asc()).all()
|
all_locations = SysWarehouseLocation.query.order_by(SysWarehouseLocation.level, SysWarehouseLocation.id).all()
|
||||||
|
|
||||||
# 构建树形结构
|
# 构建树形结构
|
||||||
tree_data = build_tree(all_locations, parent_id=None)
|
tree_data = build_tree(all_locations, parent_id=None)
|
||||||
|
|||||||
@ -17,6 +17,10 @@ class BuyInboundService:
|
|||||||
# ============================================================
|
# ============================================================
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _check_unique(base_id, serial_number, batch_number, exclude_id=None):
|
def _check_unique(base_id, serial_number, batch_number, exclude_id=None):
|
||||||
|
"""
|
||||||
|
校验序列号/批号是否已存在
|
||||||
|
返回: None 表示校验通过,str 类型的错误信息表示校验失败
|
||||||
|
"""
|
||||||
if serial_number:
|
if serial_number:
|
||||||
query = StockBuy.query.filter(StockBuy.serial_number == serial_number)
|
query = StockBuy.query.filter(StockBuy.serial_number == serial_number)
|
||||||
if exclude_id:
|
if exclude_id:
|
||||||
@ -24,7 +28,7 @@ class BuyInboundService:
|
|||||||
exists = query.first()
|
exists = query.first()
|
||||||
if exists:
|
if exists:
|
||||||
occupied_name = exists.base.name if exists.base else "未知物料"
|
occupied_name = exists.base.name if exists.base else "未知物料"
|
||||||
raise ValueError(f"序列号【{serial_number}】已存在!被物料 [{occupied_name}] 占用,请核查。")
|
return f"序列号【{serial_number}】已存在!被物料 [{occupied_name}] 占用,请核查。"
|
||||||
|
|
||||||
if batch_number and base_id:
|
if batch_number and base_id:
|
||||||
query = StockBuy.query.filter(
|
query = StockBuy.query.filter(
|
||||||
@ -34,7 +38,9 @@ class BuyInboundService:
|
|||||||
if exclude_id:
|
if exclude_id:
|
||||||
query = query.filter(StockBuy.id != exclude_id)
|
query = query.filter(StockBuy.id != exclude_id)
|
||||||
if query.first():
|
if query.first():
|
||||||
raise ValueError(f"该物料已存在批号【{batch_number}】,请勿重复录入,可直接在该批次下追加库存。")
|
return f"该物料已存在批号【{batch_number}】,请勿重复录入,可直接在该批次下追加库存。"
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# 1. 基础物料搜索
|
# 1. 基础物料搜索
|
||||||
@ -60,9 +66,25 @@ class BuyInboundService:
|
|||||||
|
|
||||||
items = []
|
items = []
|
||||||
for item in pagination.items:
|
for item in pagination.items:
|
||||||
|
# 查询最近一次入库的库位(采购入库 + 成品入库)
|
||||||
|
last_location = ''
|
||||||
|
# 查采购入库
|
||||||
|
last_buy = StockBuy.query.filter(
|
||||||
|
StockBuy.base_id == item.id
|
||||||
|
).order_by(StockBuy.in_date.desc()).first()
|
||||||
|
if last_buy and last_buy.warehouse_location:
|
||||||
|
last_location = last_buy.warehouse_location
|
||||||
|
else:
|
||||||
|
# 查成品入库
|
||||||
|
last_product = StockProduct.query.filter(
|
||||||
|
StockProduct.base_id == item.id
|
||||||
|
).order_by(StockProduct.in_date.desc()).first()
|
||||||
|
if last_product and last_product.warehouse_location:
|
||||||
|
last_location = last_product.warehouse_location
|
||||||
|
|
||||||
items.append({
|
items.append({
|
||||||
'id': item.id,
|
'id': item.id,
|
||||||
'company_name': item.company_name, # [新增]
|
'company_name': item.company_name,
|
||||||
'name': item.name,
|
'name': item.name,
|
||||||
'spec': item.spec_model,
|
'spec': item.spec_model,
|
||||||
'category': item.category,
|
'category': item.category,
|
||||||
@ -71,7 +93,8 @@ class BuyInboundService:
|
|||||||
'brand': getattr(item, 'brand', ''),
|
'brand': getattr(item, 'brand', ''),
|
||||||
'manufacturer': getattr(item, 'manufacturer', ''),
|
'manufacturer': getattr(item, 'manufacturer', ''),
|
||||||
'pinyin': getattr(item, 'pinyin', ''),
|
'pinyin': getattr(item, 'pinyin', ''),
|
||||||
'status': '启用'
|
'status': '启用',
|
||||||
|
'history_location': last_location
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -114,11 +137,14 @@ class BuyInboundService:
|
|||||||
if not has_report_file:
|
if not has_report_file:
|
||||||
raise ValueError(f"物料【{material.name}】为强管控物料,必须提供检测报告文件或外部链接")
|
raise ValueError(f"物料【{material.name}】为强管控物料,必须提供检测报告文件或外部链接")
|
||||||
|
|
||||||
BuyInboundService._check_unique(
|
# 校验批号/序列号唯一性
|
||||||
|
unique_error = BuyInboundService._check_unique(
|
||||||
base_id=base_id,
|
base_id=base_id,
|
||||||
serial_number=data.get('serial_number'),
|
serial_number=data.get('serial_number'),
|
||||||
batch_number=data.get('batch_number')
|
batch_number=data.get('batch_number')
|
||||||
)
|
)
|
||||||
|
if unique_error:
|
||||||
|
return {'error': unique_error}
|
||||||
|
|
||||||
beijing_tz = timezone(timedelta(hours=8))
|
beijing_tz = timezone(timedelta(hours=8))
|
||||||
current_time = datetime.now(beijing_tz).replace(tzinfo=None)
|
current_time = datetime.now(beijing_tz).replace(tzinfo=None)
|
||||||
@ -526,42 +552,3 @@ class BuyInboundService:
|
|||||||
def get_history_locations(base_id):
|
def get_history_locations(base_id):
|
||||||
return [r[0] for r in
|
return [r[0] for r in
|
||||||
db.session.query(StockBuy.warehouse_location).filter(StockBuy.base_id == base_id).distinct().all()]
|
db.session.query(StockBuy.warehouse_location).filter(StockBuy.base_id == base_id).distinct().all()]
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_last_location_by_base_id(base_id):
|
|
||||||
"""
|
|
||||||
获取指定物料最近一次入库的库位(跨表查询)
|
|
||||||
查询顺序:采购入库 -> 成品入库 -> 半成品入库,返回最新入库的库位
|
|
||||||
"""
|
|
||||||
from app.models.inbound.semi import StockSemi
|
|
||||||
|
|
||||||
# 1. 查询采购入库最新记录
|
|
||||||
last_buy = StockBuy.query.filter(
|
|
||||||
StockBuy.base_id == base_id
|
|
||||||
).order_by(StockBuy.in_date.desc()).first()
|
|
||||||
|
|
||||||
# 2. 查询成品入库最新记录
|
|
||||||
last_product = StockProduct.query.filter(
|
|
||||||
StockProduct.base_id == base_id
|
|
||||||
).order_by(StockProduct.production_date.desc()).first()
|
|
||||||
|
|
||||||
# 3. 查询半成品入库最新记录
|
|
||||||
last_semi = StockSemi.query.filter(
|
|
||||||
StockSemi.base_id == base_id
|
|
||||||
).order_by(StockSemi.production_date.desc()).first()
|
|
||||||
|
|
||||||
# 比较三个表中的最新入库时间,返回最新的库位
|
|
||||||
candidates = []
|
|
||||||
if last_buy and last_buy.warehouse_location:
|
|
||||||
candidates.append((last_buy.in_date, last_buy.warehouse_location))
|
|
||||||
if last_product and last_product.warehouse_location:
|
|
||||||
candidates.append((last_product.production_date, last_product.warehouse_location))
|
|
||||||
if last_semi and last_semi.warehouse_location:
|
|
||||||
candidates.append((last_semi.production_date, last_semi.warehouse_location))
|
|
||||||
|
|
||||||
if not candidates:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
# 按时间倒序排序,返回最新的库位
|
|
||||||
candidates.sort(key=lambda x: x[0] if x[0] else datetime.min, reverse=True)
|
|
||||||
return candidates[0][1] if candidates[0][1] else ""
|
|
||||||
|
|||||||
@ -23,7 +23,9 @@ class ProductInboundService:
|
|||||||
exists = query.first()
|
exists = query.first()
|
||||||
if exists:
|
if exists:
|
||||||
occupied_name = exists.base.name if (hasattr(exists, 'base') and exists.base) else "未知物料"
|
occupied_name = exists.base.name if (hasattr(exists, 'base') and exists.base) else "未知物料"
|
||||||
raise ValueError(f"序列号【{serial_number}】已存在!被成品 [{occupied_name}] 占用,请核查。")
|
return f"序列号【{serial_number}】已存在!被成品 [{occupied_name}] 占用,请核查。"
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def search_base_material(keyword, page=1, limit=50):
|
def search_base_material(keyword, page=1, limit=50):
|
||||||
@ -42,6 +44,29 @@ class ProductInboundService:
|
|||||||
pagination = query.paginate(page=page, per_page=limit, error_out=False)
|
pagination = query.paginate(page=page, per_page=limit, error_out=False)
|
||||||
results = []
|
results = []
|
||||||
for item in pagination.items:
|
for item in pagination.items:
|
||||||
|
# 查询最近一次入库的库位(成品入库 + 采购入库 + 半成品入库)
|
||||||
|
last_location = ''
|
||||||
|
# 查成品入库
|
||||||
|
last_product = StockProduct.query.filter(
|
||||||
|
StockProduct.base_id == item.id
|
||||||
|
).order_by(StockProduct.in_date.desc()).first()
|
||||||
|
if last_product and last_product.warehouse_location:
|
||||||
|
last_location = last_product.warehouse_location
|
||||||
|
else:
|
||||||
|
# 查采购入库
|
||||||
|
last_buy = StockBuy.query.filter(
|
||||||
|
StockBuy.base_id == item.id
|
||||||
|
).order_by(StockBuy.in_date.desc()).first()
|
||||||
|
if last_buy and last_buy.warehouse_location:
|
||||||
|
last_location = last_buy.warehouse_location
|
||||||
|
else:
|
||||||
|
# 查半成品入库
|
||||||
|
last_semi = StockSemi.query.filter(
|
||||||
|
StockSemi.base_id == item.id
|
||||||
|
).order_by(StockSemi.in_date.desc()).first()
|
||||||
|
if last_semi and last_semi.warehouse_location:
|
||||||
|
last_location = last_semi.warehouse_location
|
||||||
|
|
||||||
results.append({
|
results.append({
|
||||||
'id': item.id,
|
'id': item.id,
|
||||||
'company_name': item.company_name,
|
'company_name': item.company_name,
|
||||||
@ -50,7 +75,8 @@ class ProductInboundService:
|
|||||||
'category': item.category,
|
'category': item.category,
|
||||||
'unit': item.unit,
|
'unit': item.unit,
|
||||||
'type': item.material_type,
|
'type': item.material_type,
|
||||||
'status': '启用'
|
'status': '启用',
|
||||||
|
'history_location': last_location
|
||||||
})
|
})
|
||||||
return {
|
return {
|
||||||
"items": results,
|
"items": results,
|
||||||
@ -109,9 +135,12 @@ class ProductInboundService:
|
|||||||
if not material.is_enabled:
|
if not material.is_enabled:
|
||||||
raise ValueError(f"物料【{material.name}】已停用,无法办理新入库。")
|
raise ValueError(f"物料【{material.name}】已停用,无法办理新入库。")
|
||||||
|
|
||||||
ProductInboundService._check_unique(
|
# 校验序列号唯一性
|
||||||
|
unique_error = ProductInboundService._check_unique(
|
||||||
serial_number=data.get('serial_number')
|
serial_number=data.get('serial_number')
|
||||||
)
|
)
|
||||||
|
if unique_error:
|
||||||
|
return {'error': unique_error}
|
||||||
|
|
||||||
beijing_tz = timezone(timedelta(hours=8))
|
beijing_tz = timezone(timedelta(hours=8))
|
||||||
current_time = datetime.now(beijing_tz).replace(tzinfo=None)
|
current_time = datetime.now(beijing_tz).replace(tzinfo=None)
|
||||||
@ -578,42 +607,3 @@ class ProductInboundService:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_last_location_by_base_id(base_id):
|
|
||||||
"""
|
|
||||||
获取指定物料最近一次入库的库位(跨表查询)
|
|
||||||
查询顺序:成品入库 -> 采购入库 -> 半成品入库,返回最新入库的库位
|
|
||||||
"""
|
|
||||||
from app.models.inbound.product import StockProduct
|
|
||||||
|
|
||||||
# 1. 查询成品入库最新记录
|
|
||||||
last_product = StockProduct.query.filter(
|
|
||||||
StockProduct.base_id == base_id
|
|
||||||
).order_by(StockProduct.production_date.desc()).first()
|
|
||||||
|
|
||||||
# 2. 查询采购入库最新记录
|
|
||||||
last_buy = StockBuy.query.filter(
|
|
||||||
StockBuy.base_id == base_id
|
|
||||||
).order_by(StockBuy.in_date.desc()).first()
|
|
||||||
|
|
||||||
# 3. 查询半成品入库最新记录
|
|
||||||
last_semi = StockSemi.query.filter(
|
|
||||||
StockSemi.base_id == base_id
|
|
||||||
).order_by(StockSemi.production_date.desc()).first()
|
|
||||||
|
|
||||||
# 比较三个表中的最新入库时间,返回最新的库位
|
|
||||||
candidates = []
|
|
||||||
if last_product and last_product.warehouse_location:
|
|
||||||
candidates.append((last_product.production_date, last_product.warehouse_location))
|
|
||||||
if last_buy and last_buy.warehouse_location:
|
|
||||||
candidates.append((last_buy.in_date, last_buy.warehouse_location))
|
|
||||||
if last_semi and last_semi.warehouse_location:
|
|
||||||
candidates.append((last_semi.production_date, last_semi.warehouse_location))
|
|
||||||
|
|
||||||
if not candidates:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
# 按时间倒序排序,返回最新的库位
|
|
||||||
candidates.sort(key=lambda x: x[0] if x[0] else datetime.min, reverse=True)
|
|
||||||
return candidates[0][1] if candidates[0][1] else ""
|
|
||||||
|
|||||||
@ -23,7 +23,7 @@ class SemiInboundService:
|
|||||||
exists = query.first()
|
exists = query.first()
|
||||||
if exists:
|
if exists:
|
||||||
occupied_name = exists.base.name if (hasattr(exists, 'base') and exists.base) else "未知物料"
|
occupied_name = exists.base.name if (hasattr(exists, 'base') and exists.base) else "未知物料"
|
||||||
raise ValueError(f"序列号【{serial_number}】已存在!被半成品 [{occupied_name}] 占用,请核查。")
|
return f"序列号【{serial_number}】已存在!被半成品 [{occupied_name}] 占用,请核查。"
|
||||||
|
|
||||||
if batch_number and base_id:
|
if batch_number and base_id:
|
||||||
query = StockSemi.query.filter(
|
query = StockSemi.query.filter(
|
||||||
@ -33,7 +33,9 @@ class SemiInboundService:
|
|||||||
if exclude_id:
|
if exclude_id:
|
||||||
query = query.filter(StockSemi.id != exclude_id)
|
query = query.filter(StockSemi.id != exclude_id)
|
||||||
if query.first():
|
if query.first():
|
||||||
raise ValueError(f"该物料已存在批号【{batch_number}】,请勿重复建单,建议在原批次上追加库存。")
|
return f"该物料已存在批号【{batch_number}】,请勿重复建单,建议在原批次上追加库存。"
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def search_base_material(keyword, page=1, limit=50):
|
def search_base_material(keyword, page=1, limit=50):
|
||||||
@ -52,6 +54,29 @@ class SemiInboundService:
|
|||||||
pagination = query.paginate(page=page, per_page=limit, error_out=False)
|
pagination = query.paginate(page=page, per_page=limit, error_out=False)
|
||||||
results = []
|
results = []
|
||||||
for item in pagination.items:
|
for item in pagination.items:
|
||||||
|
# 查询最近一次入库的库位(半成品入库 + 采购入库 + 成品入库)
|
||||||
|
last_location = ''
|
||||||
|
# 查半成品入库
|
||||||
|
last_semi = StockSemi.query.filter(
|
||||||
|
StockSemi.base_id == item.id
|
||||||
|
).order_by(StockSemi.in_date.desc()).first()
|
||||||
|
if last_semi and last_semi.warehouse_location:
|
||||||
|
last_location = last_semi.warehouse_location
|
||||||
|
else:
|
||||||
|
# 查采购入库
|
||||||
|
last_buy = StockBuy.query.filter(
|
||||||
|
StockBuy.base_id == item.id
|
||||||
|
).order_by(StockBuy.in_date.desc()).first()
|
||||||
|
if last_buy and last_buy.warehouse_location:
|
||||||
|
last_location = last_buy.warehouse_location
|
||||||
|
else:
|
||||||
|
# 查成品入库
|
||||||
|
last_product = StockProduct.query.filter(
|
||||||
|
StockProduct.base_id == item.id
|
||||||
|
).order_by(StockProduct.in_date.desc()).first()
|
||||||
|
if last_product and last_product.warehouse_location:
|
||||||
|
last_location = last_product.warehouse_location
|
||||||
|
|
||||||
results.append({
|
results.append({
|
||||||
'id': item.id,
|
'id': item.id,
|
||||||
'company_name': item.company_name,
|
'company_name': item.company_name,
|
||||||
@ -60,7 +85,8 @@ class SemiInboundService:
|
|||||||
'category': item.category,
|
'category': item.category,
|
||||||
'unit': item.unit,
|
'unit': item.unit,
|
||||||
'type': item.material_type,
|
'type': item.material_type,
|
||||||
'status': '启用'
|
'status': '启用',
|
||||||
|
'history_location': last_location
|
||||||
})
|
})
|
||||||
return {"items": results, "total": pagination.total, "page": page, "has_next": pagination.has_next}
|
return {"items": results, "total": pagination.total, "page": page, "has_next": pagination.has_next}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -116,11 +142,14 @@ class SemiInboundService:
|
|||||||
if not material.is_enabled:
|
if not material.is_enabled:
|
||||||
raise ValueError(f"物料【{material.name}】已停用,无法办理新入库。")
|
raise ValueError(f"物料【{material.name}】已停用,无法办理新入库。")
|
||||||
|
|
||||||
SemiInboundService._check_unique(
|
# 校验批号/序列号唯一性
|
||||||
|
unique_error = SemiInboundService._check_unique(
|
||||||
base_id=base_id,
|
base_id=base_id,
|
||||||
serial_number=data.get('serial_number'),
|
serial_number=data.get('serial_number'),
|
||||||
batch_number=data.get('batch_number')
|
batch_number=data.get('batch_number')
|
||||||
)
|
)
|
||||||
|
if unique_error:
|
||||||
|
return {'error': unique_error}
|
||||||
|
|
||||||
beijing_tz = timezone(timedelta(hours=8))
|
beijing_tz = timezone(timedelta(hours=8))
|
||||||
current_time = datetime.now(beijing_tz).replace(tzinfo=None)
|
current_time = datetime.now(beijing_tz).replace(tzinfo=None)
|
||||||
@ -655,42 +684,3 @@ class SemiInboundService:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_last_location_by_base_id(base_id):
|
|
||||||
"""
|
|
||||||
获取指定物料最近一次入库的库位(跨表查询)
|
|
||||||
查询顺序:半成品入库 -> 采购入库 -> 成品入库,返回最新入库的库位
|
|
||||||
"""
|
|
||||||
from app.models.inbound.semi import StockSemi
|
|
||||||
|
|
||||||
# 1. 查询半成品入库最新记录
|
|
||||||
last_semi = StockSemi.query.filter(
|
|
||||||
StockSemi.base_id == base_id
|
|
||||||
).order_by(StockSemi.production_date.desc()).first()
|
|
||||||
|
|
||||||
# 2. 查询采购入库最新记录
|
|
||||||
last_buy = StockBuy.query.filter(
|
|
||||||
StockBuy.base_id == base_id
|
|
||||||
).order_by(StockBuy.in_date.desc()).first()
|
|
||||||
|
|
||||||
# 3. 查询成品入库最新记录
|
|
||||||
last_product = StockProduct.query.filter(
|
|
||||||
StockProduct.base_id == base_id
|
|
||||||
).order_by(StockProduct.production_date.desc()).first()
|
|
||||||
|
|
||||||
# 比较三个表中的最新入库时间,返回最新的库位
|
|
||||||
candidates = []
|
|
||||||
if last_semi and last_semi.warehouse_location:
|
|
||||||
candidates.append((last_semi.production_date, last_semi.warehouse_location))
|
|
||||||
if last_buy and last_buy.warehouse_location:
|
|
||||||
candidates.append((last_buy.in_date, last_buy.warehouse_location))
|
|
||||||
if last_product and last_product.warehouse_location:
|
|
||||||
candidates.append((last_product.production_date, last_product.warehouse_location))
|
|
||||||
|
|
||||||
if not candidates:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
# 按时间倒序排序,返回最新的库位
|
|
||||||
candidates.sort(key=lambda x: x[0] if x[0] else datetime.min, reverse=True)
|
|
||||||
return candidates[0][1] if candidates[0][1] else ""
|
|
||||||
|
|||||||
@ -317,10 +317,7 @@ const selectedIds = ref<number[]>([])
|
|||||||
const isBulkDeleteMode = ref(false)
|
const isBulkDeleteMode = ref(false)
|
||||||
|
|
||||||
const handleTreeCheck = (data: any, checked: any) => {
|
const handleTreeCheck = (data: any, checked: any) => {
|
||||||
// 使用 getCheckedKeys(true) 只获取叶子节点,防止 el-tree 自动连带选中父节点导致误删
|
selectedIds.value = checked.checkedKeys
|
||||||
if (treeRef.value) {
|
|
||||||
selectedIds.value = treeRef.value.getCheckedKeys(true) as number[]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const cancelBatchMode = () => {
|
const cancelBatchMode = () => {
|
||||||
|
|||||||
@ -669,7 +669,6 @@ import {ref, reactive, onMounted, watch, computed} from 'vue'
|
|||||||
import {Plus, Setting, Refresh, Search, Lock, Box, House, InfoFilled, Link, Printer, Camera, Delete, Picture} from '@element-plus/icons-vue'
|
import {Plus, Setting, Refresh, Search, Lock, Box, House, InfoFilled, Link, Printer, Camera, Delete, Picture} from '@element-plus/icons-vue'
|
||||||
import {ElMessage, ElMessageBox, ElLoading} from 'element-plus'
|
import {ElMessage, ElMessageBox, ElLoading} from 'element-plus'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import request from '@/utils/request'
|
|
||||||
import {
|
import {
|
||||||
getBuyList,
|
getBuyList,
|
||||||
createBuyInbound,
|
createBuyInbound,
|
||||||
@ -1133,7 +1132,7 @@ const loadMoreMaterials = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onMaterialSelected = async (val: number) => {
|
const onMaterialSelected = (val: number) => {
|
||||||
const item = materialOptions.value.find(i => i.id === val)
|
const item = materialOptions.value.find(i => i.id === val)
|
||||||
if (item) {
|
if (item) {
|
||||||
form.company_name = item.company_name
|
form.company_name = item.company_name
|
||||||
@ -1147,16 +1146,10 @@ const onMaterialSelected = async (val: number) => {
|
|||||||
// 更新表单校验规则
|
// 更新表单校验规则
|
||||||
updateInspectionRules()
|
updateInspectionRules()
|
||||||
checkHistoryAndSetMode(item.id)
|
checkHistoryAndSetMode(item.id)
|
||||||
|
// 自动填充历史库位
|
||||||
// 获取该物料历史入库库位(新增独立接口)
|
if (item.history_location) {
|
||||||
try {
|
form.warehouse_location = item.history_location
|
||||||
const res = await request.get('/v1/inbound/buy/last-location', { params: { base_id: val } })
|
ElMessage.info(`已自动带入该物料的历史库位:【${item.history_location}】,请核对。`)
|
||||||
if (res.code === 200 && res.data.location) {
|
|
||||||
form.warehouse_location = res.data.location
|
|
||||||
ElMessage.info(`已自动带入该物料历史库位:【${res.data.location}】,请核对。`)
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('获取历史库位失败', e)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -559,7 +559,6 @@ import { ref, reactive, onMounted, watch, computed } from 'vue'
|
|||||||
import { Plus, Setting, Refresh, Search, Box, House, Link, InfoFilled, Printer, Camera, Picture } from '@element-plus/icons-vue'
|
import { Plus, Setting, Refresh, Search, Box, House, Link, InfoFilled, Printer, Camera, Picture } from '@element-plus/icons-vue'
|
||||||
import { ElMessage, ElLoading } from 'element-plus'
|
import { ElMessage, ElLoading } from 'element-plus'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import request from '@/utils/request'
|
|
||||||
import {
|
import {
|
||||||
getProductList,
|
getProductList,
|
||||||
createProductInbound,
|
createProductInbound,
|
||||||
@ -1030,7 +1029,7 @@ const loadMoreMaterials = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onMaterialSelected = async (val: number) => {
|
const onMaterialSelected = (val: number) => {
|
||||||
const item = materialOptions.value.find(i => i.id === val)
|
const item = materialOptions.value.find(i => i.id === val)
|
||||||
if (item) {
|
if (item) {
|
||||||
form.company_name = item.company_name // [新增]
|
form.company_name = item.company_name // [新增]
|
||||||
@ -1039,16 +1038,10 @@ const onMaterialSelected = async (val: number) => {
|
|||||||
form.material_type = item.type
|
form.material_type = item.type
|
||||||
form.category = item.category
|
form.category = item.category
|
||||||
form.unit = item.unit
|
form.unit = item.unit
|
||||||
|
// 自动填充历史库位
|
||||||
// 获取该物料历史入库库位(新增独立接口)
|
if (item.history_location) {
|
||||||
try {
|
form.warehouse_location = item.history_location
|
||||||
const res = await request.get('/v1/inbound/product/last-location', { params: { base_id: val } })
|
ElMessage.info(`已自动带入该物料的历史库位:【${item.history_location}】,请核对。`)
|
||||||
if (res.code === 200 && res.data.location) {
|
|
||||||
form.warehouse_location = res.data.location
|
|
||||||
ElMessage.info(`已自动带入该物料历史库位:【${res.data.location}】,请核对。`)
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('获取历史库位失败', e)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -616,7 +616,6 @@ import {ref, reactive, onMounted, watch, computed} from 'vue'
|
|||||||
import {Plus, Setting, Refresh, Search, Lock, Box, House, InfoFilled, Link, Printer, Camera, Picture} from '@element-plus/icons-vue'
|
import {Plus, Setting, Refresh, Search, Lock, Box, House, InfoFilled, Link, Printer, Camera, Picture} from '@element-plus/icons-vue'
|
||||||
import {ElMessage, ElLoading} from 'element-plus'
|
import {ElMessage, ElLoading} from 'element-plus'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import request from '@/utils/request'
|
|
||||||
import {
|
import {
|
||||||
getSemiList,
|
getSemiList,
|
||||||
createSemiInbound,
|
createSemiInbound,
|
||||||
@ -1028,7 +1027,7 @@ const loadMoreMaterials = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onMaterialSelected = async (val: number) => {
|
const onMaterialSelected = (val: number) => {
|
||||||
const item = materialOptions.value.find(i => i.id === val)
|
const item = materialOptions.value.find(i => i.id === val)
|
||||||
if (item) {
|
if (item) {
|
||||||
form.company_name = item.company_name // [新增]
|
form.company_name = item.company_name // [新增]
|
||||||
@ -1038,16 +1037,10 @@ const onMaterialSelected = async (val: number) => {
|
|||||||
form.unit = item.unit
|
form.unit = item.unit
|
||||||
form.material_type = item.type
|
form.material_type = item.type
|
||||||
checkHistoryAndSetMode(item.id)
|
checkHistoryAndSetMode(item.id)
|
||||||
|
// 自动填充历史库位
|
||||||
// 获取该物料历史入库库位(新增独立接口)
|
if (item.history_location) {
|
||||||
try {
|
form.warehouse_location = item.history_location
|
||||||
const res = await request.get('/v1/inbound/semi/last-location', { params: { base_id: val } })
|
ElMessage.info(`已自动带入该物料的历史库位:【${item.history_location}】,请核对。`)
|
||||||
if (res.code === 200 && res.data.location) {
|
|
||||||
form.warehouse_location = res.data.location
|
|
||||||
ElMessage.info(`已自动带入该物料历史库位:【${res.data.location}】,请核对。`)
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('获取历史库位失败', e)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user