增加入库记录页面,同时修正三组入库的时间问题

This commit is contained in:
dxc
2026-02-05 14:30:11 +08:00
parent 10e53cab23
commit 0bc47d306d
12 changed files with 511 additions and 85 deletions

View File

@ -4,6 +4,8 @@ from .semi import inbound_semi_bp
from .base import inbound_base_bp
# 导入 product
from .product import inbound_product_bp
# ★ [新增] 导入 summary
from .inbound_summary import bp as inbound_summary_bp
inbound_bp = Blueprint('inbound', __name__)
@ -12,3 +14,6 @@ inbound_bp.register_blueprint(inbound_semi_bp, url_prefix='/semi')
inbound_bp.register_blueprint(inbound_base_bp, url_prefix='/base')
# 挂载 product前缀改为 /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')

View 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

View File

@ -15,7 +15,7 @@ class StockBuy(db.Model):
# 身份标识
sku = db.Column(db.String(100))
in_date = db.Column(db.Date)
in_date = db.Column(db.DateTime)
barcode = db.Column(db.String(100))
serial_number = db.Column(db.String(100))
batch_number = db.Column(db.String(100))

View File

@ -15,7 +15,7 @@ class StockProduct(db.Model):
# 身份标识
sku = db.Column(db.String(100))
production_date = db.Column(db.Date)
production_date = db.Column(db.DateTime)
barcode = db.Column(db.String(100))
serial_number = db.Column(db.String(100))

View File

@ -13,7 +13,7 @@ class StockSemi(db.Model):
base_id = db.Column(db.Integer, db.ForeignKey('material_base.id'), nullable=False)
sku = db.Column(db.String(100))
production_date = db.Column(db.Date)
production_date = db.Column(db.DateTime)
barcode = db.Column(db.String(100))
serial_number = db.Column(db.String(100))
batch_number = db.Column(db.String(100))

View File

