增加入库记录页面,同时修正三组入库的时间问题
This commit is contained in:
@ -4,6 +4,8 @@ from .semi import inbound_semi_bp
|
|||||||
from .base import inbound_base_bp
|
from .base import inbound_base_bp
|
||||||
# 导入 product
|
# 导入 product
|
||||||
from .product import inbound_product_bp
|
from .product import inbound_product_bp
|
||||||
|
# ★ [新增] 导入 summary
|
||||||
|
from .inbound_summary import bp as inbound_summary_bp
|
||||||
|
|
||||||
inbound_bp = Blueprint('inbound', __name__)
|
inbound_bp = Blueprint('inbound', __name__)
|
||||||
|
|
||||||
@ -11,4 +13,7 @@ inbound_bp.register_blueprint(inbound_buy_bp, url_prefix='/buy')
|
|||||||
inbound_bp.register_blueprint(inbound_semi_bp, url_prefix='/semi')
|
inbound_bp.register_blueprint(inbound_semi_bp, url_prefix='/semi')
|
||||||
inbound_bp.register_blueprint(inbound_base_bp, url_prefix='/base')
|
inbound_bp.register_blueprint(inbound_base_bp, url_prefix='/base')
|
||||||
# 挂载 product,前缀改为 /product
|
# 挂载 product,前缀改为 /product
|
||||||
inbound_bp.register_blueprint(inbound_product_bp, url_prefix='/product')
|
inbound_bp.register_blueprint(inbound_product_bp, url_prefix='/product')
|
||||||
|
|
||||||
|
# ★ [新增] 挂载 summary, url 变成 /api/v1/inbound/summary/list
|
||||||
|
inbound_bp.register_blueprint(inbound_summary_bp, url_prefix='/summary')
|
||||||
35
inventory-backend/app/api/v1/inbound/inbound_summary.py
Normal file
35
inventory-backend/app/api/v1/inbound/inbound_summary.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
from flask import Blueprint, request, jsonify
|
||||||
|
from app.services.inbound.inbound_summary_service import InboundSummaryService
|
||||||
|
|
||||||
|
# 定义蓝图
|
||||||
|
bp = Blueprint('inbound_summary', __name__)
|
||||||
|
|
||||||
|
@bp.route('/list', methods=['GET'])
|
||||||
|
def get_list():
|
||||||
|
try:
|
||||||
|
# 获取参数
|
||||||
|
page = request.args.get('page', 1, type=int)
|
||||||
|
per_page = request.args.get('per_page', 20, type=int) # 默认每页20
|
||||||
|
keyword = request.args.get('keyword', '')
|
||||||
|
start_date = request.args.get('start_date')
|
||||||
|
end_date = request.args.get('end_date')
|
||||||
|
source_type = request.args.get('source_type') # 可选:筛选 specific table
|
||||||
|
|
||||||
|
result = InboundSummaryService.get_list(
|
||||||
|
page=page,
|
||||||
|
per_page=per_page,
|
||||||
|
keyword=keyword,
|
||||||
|
start_date=start_date,
|
||||||
|
end_date=end_date,
|
||||||
|
source_type=source_type
|
||||||
|
)
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'code': 200,
|
||||||
|
'msg': 'success',
|
||||||
|
'data': result
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
# 生产环境建议记录详细日志
|
||||||
|
print(f"Inbound Summary Error: {str(e)}")
|
||||||
|
return jsonify({'code': 500, 'msg': str(e)}), 500
|
||||||
@ -15,7 +15,7 @@ class StockBuy(db.Model):
|
|||||||
|
|
||||||
# 身份标识
|
# 身份标识
|
||||||
sku = db.Column(db.String(100))
|
sku = db.Column(db.String(100))
|
||||||
in_date = db.Column(db.Date)
|
in_date = db.Column(db.DateTime)
|
||||||
barcode = db.Column(db.String(100))
|
barcode = db.Column(db.String(100))
|
||||||
serial_number = db.Column(db.String(100))
|
serial_number = db.Column(db.String(100))
|
||||||
batch_number = db.Column(db.String(100))
|
batch_number = db.Column(db.String(100))
|
||||||
|
|||||||
@ -15,7 +15,7 @@ class StockProduct(db.Model):
|
|||||||
|
|
||||||
# 身份标识
|
# 身份标识
|
||||||
sku = db.Column(db.String(100))
|
sku = db.Column(db.String(100))
|
||||||
production_date = db.Column(db.Date)
|
production_date = db.Column(db.DateTime)
|
||||||
barcode = db.Column(db.String(100))
|
barcode = db.Column(db.String(100))
|
||||||
serial_number = db.Column(db.String(100))
|
serial_number = db.Column(db.String(100))
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,7 @@ class StockSemi(db.Model):
|
|||||||
base_id = db.Column(db.Integer, db.ForeignKey('material_base.id'), nullable=False)
|
base_id = db.Column(db.Integer, db.ForeignKey('material_base.id'), nullable=False)
|
||||||
|
|
||||||
sku = db.Column(db.String(100))
|
sku = db.Column(db.String(100))
|
||||||
production_date = db.Column(db.Date)
|
production_date = db.Column(db.DateTime)
|
||||||
barcode = db.Column(db.String(100))
|
barcode = db.Column(db.String(100))
|
||||||
serial_number = db.Column(db.String(100))
|
serial_number = db.Column(db.String(100))
|
||||||
batch_number = db.Column(db.String(100))
|
batch_number = db.Column(db.String(100))
|
||||||
|
|||||||
@ -40,7 +40,7 @@ class BuyInboundService:
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# 2. 新增入库逻辑
|
# 2. 新增入库逻辑 (修改:精确到时间)
|
||||||
# ============================================================
|
# ============================================================
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def handle_inbound(data):
|
def handle_inbound(data):
|
||||||
@ -51,14 +51,24 @@ class BuyInboundService:
|
|||||||
material = MaterialBase.query.get(base_id)
|
material = MaterialBase.query.get(base_id)
|
||||||
if not material: raise ValueError("物料不存在")
|
if not material: raise ValueError("物料不存在")
|
||||||
|
|
||||||
in_date_val = datetime.utcnow().date()
|
# [核心修改] 默认使用当前时间(含时分秒),不再截取 .date()
|
||||||
|
current_time = datetime.now()
|
||||||
|
in_date_val = current_time
|
||||||
|
|
||||||
if data.get('in_date'):
|
if data.get('in_date'):
|
||||||
try:
|
try:
|
||||||
date_str = str(data['in_date'])
|
date_str = str(data['in_date'])
|
||||||
if len(date_str) > 10: date_str = date_str[:10]
|
# 如果前端传了时分秒,尝试直接解析
|
||||||
in_date_val = datetime.strptime(date_str, '%Y-%m-%d').date()
|
if len(date_str) > 10:
|
||||||
|
in_date_val = datetime.strptime(date_str, '%Y-%m-%d %H:%M:%S')
|
||||||
|
else:
|
||||||
|
# 如果只传了日期,手动补上当前时间的时分秒,保证同日入库的排序正确
|
||||||
|
d_temp = datetime.strptime(date_str, '%Y-%m-%d')
|
||||||
|
in_date_val = datetime(d_temp.year, d_temp.month, d_temp.day,
|
||||||
|
current_time.hour, current_time.minute, current_time.second)
|
||||||
except:
|
except:
|
||||||
pass
|
# 解析失败则使用当前时间作为兜底
|
||||||
|
in_date_val = current_time
|
||||||
|
|
||||||
in_qty = float(data.get('in_quantity') or 0)
|
in_qty = float(data.get('in_quantity') or 0)
|
||||||
u_price = float(data.get('unit_price') or 0)
|
u_price = float(data.get('unit_price') or 0)
|
||||||
@ -80,10 +90,10 @@ class BuyInboundService:
|
|||||||
global_print_id=next_global_id,
|
global_print_id=next_global_id,
|
||||||
sku=generated_sku,
|
sku=generated_sku,
|
||||||
barcode=final_barcode,
|
barcode=final_barcode,
|
||||||
in_date=in_date_val,
|
in_date=in_date_val, # 存入 DateTime 对象
|
||||||
serial_number=data.get('serial_number'),
|
serial_number=data.get('serial_number'),
|
||||||
batch_number=data.get('batch_number'),
|
batch_number=data.get('batch_number'),
|
||||||
status=data.get('status', '在库'), # 默认在库
|
status=data.get('status', '在库'),
|
||||||
in_quantity=in_qty,
|
in_quantity=in_qty,
|
||||||
stock_quantity=in_qty,
|
stock_quantity=in_qty,
|
||||||
available_quantity=in_qty,
|
available_quantity=in_qty,
|
||||||
@ -185,7 +195,7 @@ class BuyInboundService:
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# 6. 获取列表 (核心逻辑修改)
|
# 6. 获取列表 (修改:按时间倒序排序 + 展示只显示日期)
|
||||||
# ============================================================
|
# ============================================================
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_list(page, limit, keyword=None, statuses=None):
|
def get_list(page, limit, keyword=None, statuses=None):
|
||||||
@ -196,7 +206,7 @@ class BuyInboundService:
|
|||||||
try:
|
try:
|
||||||
query = db.session.query(StockBuy).outerjoin(MaterialBase, StockBuy.base_id == MaterialBase.id)
|
query = db.session.query(StockBuy).outerjoin(MaterialBase, StockBuy.base_id == MaterialBase.id)
|
||||||
|
|
||||||
# 1. 关键词搜索 (覆盖所有关键字段)
|
# 1. 关键词搜索
|
||||||
if keyword:
|
if keyword:
|
||||||
kw = f'%{keyword}%'
|
kw = f'%{keyword}%'
|
||||||
query = query.filter(
|
query = query.filter(
|
||||||
@ -210,24 +220,13 @@ class BuyInboundService:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# 2. 状态筛选与零库存隐藏逻辑
|
# 2. 状态筛选
|
||||||
# 用户要求:
|
|
||||||
# - 默认显示:'在库', '借库'。
|
|
||||||
# - 零库存规则:库存为0时,不在页面显示(除非筛选了'已出库')。
|
|
||||||
|
|
||||||
if not statuses:
|
if not statuses:
|
||||||
# 默认情况:只查 '在库' 和 '借库'
|
|
||||||
statuses = ['在库', '借库']
|
statuses = ['在库', '借库']
|
||||||
|
|
||||||
# 构建筛选条件
|
|
||||||
# 如果筛选条件中 包含 '已出库',则允许显示 stock_quantity >= 0 (即显示所有)
|
|
||||||
# 如果筛选条件中 不包含 '已出库',则强制要求 stock_quantity > 0 (隐藏零库存)
|
|
||||||
|
|
||||||
if '已出库' in statuses:
|
if '已出库' in statuses:
|
||||||
# 用户想看已出库的,直接按状态查,不做数量限制
|
|
||||||
query = query.filter(StockBuy.status.in_(statuses))
|
query = query.filter(StockBuy.status.in_(statuses))
|
||||||
else:
|
else:
|
||||||
# 用户不想看已出库的,按状态查 AND 数量必须 > 0
|
|
||||||
query = query.filter(
|
query = query.filter(
|
||||||
and_(
|
and_(
|
||||||
StockBuy.status.in_(statuses),
|
StockBuy.status.in_(statuses),
|
||||||
@ -235,7 +234,8 @@ class BuyInboundService:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
pagination = query.order_by(StockBuy.id.desc()).paginate(page=page, per_page=limit, error_out=False)
|
# [核心修改] 按照入库时间倒序排序 (从近到远)
|
||||||
|
pagination = query.order_by(StockBuy.in_date.desc()).paginate(page=page, per_page=limit, error_out=False)
|
||||||
current_items = pagination.items
|
current_items = pagination.items
|
||||||
|
|
||||||
def parse_img(json_str):
|
def parse_img(json_str):
|
||||||
@ -247,10 +247,17 @@ class BuyInboundService:
|
|||||||
|
|
||||||
items = []
|
items = []
|
||||||
for item in current_items:
|
for item in current_items:
|
||||||
# 获取单行数据,不再进行聚合计算
|
|
||||||
qty_stock = float(item.stock_quantity or 0)
|
qty_stock = float(item.stock_quantity or 0)
|
||||||
qty_avail = float(item.available_quantity or 0)
|
qty_avail = float(item.available_quantity or 0)
|
||||||
|
|
||||||
|
# [核心修改] 格式化展示日期,去掉时分秒
|
||||||
|
date_display = ''
|
||||||
|
if item.in_date:
|
||||||
|
try:
|
||||||
|
date_display = item.in_date.strftime('%Y-%m-%d')
|
||||||
|
except:
|
||||||
|
date_display = str(item.in_date)[:10]
|
||||||
|
|
||||||
d = {
|
d = {
|
||||||
'id': item.id,
|
'id': item.id,
|
||||||
'base_id': item.base_id,
|
'base_id': item.base_id,
|
||||||
@ -261,7 +268,7 @@ class BuyInboundService:
|
|||||||
'material_type': item.material.material_type if item.material else '',
|
'material_type': item.material.material_type if item.material else '',
|
||||||
|
|
||||||
'sku': item.sku,
|
'sku': item.sku,
|
||||||
'inbound_date': str(item.in_date) if item.in_date else '',
|
'inbound_date': date_display, # 前端展示用的日期字符串
|
||||||
'barcode': item.barcode,
|
'barcode': item.barcode,
|
||||||
'serial_number': item.serial_number,
|
'serial_number': item.serial_number,
|
||||||
'batch_number': item.batch_number,
|
'batch_number': item.batch_number,
|
||||||
@ -273,8 +280,6 @@ class BuyInboundService:
|
|||||||
'qty_stock': qty_stock,
|
'qty_stock': qty_stock,
|
||||||
'qty_available': qty_avail,
|
'qty_available': qty_avail,
|
||||||
|
|
||||||
# 解除挂钩:不再返回所有批次的总和,直接返回当前批次的数量
|
|
||||||
# 为了兼容前端字段名,这里直接用当前行数量填充
|
|
||||||
'sum_stock': qty_stock,
|
'sum_stock': qty_stock,
|
||||||
'sum_available': qty_avail,
|
'sum_available': qty_avail,
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,190 @@
|
|||||||
|
from sqlalchemy import select, literal, union_all, desc, asc, func, or_, cast, String, Numeric, Date
|
||||||
|
from app.extensions import db
|
||||||
|
from app.models.inbound.buy import StockBuy
|
||||||
|
from app.models.inbound.semi import StockSemi
|
||||||
|
from app.models.inbound.product import StockProduct
|
||||||
|
from app.models.base import MaterialBase
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
|
||||||
|
class InboundSummaryService:
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_list(page=1, per_page=10, keyword=None, start_date=None, end_date=None, source_type=None):
|
||||||
|
"""
|
||||||
|
聚合查询:
|
||||||
|
1. 联合 StockBuy, StockSemi, StockProduct 三张表
|
||||||
|
2. 关联 MaterialBase 获取名称规格
|
||||||
|
3. 计算动态状态 (库存耗尽显示已出库)
|
||||||
|
4. 排序:默认按入库日期倒序 (最近的在前)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# =========================================================
|
||||||
|
# 1. 构建三个子查询 (Subqueries)
|
||||||
|
# =========================================================
|
||||||
|
|
||||||
|
# --- A. 采购件 (StockBuy) ---
|
||||||
|
q_buy = db.session.query(
|
||||||
|
StockBuy.id.label('id'),
|
||||||
|
StockBuy.base_id.label('base_id'),
|
||||||
|
StockBuy.sku.label('sku'),
|
||||||
|
StockBuy.in_date.label('inbound_date'),
|
||||||
|
StockBuy.in_quantity.label('in_qty'),
|
||||||
|
StockBuy.stock_quantity.label('current_qty'),
|
||||||
|
|
||||||
|
cast(StockBuy.supplier_name, String).label('source_info'),
|
||||||
|
StockBuy.status.label('orig_status'),
|
||||||
|
cast(StockBuy.batch_number, String).label('batch_number'),
|
||||||
|
cast(literal('buy'), String).label('source_type')
|
||||||
|
)
|
||||||
|
|
||||||
|
# --- B. 半成品 (StockSemi) ---
|
||||||
|
q_semi = db.session.query(
|
||||||
|
StockSemi.id.label('id'),
|
||||||
|
StockSemi.base_id.label('base_id'),
|
||||||
|
StockSemi.sku.label('sku'),
|
||||||
|
StockSemi.production_date.label('inbound_date'),
|
||||||
|
StockSemi.in_quantity.label('in_qty'),
|
||||||
|
StockSemi.stock_quantity.label('current_qty'),
|
||||||
|
|
||||||
|
cast(StockSemi.production_manager, String).label('source_info'),
|
||||||
|
StockSemi.status.label('orig_status'),
|
||||||
|
cast(StockSemi.batch_number, String).label('batch_number'),
|
||||||
|
cast(literal('semi'), String).label('source_type')
|
||||||
|
)
|
||||||
|
|
||||||
|
# --- C. 成品 (StockProduct) ---
|
||||||
|
q_product = db.session.query(
|
||||||
|
StockProduct.id.label('id'),
|
||||||
|
StockProduct.base_id.label('base_id'),
|
||||||
|
StockProduct.sku.label('sku'),
|
||||||
|
StockProduct.production_date.label('inbound_date'),
|
||||||
|
StockProduct.in_quantity.label('in_qty'),
|
||||||
|
StockProduct.stock_quantity.label('current_qty'),
|
||||||
|
|
||||||
|
cast(StockProduct.production_manager, String).label('source_info'),
|
||||||
|
StockProduct.status.label('orig_status'),
|
||||||
|
cast(StockProduct.serial_number, String).label('batch_number'),
|
||||||
|
cast(literal('product'), String).label('source_type')
|
||||||
|
)
|
||||||
|
|
||||||
|
# =========================================================
|
||||||
|
# 2. 组合查询 (UNION ALL)
|
||||||
|
# =========================================================
|
||||||
|
combined_query = union_all(q_buy, q_semi, q_product)
|
||||||
|
cte = combined_query.subquery()
|
||||||
|
|
||||||
|
# =========================================================
|
||||||
|
# 3. 主查询:关联 MaterialBase
|
||||||
|
# =========================================================
|
||||||
|
query = db.session.query(
|
||||||
|
cte,
|
||||||
|
MaterialBase.name.label('material_name'),
|
||||||
|
MaterialBase.spec_model.label('spec_model'),
|
||||||
|
MaterialBase.category.label('category'),
|
||||||
|
MaterialBase.material_type.label('material_type')
|
||||||
|
).outerjoin(
|
||||||
|
MaterialBase, cte.c.base_id == MaterialBase.id
|
||||||
|
)
|
||||||
|
|
||||||
|
# =========================================================
|
||||||
|
# 4. 过滤条件
|
||||||
|
# =========================================================
|
||||||
|
if keyword:
|
||||||
|
rule = or_(
|
||||||
|
cte.c.sku.ilike(f'%{keyword}%'),
|
||||||
|
cte.c.source_info.ilike(f'%{keyword}%'),
|
||||||
|
cte.c.batch_number.ilike(f'%{keyword}%'),
|
||||||
|
MaterialBase.name.ilike(f'%{keyword}%'),
|
||||||
|
MaterialBase.spec_model.ilike(f'%{keyword}%')
|
||||||
|
)
|
||||||
|
query = query.filter(rule)
|
||||||
|
|
||||||
|
if start_date and end_date:
|
||||||
|
query = query.filter(cte.c.inbound_date.between(start_date, end_date))
|
||||||
|
|
||||||
|
if source_type:
|
||||||
|
query = query.filter(cte.c.source_type == source_type)
|
||||||
|
|
||||||
|
# =========================================================
|
||||||
|
# 5. 获取总数
|
||||||
|
# =========================================================
|
||||||
|
count_query = db.session.query(func.count()) \
|
||||||
|
.select_from(cte) \
|
||||||
|
.outerjoin(MaterialBase, cte.c.base_id == MaterialBase.id)
|
||||||
|
|
||||||
|
if keyword:
|
||||||
|
count_query = count_query.filter(rule)
|
||||||
|
if start_date and end_date:
|
||||||
|
count_query = count_query.filter(cte.c.inbound_date.between(start_date, end_date))
|
||||||
|
if source_type:
|
||||||
|
count_query = count_query.filter(cte.c.source_type == source_type)
|
||||||
|
|
||||||
|
total = count_query.scalar() or 0
|
||||||
|
|
||||||
|
# =========================================================
|
||||||
|
# 6. 排序与分页
|
||||||
|
# =========================================================
|
||||||
|
# ★★★ 修改处:优先按入库日期倒序排列 (最近的在前) ★★★
|
||||||
|
# 如果日期相同,再按 SKU 排序,保证分页稳定性
|
||||||
|
query = query.order_by(desc(cte.c.inbound_date), asc(cte.c.sku))
|
||||||
|
|
||||||
|
pagination = query.limit(per_page).offset((page - 1) * per_page).all()
|
||||||
|
|
||||||
|
# =========================================================
|
||||||
|
# 7. 数据格式化
|
||||||
|
# =========================================================
|
||||||
|
items = []
|
||||||
|
type_map = {
|
||||||
|
'buy': '采购入库',
|
||||||
|
'semi': '半成品生产',
|
||||||
|
'product': '成品完工'
|
||||||
|
}
|
||||||
|
|
||||||
|
for row in pagination:
|
||||||
|
date_str = ""
|
||||||
|
if row.inbound_date:
|
||||||
|
try:
|
||||||
|
date_str = row.inbound_date.strftime('%Y-%m-%d')
|
||||||
|
except Exception:
|
||||||
|
date_str = str(row.inbound_date)
|
||||||
|
|
||||||
|
in_qty = float(row.in_qty) if row.in_qty is not None else 0.0
|
||||||
|
current_qty = float(row.current_qty) if row.current_qty is not None else 0.0
|
||||||
|
|
||||||
|
# 状态逻辑
|
||||||
|
final_status = row.orig_status
|
||||||
|
if current_qty <= 0:
|
||||||
|
final_status = "已出库"
|
||||||
|
elif current_qty < in_qty:
|
||||||
|
final_status = "部分出库"
|
||||||
|
|
||||||
|
items.append({
|
||||||
|
'id': row.id,
|
||||||
|
'sku': row.sku or "",
|
||||||
|
'name': row.material_name or "未知物品",
|
||||||
|
'spec_model': row.spec_model or "",
|
||||||
|
'category': row.category or "",
|
||||||
|
'material_type': row.material_type or "",
|
||||||
|
|
||||||
|
'inbound_date': date_str,
|
||||||
|
'quantity': in_qty,
|
||||||
|
'current_qty': current_qty,
|
||||||
|
'source_info': row.source_info or "",
|
||||||
|
'status': final_status,
|
||||||
|
'source_type': row.source_type,
|
||||||
|
'type_label': type_map.get(row.source_type, "未知类型"),
|
||||||
|
'batch_number': row.batch_number or ""
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
'items': items,
|
||||||
|
'total': total,
|
||||||
|
'pages': (total + per_page - 1) // per_page if per_page > 0 else 0,
|
||||||
|
'current_page': page
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print("【InboundSummaryService Error】:", str(e))
|
||||||
|
traceback.print_exc()
|
||||||
|
raise e
|
||||||
@ -32,6 +32,9 @@ class ProductInboundService:
|
|||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# 2. 新增入库逻辑 (修改:精确到时间)
|
||||||
|
# ============================================================
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def handle_inbound(data):
|
def handle_inbound(data):
|
||||||
from app.models.inbound.product import StockProduct
|
from app.models.inbound.product import StockProduct
|
||||||
@ -42,14 +45,21 @@ class ProductInboundService:
|
|||||||
material = MaterialBase.query.get(base_id)
|
material = MaterialBase.query.get(base_id)
|
||||||
if not material: raise ValueError("物料不存在")
|
if not material: raise ValueError("物料不存在")
|
||||||
|
|
||||||
in_date_val = datetime.utcnow().date()
|
# [核心修改] 处理 production_date,包含时分秒
|
||||||
|
current_time = datetime.now()
|
||||||
|
in_date_val = current_time
|
||||||
|
|
||||||
if data.get('in_date'):
|
if data.get('in_date'):
|
||||||
try:
|
try:
|
||||||
date_str = str(data['in_date'])
|
date_str = str(data['in_date'])
|
||||||
if len(date_str) > 10: date_str = date_str[:10]
|
if len(date_str) > 10:
|
||||||
in_date_val = datetime.strptime(date_str, '%Y-%m-%d').date()
|
in_date_val = datetime.strptime(date_str, '%Y-%m-%d %H:%M:%S')
|
||||||
|
else:
|
||||||
|
d_temp = datetime.strptime(date_str, '%Y-%m-%d')
|
||||||
|
in_date_val = datetime(d_temp.year, d_temp.month, d_temp.day,
|
||||||
|
current_time.hour, current_time.minute, current_time.second)
|
||||||
except:
|
except:
|
||||||
pass
|
in_date_val = current_time
|
||||||
|
|
||||||
in_qty = float(data.get('in_quantity') or 0)
|
in_qty = float(data.get('in_quantity') or 0)
|
||||||
|
|
||||||
@ -65,7 +75,6 @@ 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', [])
|
||||||
@ -78,7 +87,7 @@ class ProductInboundService:
|
|||||||
base_id=material.id,
|
base_id=material.id,
|
||||||
global_print_id=next_global_id,
|
global_print_id=next_global_id,
|
||||||
sku=generated_sku,
|
sku=generated_sku,
|
||||||
production_date=in_date_val,
|
production_date=in_date_val, # 存入 DateTime
|
||||||
barcode=final_barcode,
|
barcode=final_barcode,
|
||||||
serial_number=data.get('serial_number'),
|
serial_number=data.get('serial_number'),
|
||||||
|
|
||||||
@ -100,7 +109,6 @@ class ProductInboundService:
|
|||||||
|
|
||||||
quality_status=data.get('quality_status', '合格'),
|
quality_status=data.get('quality_status', '合格'),
|
||||||
|
|
||||||
# 存为 JSON
|
|
||||||
product_photo=json.dumps(photo_list),
|
product_photo=json.dumps(photo_list),
|
||||||
quality_report_link=json.dumps(quality_list),
|
quality_report_link=json.dumps(quality_list),
|
||||||
inspection_report_link=json.dumps(inspection_list),
|
inspection_report_link=json.dumps(inspection_list),
|
||||||
@ -136,7 +144,6 @@ 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 字段
|
|
||||||
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)
|
||||||
@ -188,9 +195,6 @@ class ProductInboundService:
|
|||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# 获取出库流转历史 (与 Buy 逻辑一致,关联 TransOutbound 表)
|
|
||||||
# ============================================================
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_outbound_history(stock_id):
|
def get_outbound_history(stock_id):
|
||||||
"""获取出库历史"""
|
"""获取出库历史"""
|
||||||
@ -203,7 +207,7 @@ class ProductInboundService:
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# 获取列表 (包含状态筛选与零库存隐藏逻辑)
|
# 获取列表 (修改:按时间倒序排序 + 展示只显示日期)
|
||||||
# ============================================================
|
# ============================================================
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_list(page, limit, keyword=None, statuses=None):
|
def get_list(page, limit, keyword=None, statuses=None):
|
||||||
@ -211,7 +215,6 @@ class ProductInboundService:
|
|||||||
try:
|
try:
|
||||||
query = db.session.query(StockProduct).outerjoin(MaterialBase, StockProduct.base_id == MaterialBase.id)
|
query = db.session.query(StockProduct).outerjoin(MaterialBase, StockProduct.base_id == MaterialBase.id)
|
||||||
|
|
||||||
# 1. 关键词搜索
|
|
||||||
if keyword:
|
if keyword:
|
||||||
query = query.filter(or_(
|
query = query.filter(or_(
|
||||||
MaterialBase.name.ilike(f'%{keyword}%'),
|
MaterialBase.name.ilike(f'%{keyword}%'),
|
||||||
@ -222,11 +225,9 @@ class ProductInboundService:
|
|||||||
StockProduct.sku.ilike(f'%{keyword}%')
|
StockProduct.sku.ilike(f'%{keyword}%')
|
||||||
))
|
))
|
||||||
|
|
||||||
# 2. 状态筛选与零库存隐藏逻辑
|
|
||||||
if not statuses:
|
if not statuses:
|
||||||
statuses = ['在库', '借库']
|
statuses = ['在库', '借库']
|
||||||
|
|
||||||
# 如果筛选包含'已出库',则显示所有数量;否则隐藏 stock_quantity <= 0 的记录
|
|
||||||
if '已出库' in statuses:
|
if '已出库' in statuses:
|
||||||
query = query.filter(StockProduct.status.in_(statuses))
|
query = query.filter(StockProduct.status.in_(statuses))
|
||||||
else:
|
else:
|
||||||
@ -237,7 +238,9 @@ class ProductInboundService:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
pagination = query.order_by(StockProduct.id.desc()).paginate(page=page, per_page=limit, error_out=False)
|
# [核心修改] 按照 production_date (入库日期) 倒序排序
|
||||||
|
pagination = query.order_by(StockProduct.production_date.desc()).paginate(page=page, per_page=limit,
|
||||||
|
error_out=False)
|
||||||
|
|
||||||
current_items = pagination.items
|
current_items = pagination.items
|
||||||
|
|
||||||
@ -252,20 +255,23 @@ class ProductInboundService:
|
|||||||
for item in current_items:
|
for item in current_items:
|
||||||
d = item.to_dict()
|
d = item.to_dict()
|
||||||
|
|
||||||
# 直接使用当前行的库存,不再聚合
|
# [核心修改] 格式化日期
|
||||||
|
date_display = ''
|
||||||
|
if item.production_date:
|
||||||
|
try:
|
||||||
|
date_display = item.production_date.strftime('%Y-%m-%d')
|
||||||
|
except:
|
||||||
|
date_display = str(item.production_date)[:10]
|
||||||
|
d['inbound_date'] = date_display
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
# 兼容前端字段 key
|
|
||||||
d['sum_stock'] = d['qty_stock']
|
d['sum_stock'] = d['qty_stock']
|
||||||
d['sum_available'] = d['qty_available']
|
d['sum_available'] = d['qty_available']
|
||||||
|
|
||||||
# 图片/链接解析
|
|
||||||
d['product_photo'] = parse_img(item.product_photo)
|
d['product_photo'] = parse_img(item.product_photo)
|
||||||
d['quality_report_link'] = parse_img(item.quality_report_link)
|
d['quality_report_link'] = parse_img(item.quality_report_link)
|
||||||
d['inspection_report_link'] = parse_img(item.inspection_report_link)
|
d['inspection_report_link'] = parse_img(item.inspection_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)
|
||||||
|
|||||||
@ -41,7 +41,6 @@ class SemiInboundService:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def handle_inbound(data):
|
def handle_inbound(data):
|
||||||
# 局部导入 Model,解决循环引用
|
|
||||||
from app.models.inbound.semi import StockSemi
|
from app.models.inbound.semi import StockSemi
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -53,16 +52,21 @@ class SemiInboundService:
|
|||||||
if not material:
|
if not material:
|
||||||
raise ValueError(f"ID为 {base_id} 的基础物料不存在")
|
raise ValueError(f"ID为 {base_id} 的基础物料不存在")
|
||||||
|
|
||||||
# 1. 处理入库日期
|
# [核心修改] 处理入库日期(production_date),包含时分秒
|
||||||
in_date_val = datetime.utcnow().date()
|
current_time = datetime.now()
|
||||||
|
in_date_val = current_time
|
||||||
|
|
||||||
if data.get('in_date'):
|
if data.get('in_date'):
|
||||||
try:
|
try:
|
||||||
date_str = str(data['in_date'])
|
date_str = str(data['in_date'])
|
||||||
if len(date_str) > 10:
|
if len(date_str) > 10:
|
||||||
date_str = date_str[:10]
|
in_date_val = datetime.strptime(date_str, '%Y-%m-%d %H:%M:%S')
|
||||||
in_date_val = datetime.strptime(date_str, '%Y-%m-%d').date()
|
else:
|
||||||
|
d_temp = datetime.strptime(date_str, '%Y-%m-%d')
|
||||||
|
in_date_val = datetime(d_temp.year, d_temp.month, d_temp.day,
|
||||||
|
current_time.hour, current_time.minute, current_time.second)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
in_date_val = current_time
|
||||||
|
|
||||||
# 2. 处理生产时间
|
# 2. 处理生产时间
|
||||||
p_start = None
|
p_start = None
|
||||||
@ -102,18 +106,15 @@ class SemiInboundService:
|
|||||||
print("❌ 数据库序列 global_print_seq 不存在,请执行SQL创建!")
|
print("❌ 数据库序列 global_print_seq 不存在,请执行SQL创建!")
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
# 5. 自动生成 SKU
|
|
||||||
generated_sku = str(next_global_id).zfill(10)
|
generated_sku = str(next_global_id).zfill(10)
|
||||||
final_sku = data.get('sku')
|
final_sku = data.get('sku')
|
||||||
if not final_sku:
|
if not final_sku:
|
||||||
final_sku = generated_sku
|
final_sku = generated_sku
|
||||||
|
|
||||||
# 6. 条码逻辑处理
|
|
||||||
final_barcode = data.get('barcode')
|
final_barcode = data.get('barcode')
|
||||||
if not final_barcode:
|
if not final_barcode:
|
||||||
final_barcode = final_sku
|
final_barcode = final_sku
|
||||||
|
|
||||||
# 7. 图片列表转 JSON 字符串处理
|
|
||||||
arrival_list = data.get('arrival_photo', [])
|
arrival_list = data.get('arrival_photo', [])
|
||||||
quality_report_list = data.get('quality_report_link', [])
|
quality_report_list = data.get('quality_report_link', [])
|
||||||
|
|
||||||
@ -125,7 +126,7 @@ class SemiInboundService:
|
|||||||
base_id=material.id,
|
base_id=material.id,
|
||||||
global_print_id=next_global_id,
|
global_print_id=next_global_id,
|
||||||
sku=final_sku,
|
sku=final_sku,
|
||||||
production_date=in_date_val,
|
production_date=in_date_val, # 存入 DateTime
|
||||||
|
|
||||||
serial_number=data.get('serial_number'),
|
serial_number=data.get('serial_number'),
|
||||||
batch_number=data.get('batch_number'),
|
batch_number=data.get('batch_number'),
|
||||||
@ -151,7 +152,6 @@ 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),
|
||||||
|
|
||||||
@ -174,8 +174,6 @@ class SemiInboundService:
|
|||||||
from app.models.inbound.semi import StockSemi
|
from app.models.inbound.semi import StockSemi
|
||||||
|
|
||||||
try:
|
try:
|
||||||
print(f"----- UPDATE SEMI DEBUG: ID={stock_id} -----")
|
|
||||||
|
|
||||||
stock = StockSemi.query.get(stock_id)
|
stock = StockSemi.query.get(stock_id)
|
||||||
if not stock:
|
if not stock:
|
||||||
raise ValueError("记录不存在")
|
raise ValueError("记录不存在")
|
||||||
@ -200,7 +198,6 @@ 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):
|
||||||
@ -211,7 +208,6 @@ class SemiInboundService:
|
|||||||
if isinstance(imgs, list):
|
if isinstance(imgs, list):
|
||||||
stock.quality_report_link = json.dumps(imgs)
|
stock.quality_report_link = json.dumps(imgs)
|
||||||
|
|
||||||
# 时间处理
|
|
||||||
if 'production_start_time' in data:
|
if 'production_start_time' in data:
|
||||||
try:
|
try:
|
||||||
if data['production_start_time']:
|
if data['production_start_time']:
|
||||||
@ -232,7 +228,6 @@ class SemiInboundService:
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# 更新 production_time_range 字符串
|
|
||||||
if 'production_time_range' in data:
|
if 'production_time_range' in data:
|
||||||
raw_range = data['production_time_range']
|
raw_range = data['production_time_range']
|
||||||
if isinstance(raw_range, list):
|
if isinstance(raw_range, list):
|
||||||
@ -269,8 +264,6 @@ class SemiInboundService:
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
print(f"----- UPDATE SEMI FAILED: {str(e)} -----")
|
|
||||||
traceback.print_exc()
|
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -287,9 +280,6 @@ class SemiInboundService:
|
|||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
# [核心修改] 获取关联出库历史 (跟 Buy 保持一致)
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_outbound_history(stock_id):
|
def get_outbound_history(stock_id):
|
||||||
"""获取出库历史"""
|
"""获取出库历史"""
|
||||||
@ -301,9 +291,9 @@ class SemiInboundService:
|
|||||||
except:
|
except:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ============================================================
|
||||||
# [核心修改] 列表查询:支持状态筛选、默认隐藏0库存、去除聚合
|
# 6. 获取列表 (修改:按时间倒序排序 + 展示只显示日期)
|
||||||
# ------------------------------------------------------------------
|
# ============================================================
|
||||||
@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
|
||||||
@ -324,11 +314,9 @@ class SemiInboundService:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# [新增] 状态筛选与零库存隐藏逻辑
|
|
||||||
if not statuses:
|
if not statuses:
|
||||||
statuses = ['在库', '借库']
|
statuses = ['在库', '借库']
|
||||||
|
|
||||||
# 如果筛选包含'已出库',则显示所有数量;否则隐藏 stock_quantity <= 0 的记录
|
|
||||||
if '已出库' in statuses:
|
if '已出库' in statuses:
|
||||||
query = query.filter(StockSemi.status.in_(statuses))
|
query = query.filter(StockSemi.status.in_(statuses))
|
||||||
else:
|
else:
|
||||||
@ -339,7 +327,9 @@ class SemiInboundService:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
pagination = query.order_by(StockSemi.id.desc()).paginate(page=page, per_page=limit, error_out=False)
|
# [核心修改] 按照 production_date (入库日期) 倒序排序
|
||||||
|
pagination = query.order_by(StockSemi.production_date.desc()).paginate(page=page, per_page=limit,
|
||||||
|
error_out=False)
|
||||||
|
|
||||||
current_items = pagination.items
|
current_items = pagination.items
|
||||||
|
|
||||||
@ -354,19 +344,22 @@ class SemiInboundService:
|
|||||||
for item in current_items:
|
for item in current_items:
|
||||||
d = item.to_dict()
|
d = item.to_dict()
|
||||||
|
|
||||||
# 直接使用当前行的库存,不再聚合
|
# [核心修改] 格式化展示日期,覆盖 to_dict 的默认行为
|
||||||
|
date_display = ''
|
||||||
|
if item.production_date:
|
||||||
|
try:
|
||||||
|
date_display = item.production_date.strftime('%Y-%m-%d')
|
||||||
|
except:
|
||||||
|
date_display = str(item.production_date)[:10]
|
||||||
|
d['inbound_date'] = date_display
|
||||||
|
|
||||||
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']
|
||||||
|
|
||||||
# 图片解析
|
|
||||||
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)
|
||||||
|
|||||||
18
inventory-web/src/api/inbound/inbound_summary.ts
Normal file
18
inventory-web/src/api/inbound/inbound_summary.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
export interface InboundSummaryQuery {
|
||||||
|
page: number
|
||||||
|
per_page: number
|
||||||
|
keyword?: string
|
||||||
|
start_date?: string
|
||||||
|
end_date?: string
|
||||||
|
source_type?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getInboundSummaryList(params: InboundSummaryQuery) {
|
||||||
|
return request({
|
||||||
|
url: '/v1/inbound/summary/list',
|
||||||
|
method: 'get',
|
||||||
|
params
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -67,6 +67,13 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
component: () => import('@/views/stock/inbound/product.vue'),
|
component: () => import('@/views/stock/inbound/product.vue'),
|
||||||
meta: { title: '成品' }
|
meta: { title: '成品' }
|
||||||
},
|
},
|
||||||
|
// ★ [新增] 入库记录整合
|
||||||
|
{
|
||||||
|
path: 'summary',
|
||||||
|
name: 'InventorySummary',
|
||||||
|
component: () => import('@/views/stock/inbound/inbound_summary.vue'),
|
||||||
|
meta: { title: '入库记录' }
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'service',
|
path: 'service',
|
||||||
name: 'InventoryService',
|
name: 'InventoryService',
|
||||||
|
|||||||
167
inventory-web/src/views/stock/inbound/inbound_summary.vue
Normal file
167
inventory-web/src/views/stock/inbound/inbound_summary.vue
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<div class="filter-container">
|
||||||
|
<el-input
|
||||||
|
v-model="listQuery.keyword"
|
||||||
|
placeholder="SKU / 名称 / 规格 / 批次号 / 来源"
|
||||||
|
style="width: 300px;"
|
||||||
|
class="filter-item"
|
||||||
|
clearable
|
||||||
|
@keyup.enter="handleFilter"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<el-select v-model="listQuery.source_type" placeholder="全部来源" clearable class="filter-item" style="width: 140px; margin-left: 10px;">
|
||||||
|
<el-option label="采购入库" value="buy" />
|
||||||
|
<el-option label="半成品生产" value="semi" />
|
||||||
|
<el-option label="成品完工" value="product" />
|
||||||
|
</el-select>
|
||||||
|
|
||||||
|
<el-date-picker
|
||||||
|
v-model="listQuery.dateRange"
|
||||||
|
type="daterange"
|
||||||
|
range-separator="至"
|
||||||
|
start-placeholder="开始日期"
|
||||||
|
end-placeholder="结束日期"
|
||||||
|
class="filter-item"
|
||||||
|
style="margin-left: 10px;"
|
||||||
|
value-format="YYYY-MM-DD"
|
||||||
|
@change="handleFilter"
|
||||||
|
/>
|
||||||
|
<el-button type="primary" class="filter-item" style="margin-left: 10px;" @click="handleFilter">查询</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-table
|
||||||
|
:data="list"
|
||||||
|
v-loading="loading"
|
||||||
|
border
|
||||||
|
stripe
|
||||||
|
style="width: 100%; margin-top: 20px;"
|
||||||
|
:header-cell-style="{ background: '#f5f7fa', color: '#606266' }"
|
||||||
|
>
|
||||||
|
<el-table-column prop="sku" label="SKU" min-width="140" fixed sortable show-overflow-tooltip />
|
||||||
|
|
||||||
|
<el-table-column prop="name" label="物品名称" min-width="160" show-overflow-tooltip>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span style="font-weight: 500;">{{ row.name }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column prop="spec_model" label="规格型号" min-width="140" show-overflow-tooltip />
|
||||||
|
|
||||||
|
<el-table-column label="分类" min-width="120" show-overflow-tooltip>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span>{{ row.category }}</span>
|
||||||
|
<span v-if="row.category && row.material_type"> / </span>
|
||||||
|
<span>{{ row.material_type }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column prop="type_label" label="入库来源" width="110" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="getSourceTag(row.source_type)" effect="plain">
|
||||||
|
{{ row.type_label }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column prop="inbound_date" label="入库/生产日期" width="120" align="center" sortable />
|
||||||
|
|
||||||
|
<el-table-column label="入库数量" width="110" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span style="font-weight: bold; color: #409EFF;">{{ row.quantity }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column prop="batch_number" label="批次/序列号" min-width="140" show-overflow-tooltip />
|
||||||
|
|
||||||
|
<el-table-column prop="source_info" label="供应商/负责人" min-width="140" show-overflow-tooltip />
|
||||||
|
|
||||||
|
<el-table-column prop="status" label="当前状态" width="100" align="center" fixed="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag size="small" :type="getStatusTag(row.status)">{{ row.status }}</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<div style="margin-top: 20px; display: flex; justify-content: flex-end;">
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="listQuery.page"
|
||||||
|
v-model:page-size="listQuery.per_page"
|
||||||
|
:total="total"
|
||||||
|
:page-sizes="[10, 20, 50, 100, 200]"
|
||||||
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
|
@size-change="handleFilter"
|
||||||
|
@current-change="fetchData"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, onMounted } from 'vue'
|
||||||
|
import { getInboundSummaryList } from '@/api/inbound/inbound_summary'
|
||||||
|
|
||||||
|
const list = ref([])
|
||||||
|
const total = ref(0)
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
const listQuery = reactive({
|
||||||
|
page: 1,
|
||||||
|
per_page: 20, // 默认每页20
|
||||||
|
keyword: '',
|
||||||
|
source_type: '',
|
||||||
|
dateRange: null as any
|
||||||
|
})
|
||||||
|
|
||||||
|
const fetchData = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
page: listQuery.page,
|
||||||
|
per_page: listQuery.per_page,
|
||||||
|
keyword: listQuery.keyword,
|
||||||
|
source_type: listQuery.source_type,
|
||||||
|
start_date: listQuery.dateRange ? listQuery.dateRange[0] : null,
|
||||||
|
end_date: listQuery.dateRange ? listQuery.dateRange[1] : null
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await getInboundSummaryList(params)
|
||||||
|
if (res.data) {
|
||||||
|
list.value = res.data.items || []
|
||||||
|
total.value = res.data.total || 0
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取入库记录失败', error)
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询操作重置页码
|
||||||
|
const handleFilter = () => {
|
||||||
|
listQuery.page = 1
|
||||||
|
fetchData()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 来源类型的 Tag 颜色
|
||||||
|
const getSourceTag = (type: string) => {
|
||||||
|
if (type === 'buy') return 'success' // 绿色
|
||||||
|
if (type === 'semi') return 'warning' // 橙色
|
||||||
|
if (type === 'product') return 'primary' // 蓝色
|
||||||
|
return 'info'
|
||||||
|
}
|
||||||
|
|
||||||
|
// [新增] 状态的 Tag 颜色逻辑
|
||||||
|
const getStatusTag = (status: string) => {
|
||||||
|
if (!status) return 'info'
|
||||||
|
if (status.includes('已出库')) return 'info' // 灰色
|
||||||
|
if (status.includes('部分')) return 'warning' // 橙色
|
||||||
|
if (status.includes('不合格') || status.includes('异常')) return 'danger' // 红色
|
||||||
|
return 'success' // 默认(如:合格、在库)为绿色
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchData()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
Reference in New Issue
Block a user