@ -40,7 +40,7 @@ class BuyInboundService:
return []
# ============================================================
# 2. 新增入库逻辑
# 2. 新增入库逻辑 (修改:精确到时间)
# ============================================================
@staticmethod
def handle_inbound(data):
@ -51,14 +51,24 @@ class BuyInboundService:
material = MaterialBase.query.get(base_id)
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'):
try:
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:
pass
# 解析失败则使用当前时间作为兜底
in_date_val = current_time
in_qty = float(data.get('in_quantity') or 0)
u_price = float(data.get('unit_price') or 0)
@ -80,10 +90,10 @@ class BuyInboundService:
global_print_id=next_global_id,
sku=generated_sku,
barcode=final_barcode,
in_date=in_date_val,
in_date=in_date_val, # 存入 DateTime 对象
serial_number=data.get('serial_number'),
batch_number=data.get('batch_number'),
status=data.get('status', '在库'), # 默认在库
status=data.get('status', '在库'),
in_quantity=in_qty,
stock_quantity=in_qty,
available_quantity=in_qty,
@ -185,7 +195,7 @@ class BuyInboundService:
return []
# ============================================================
# 6. 获取列表 (核心逻辑修改)
# 6. 获取列表 (修改:按时间倒序排序 + 展示只显示日期)
# ============================================================
@staticmethod
def get_list(page, limit, keyword=None, statuses=None):
@ -196,7 +206,7 @@ class BuyInboundService:
try:
query = db.session.query(StockBuy).outerjoin(MaterialBase, StockBuy.base_id == MaterialBase.id)
# 1. 关键词搜索 (覆盖所有关键字段)
# 1. 关键词搜索
if keyword:
kw = f'%{keyword}%'
query = query.filter(
@ -210,24 +220,13 @@ class BuyInboundService:
)
)
# 2. 状态筛选与零库存隐藏逻辑
# 用户要求:
# - 默认显示:'在库', '借库'。
# - 零库存规则库存为0时不在页面显示除非筛选了'已出库')。
# 2. 状态筛选
if not statuses:
# 默认情况:只查 '在库' 和 '借库'
statuses = ['在库', '借库']
# 构建筛选条件
# 如果筛选条件中 包含 '已出库',则允许显示 stock_quantity >= 0 (即显示所有)
# 如果筛选条件中 不包含 '已出库',则强制要求 stock_quantity > 0 (隐藏零库存)
if '已出库' in statuses:
# 用户想看已出库的,直接按状态查,不做数量限制
query = query.filter(StockBuy.status.in_(statuses))
else:
# 用户不想看已出库的,按状态查 AND 数量必须 > 0
query = query.filter(
and_(
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
def parse_img(json_str):
@ -247,10 +247,17 @@ class BuyInboundService:
items = []
for item in current_items:
# 获取单行数据,不再进行聚合计算
qty_stock = float(item.stock_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 = {
'id': item.id,
'base_id': item.base_id,
@ -261,7 +268,7 @@ class BuyInboundService:
'material_type': item.material.material_type if item.material else '',
'sku': item.sku,
'inbound_date': str(item.in_date) if item.in_date else '',
'inbound_date': date_display, # 前端展示用的日期字符串
'barcode': item.barcode,
'serial_number': item.serial_number,
'batch_number': item.batch_number,
@ -273,8 +280,6 @@ class BuyInboundService:
'qty_stock': qty_stock,
'qty_available': qty_avail,
# 解除挂钩:不再返回所有批次的总和,直接返回当前批次的数量
# 为了兼容前端字段名,这里直接用当前行数量填充
'sum_stock': qty_stock,
'sum_available': qty_avail,

View File

@ -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

View File

@ -32,6 +32,9 @@ class ProductInboundService:
traceback.print_exc()
return []
# ============================================================
# 2. 新增入库逻辑 (修改:精确到时间)
# ============================================================
@staticmethod
def handle_inbound(data):
from app.models.inbound.product import StockProduct
@ -42,14 +45,21 @@ class ProductInboundService:
material = MaterialBase.query.get(base_id)
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'):
try:
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:
pass
in_date_val = current_time
in_qty = float(data.get('in_quantity') or 0)
@ -65,7 +75,6 @@ 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', [])
@ -78,7 +87,7 @@ class ProductInboundService:
base_id=material.id,
global_print_id=next_global_id,
sku=generated_sku,
production_date=in_date_val,
production_date=in_date_val, # 存入 DateTime
barcode=final_barcode,
serial_number=data.get('serial_number'),
@ -100,7 +109,6 @@ class ProductInboundService:
quality_status=data.get('quality_status', '合格'),
# 存为 JSON
product_photo=json.dumps(photo_list),
quality_report_link=json.dumps(quality_list),
inspection_report_link=json.dumps(inspection_list),
@ -136,7 +144,6 @@ class ProductInboundService:
for f in fields:
if f in data: setattr(stock, f, data[f])
# 更新 JSON 字段
if 'product_photo' in data:
imgs = data['product_photo']
if isinstance(imgs, list): stock.product_photo = json.dumps(imgs)
@ -188,9 +195,6 @@ class ProductInboundService:
db.session.rollback()
raise e
# ============================================================
# 获取出库流转历史 (与 Buy 逻辑一致,关联 TransOutbound 表)
# ============================================================
@staticmethod
def get_outbound_history(stock_id):
"""获取出库历史"""
@ -203,7 +207,7 @@ class ProductInboundService:
return []
# ============================================================
# 获取列表 (包含状态筛选与零库存隐藏逻辑)
# 获取列表 (修改:按时间倒序排序 + 展示只显示日期)
# ============================================================
@staticmethod
def get_list(page, limit, keyword=None, statuses=None):
@ -211,7 +215,6 @@ class ProductInboundService:
try:
query = db.session.query(StockProduct).outerjoin(MaterialBase, StockProduct.base_id == MaterialBase.id)
# 1. 关键词搜索
if keyword:
query = query.filter(or_(
MaterialBase.name.ilike(f'%{keyword}%'),
@ -222,11 +225,9 @@ class ProductInboundService:
StockProduct.sku.ilike(f'%{keyword}%')
))
# 2. 状态筛选与零库存隐藏逻辑
if not statuses:
statuses = ['在库', '借库']
# 如果筛选包含'已出库',则显示所有数量;否则隐藏 stock_quantity <= 0 的记录
if '已出库' in statuses:
query = query.filter(StockProduct.status.in_(statuses))
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
@ -252,20 +255,23 @@ class ProductInboundService:
for item in current_items:
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_available'] = float(item.available_quantity or 0)
# 兼容前端字段 key
d['sum_stock'] = d['qty_stock']
d['sum_available'] = d['qty_available']
# 图片/链接解析
d['product_photo'] = parse_img(item.product_photo)
d['quality_report_link'] = parse_img(item.quality_report_link)
d['inspection_report_link'] = parse_img(item.inspection_report_link)
# 打印ID
d['global_print_id'] = item.global_print_id
items.append(d)

View File

@ -41,7 +41,6 @@ class SemiInboundService:
@staticmethod
def handle_inbound(data):
# 局部导入 Model解决循环引用
from app.models.inbound.semi import StockSemi
try:
@ -53,16 +52,21 @@ class SemiInboundService:
if not material:
raise ValueError(f"ID为 {base_id} 的基础物料不存在")
# 1. 处理入库日期
in_date_val = datetime.utcnow().date()
# [核心修改] 处理入库日期(production_date),包含时分秒
current_time = datetime.now()
in_date_val = current_time
if data.get('in_date'):
try:
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()
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 ValueError:
pass
in_date_val = current_time
# 2. 处理生产时间
p_start = None
@ -102,18 +106,15 @@ class SemiInboundService:
print("❌ 数据库序列 global_print_seq 不存在请执行SQL创建")
raise e
# 5. 自动生成 SKU
generated_sku = str(next_global_id).zfill(10)
final_sku = data.get('sku')
if not final_sku:
final_sku = generated_sku
# 6. 条码逻辑处理
final_barcode = data.get('barcode')
if not final_barcode:
final_barcode = final_sku
# 7. 图片列表转 JSON 字符串处理
arrival_list = data.get('arrival_photo', [])
quality_report_list = data.get('quality_report_link', [])
@ -125,7 +126,7 @@ class SemiInboundService:
base_id=material.id,
global_print_id=next_global_id,
sku=final_sku,
production_date=in_date_val,
production_date=in_date_val, # 存入 DateTime
serial_number=data.get('serial_number'),
batch_number=data.get('batch_number'),
@ -151,7 +152,6 @@ 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),
@ -174,8 +174,6 @@ class SemiInboundService:
from app.models.inbound.semi import StockSemi
try:
print(f"----- UPDATE SEMI DEBUG: ID={stock_id} -----")
stock = StockSemi.query.get(stock_id)
if not stock:
raise ValueError("记录不存在")
@ -200,7 +198,6 @@ 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):
@ -211,7 +208,6 @@ class SemiInboundService:
if isinstance(imgs, list):
stock.quality_report_link = json.dumps(imgs)
# 时间处理
if 'production_start_time' in data:
try:
if data['production_start_time']:
@ -232,7 +228,6 @@ class SemiInboundService:
except:
pass
# 更新 production_time_range 字符串
if 'production_time_range' in data:
raw_range = data['production_time_range']
if isinstance(raw_range, list):
@ -269,8 +264,6 @@ class SemiInboundService:
except Exception as e:
db.session.rollback()
print(f"----- UPDATE SEMI FAILED: {str(e)} -----")
traceback.print_exc()
raise e
@staticmethod
@ -287,9 +280,6 @@ class SemiInboundService:
db.session.rollback()
raise e
# ------------------------------------------------------------------
# [核心修改] 获取关联出库历史 (跟 Buy 保持一致)
# ------------------------------------------------------------------
@staticmethod
def get_outbound_history(stock_id):
"""获取出库历史"""
@ -301,9 +291,9 @@ class SemiInboundService:
except:
return []
# ------------------------------------------------------------------
# [核心修改] 列表查询支持状态筛选、默认隐藏0库存、去除聚合
# ------------------------------------------------------------------
# ============================================================
# 6. 获取列表 (修改:按时间倒序排序 + 展示只显示日期)
# ============================================================
@staticmethod
def get_list(page, limit, keyword=None, statuses=None):
from app.models.inbound.semi import StockSemi
@ -324,11 +314,9 @@ class SemiInboundService:
)
)
# [新增] 状态筛选与零库存隐藏逻辑
if not statuses:
statuses = ['在库', '借库']
# 如果筛选包含'已出库',则显示所有数量;否则隐藏 stock_quantity <= 0 的记录
if '已出库' in statuses:
query = query.filter(StockSemi.status.in_(statuses))
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
@ -354,19 +344,22 @@ class SemiInboundService:
for item in current_items:
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_available'] = float(item.available_quantity or 0)
# 兼容前端字段名
d['sum_stock'] = d['qty_stock']
d['sum_available'] = d['qty_available']
# 图片解析
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)

View 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
})
}

View File

@ -67,6 +67,13 @@ const routes: Array<RouteRecordRaw> = [
component: () => import('@/views/stock/inbound/product.vue'),
meta: { title: '成品' }
},
// ★ [新增] 入库记录整合
{
path: 'summary',
name: 'InventorySummary',
component: () => import('@/views/stock/inbound/inbound_summary.vue'),
meta: { title: '入库记录' }
},
{
path: 'service',
name: 'InventoryService',

View 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>