feat: 打通采购申请与入库的按单入库链路
后端变更: - PurchaseRequest 新增 base_id 硬关联 MaterialBase,StockBuy 新增 request_id 关联采购单 - handle_inbound 支持 request_id 入参,入库后自动反写采购单状态为已完成 - 新增 get_approved_requests 接口,返回已审批未入库采购单列表(含物料信息+历史数据回退匹配) - create_purchase_request 新增 name+spec_model 自动匹配 MaterialBase - 新增 SQL 和 Alembic 数据库迁移脚本 前端变更: - 入库表单新增"从采购单导入"弹窗,支持搜索/选中已审批采购单并一键填充物料和商务信息 - 单价/总价交叉推算,缺项自动补全 - 选中行蓝色高亮+点击整行选中,行级交互优化
This commit is contained in:
@ -208,7 +208,30 @@ def auto_fill_purchase():
|
|||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------
|
# --------------------------------------------------------
|
||||||
# 7. 物料基础信息搜索(分页)
|
# 7. 已审批且未入库的采购单列表(供库管按单入库使用)
|
||||||
|
# GET /api/v1/purchase/approved-unstocked?page=1&keyword=xxx
|
||||||
|
# --------------------------------------------------------
|
||||||
|
@purchase_bp.route('/approved-unstocked', methods=['GET'])
|
||||||
|
@jwt_required()
|
||||||
|
def get_approved_unstocked_requests():
|
||||||
|
"""获取已审批通过且未入库的采购申请列表"""
|
||||||
|
try:
|
||||||
|
page = int(request.args.get('page', 1))
|
||||||
|
per_page = int(request.args.get('limit', 20))
|
||||||
|
keyword = request.args.get('keyword', '').strip() or None
|
||||||
|
|
||||||
|
result = PurchaseService.get_approved_requests(
|
||||||
|
page=page, per_page=per_page, keyword=keyword
|
||||||
|
)
|
||||||
|
|
||||||
|
return jsonify({'code': 200, 'msg': '获取成功', 'data': result}), 200
|
||||||
|
except Exception as e:
|
||||||
|
traceback.print_exc()
|
||||||
|
return jsonify({'code': 500, 'msg': f'获取失败: {str(e)}'}), 500
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------
|
||||||
|
# 8. 物料基础信息搜索(分页)
|
||||||
# GET /api/v1/purchase/search-material?keyword=xxx&page=1
|
# GET /api/v1/purchase/search-material?keyword=xxx&page=1
|
||||||
# --------------------------------------------------------
|
# --------------------------------------------------------
|
||||||
@purchase_bp.route('/search-material', methods=['GET'])
|
@purchase_bp.route('/search-material', methods=['GET'])
|
||||||
|
|||||||
@ -4,6 +4,7 @@ from pgvector.sqlalchemy import Vector
|
|||||||
import json
|
import json
|
||||||
# 显式导入 MaterialBase 以防 relationship 找不到引用
|
# 显式导入 MaterialBase 以防 relationship 找不到引用
|
||||||
from app.models.base import MaterialBase
|
from app.models.base import MaterialBase
|
||||||
|
from app.models.purchase import PurchaseRequest
|
||||||
|
|
||||||
|
|
||||||
class StockBuy(db.Model):
|
class StockBuy(db.Model):
|
||||||
@ -15,6 +16,8 @@ class StockBuy(db.Model):
|
|||||||
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
base_id = db.Column(db.Integer, db.ForeignKey('material_base.id'), nullable=False, index=True) # ★ 批量 IN 查询高频列
|
base_id = db.Column(db.Integer, db.ForeignKey('material_base.id'), nullable=False, index=True) # ★ 批量 IN 查询高频列
|
||||||
|
# [新增] 关联采购申请单,打通"按单入库"链路
|
||||||
|
request_id = db.Column(db.Integer, db.ForeignKey('purchase_request.id'), index=True, comment='关联采购申请单ID')
|
||||||
|
|
||||||
# 身份标识
|
# 身份标识
|
||||||
sku = db.Column(db.String(100), index=True) # ★ 条码/SKU 快速定位
|
sku = db.Column(db.String(100), index=True) # ★ 条码/SKU 快速定位
|
||||||
@ -61,6 +64,7 @@ class StockBuy(db.Model):
|
|||||||
|
|
||||||
# 关系定义
|
# 关系定义
|
||||||
base = db.relationship('MaterialBase', back_populates='stock_buys')
|
base = db.relationship('MaterialBase', back_populates='stock_buys')
|
||||||
|
purchase_request = db.relationship('PurchaseRequest', back_populates='stock_buys')
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
# 辅助解析函数
|
# 辅助解析函数
|
||||||
@ -77,6 +81,9 @@ class StockBuy(db.Model):
|
|||||||
return {
|
return {
|
||||||
'id': self.id,
|
'id': self.id,
|
||||||
'base_id': self.base_id,
|
'base_id': self.base_id,
|
||||||
|
'request_id': self.request_id,
|
||||||
|
# [新增] 采购申请单号(便于前端展示)
|
||||||
|
'request_no': self.purchase_request.request_no if self.purchase_request else '',
|
||||||
|
|
||||||
# [修改] 增加公司名称
|
# [修改] 增加公司名称
|
||||||
'company_name': self.base.company_name if self.base else '',
|
'company_name': self.base.company_name if self.base else '',
|
||||||
|
|||||||
@ -11,6 +11,8 @@ class PurchaseRequest(db.Model):
|
|||||||
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
request_no = db.Column(db.String(100), unique=True, nullable=False, index=True)
|
request_no = db.Column(db.String(100), unique=True, nullable=False, index=True)
|
||||||
|
# [新增] 硬关联基础物料表,打通"按单入库"链路
|
||||||
|
base_id = db.Column(db.Integer, db.ForeignKey('material_base.id'), index=True, comment='关联基础物料ID')
|
||||||
name = db.Column(db.String(255), nullable=False, comment='采购名称')
|
name = db.Column(db.String(255), nullable=False, comment='采购名称')
|
||||||
spec_model = db.Column(db.String(255), comment='规格型号')
|
spec_model = db.Column(db.String(255), comment='规格型号')
|
||||||
quantity = db.Column(db.Numeric(19, 4), nullable=False, comment='采购数量')
|
quantity = db.Column(db.Numeric(19, 4), nullable=False, comment='采购数量')
|
||||||
@ -23,6 +25,8 @@ class PurchaseRequest(db.Model):
|
|||||||
status = db.Column(db.Integer, default=0, nullable=False)
|
status = db.Column(db.Integer, default=0, nullable=False)
|
||||||
requester_id = db.Column(db.Integer, nullable=False, index=True)
|
requester_id = db.Column(db.Integer, nullable=False, index=True)
|
||||||
approver_id = db.Column(db.Integer, index=True)
|
approver_id = db.Column(db.Integer, index=True)
|
||||||
|
# [新增] 反向关联:该采购申请对应的入库记录
|
||||||
|
stock_buys = db.relationship('StockBuy', back_populates='purchase_request', lazy='dynamic')
|
||||||
approved_at = db.Column(db.DateTime)
|
approved_at = db.Column(db.DateTime)
|
||||||
reject_reason = db.Column(db.Text)
|
reject_reason = db.Column(db.Text)
|
||||||
created_at = db.Column(db.DateTime, default=beijing_time, nullable=False)
|
created_at = db.Column(db.DateTime, default=beijing_time, nullable=False)
|
||||||
@ -64,6 +68,7 @@ class PurchaseRequest(db.Model):
|
|||||||
return {
|
return {
|
||||||
'id': self.id,
|
'id': self.id,
|
||||||
'request_no': self.request_no,
|
'request_no': self.request_no,
|
||||||
|
'base_id': self.base_id,
|
||||||
'name': self.name,
|
'name': self.name,
|
||||||
'spec_model': self.spec_model or '',
|
'spec_model': self.spec_model or '',
|
||||||
'quantity': float(self.quantity) if self.quantity else 0,
|
'quantity': float(self.quantity) if self.quantity else 0,
|
||||||
|
|||||||
@ -165,6 +165,9 @@ class BuyInboundService:
|
|||||||
generated_sku = str(next_global_id).zfill(10) if next_global_id else datetime.now().strftime('%Y%m%d%H%M%S')
|
generated_sku = str(next_global_id).zfill(10) if next_global_id else datetime.now().strftime('%Y%m%d%H%M%S')
|
||||||
final_barcode = data.get('barcode') or generated_sku
|
final_barcode = data.get('barcode') or generated_sku
|
||||||
|
|
||||||
|
# [新增] 按单入库:关联采购申请单
|
||||||
|
request_id = data.get('request_id')
|
||||||
|
|
||||||
new_stock = StockBuy(
|
new_stock = StockBuy(
|
||||||
base_id=material.id, global_print_id=next_global_id, sku=generated_sku, barcode=final_barcode,
|
base_id=material.id, global_print_id=next_global_id, sku=generated_sku, barcode=final_barcode,
|
||||||
in_date=in_date_val, serial_number=data.get('serial_number'), batch_number=data.get('batch_number'),
|
in_date=in_date_val, serial_number=data.get('serial_number'), batch_number=data.get('batch_number'),
|
||||||
@ -172,6 +175,9 @@ class BuyInboundService:
|
|||||||
inspection_status=data.get('inspection_status', '未检'),
|
inspection_status=data.get('inspection_status', '未检'),
|
||||||
warehouse_location=data.get('warehouse_location'),
|
warehouse_location=data.get('warehouse_location'),
|
||||||
|
|
||||||
|
# [新增] 按单入库关联
|
||||||
|
request_id=request_id,
|
||||||
|
|
||||||
# 价格信息
|
# 价格信息
|
||||||
pre_tax_unit_price=u_price,
|
pre_tax_unit_price=u_price,
|
||||||
post_tax_unit_price=post_tax_price,
|
post_tax_unit_price=post_tax_price,
|
||||||
@ -189,6 +195,15 @@ class BuyInboundService:
|
|||||||
db.session.add(new_stock)
|
db.session.add(new_stock)
|
||||||
db.session.flush() # 获取 new_stock.id
|
db.session.flush() # 获取 new_stock.id
|
||||||
|
|
||||||
|
# [新增] 按单入库:反写采购申请单状态为"已完成"
|
||||||
|
if request_id:
|
||||||
|
from app.models.purchase import PurchaseRequest
|
||||||
|
purchase_req = db.session.get(PurchaseRequest, request_id)
|
||||||
|
if purchase_req:
|
||||||
|
if purchase_req.status != 1:
|
||||||
|
raise ValueError(f"采购单【{purchase_req.request_no}】当前状态不允许入库,请确认审批状态")
|
||||||
|
purchase_req.status = 3 # 3 = 已完成/已入库
|
||||||
|
|
||||||
# 先提交主事务(入库单必须落盘),图片向量异步后台提取
|
# 先提交主事务(入库单必须落盘),图片向量异步后台提取
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
|||||||
@ -45,7 +45,7 @@ class PurchaseService:
|
|||||||
"""
|
"""
|
||||||
创建采购申请
|
创建采购申请
|
||||||
data 包含: name, spec_model, quantity, purchase_date, supplier_link, remark, images,
|
data 包含: name, spec_model, quantity, purchase_date, supplier_link, remark, images,
|
||||||
unit_price, total_price, approver_id
|
unit_price, total_price, approver_id, base_id (可选)
|
||||||
"""
|
"""
|
||||||
request_no = PurchaseService.generate_request_no()
|
request_no = PurchaseService.generate_request_no()
|
||||||
|
|
||||||
@ -55,8 +55,21 @@ class PurchaseService:
|
|||||||
elif isinstance(purchase_date, datetime):
|
elif isinstance(purchase_date, datetime):
|
||||||
purchase_date = purchase_date.date()
|
purchase_date = purchase_date.date()
|
||||||
|
|
||||||
|
# [新增] 自动匹配/关联基础物料
|
||||||
|
base_id = data.get('base_id')
|
||||||
|
if not base_id and data.get('name'):
|
||||||
|
# 尝试通过 name + spec_model 精确匹配 MaterialBase
|
||||||
|
material = MaterialBase.query.filter(
|
||||||
|
MaterialBase.name == data['name'],
|
||||||
|
MaterialBase.spec_model == data.get('spec_model', ''),
|
||||||
|
MaterialBase.is_enabled == True
|
||||||
|
).first()
|
||||||
|
if material:
|
||||||
|
base_id = material.id
|
||||||
|
|
||||||
purchase = PurchaseRequest(
|
purchase = PurchaseRequest(
|
||||||
request_no=request_no,
|
request_no=request_no,
|
||||||
|
base_id=base_id, # [新增]
|
||||||
name=data['name'],
|
name=data['name'],
|
||||||
spec_model=data.get('spec_model', ''),
|
spec_model=data.get('spec_model', ''),
|
||||||
quantity=float(data['quantity']),
|
quantity=float(data['quantity']),
|
||||||
@ -138,6 +151,91 @@ class PurchaseService:
|
|||||||
purchase = db.session.get(PurchaseRequest, purchase_id)
|
purchase = db.session.get(PurchaseRequest, purchase_id)
|
||||||
return purchase.to_dict() if purchase else None
|
return purchase.to_dict() if purchase else None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_approved_requests(page=1, per_page=20, keyword=None):
|
||||||
|
"""
|
||||||
|
获取已审批通过且未入库的采购申请列表(专供库管按单入库使用)
|
||||||
|
|
||||||
|
筛选条件:
|
||||||
|
- status == 1(已审批通过)
|
||||||
|
- 尚未被任何 StockBuy 关联(request_id 未被引用)
|
||||||
|
|
||||||
|
返回字段包含: 采购申请信息 + MaterialBase 基础物料信息
|
||||||
|
"""
|
||||||
|
from app.models.inbound.buy import StockBuy
|
||||||
|
|
||||||
|
# 子查询:所有已被入库引用的 request_id(去重)
|
||||||
|
stocked_ids = db.session.query(StockBuy.request_id).filter(
|
||||||
|
StockBuy.request_id.isnot(None)
|
||||||
|
).distinct().subquery()
|
||||||
|
|
||||||
|
# 主查询:已通过 且 不在已入库集合中
|
||||||
|
query = db.session.query(PurchaseRequest).filter(
|
||||||
|
PurchaseRequest.status == 1
|
||||||
|
).filter(
|
||||||
|
~PurchaseRequest.id.in_(stocked_ids)
|
||||||
|
)
|
||||||
|
|
||||||
|
# 可选关键词搜索:采购单号 / 名称 / 规格
|
||||||
|
if keyword:
|
||||||
|
k = f'%{keyword.strip()}%'
|
||||||
|
query = query.filter(
|
||||||
|
PurchaseRequest.request_no.ilike(k) |
|
||||||
|
PurchaseRequest.name.ilike(k) |
|
||||||
|
PurchaseRequest.spec_model.ilike(k)
|
||||||
|
)
|
||||||
|
|
||||||
|
query = query.order_by(PurchaseRequest.approved_at.desc().nullslast(),
|
||||||
|
PurchaseRequest.created_at.desc())
|
||||||
|
|
||||||
|
pagination = query.paginate(page=page, per_page=per_page, error_out=False)
|
||||||
|
|
||||||
|
items = []
|
||||||
|
for p in pagination.items:
|
||||||
|
item = p.to_dict()
|
||||||
|
material = None
|
||||||
|
|
||||||
|
# 附加物料基础信息
|
||||||
|
if p.base_id:
|
||||||
|
# 优先走 base_id 硬关联
|
||||||
|
material = db.session.get(MaterialBase, p.base_id)
|
||||||
|
|
||||||
|
# 回退匹配:历史采购单没有 base_id,通过 name + spec_model 模糊匹配
|
||||||
|
if not material and p.name:
|
||||||
|
material = MaterialBase.query.filter(
|
||||||
|
MaterialBase.name == p.name,
|
||||||
|
MaterialBase.spec_model == (p.spec_model or ''),
|
||||||
|
MaterialBase.is_enabled == True
|
||||||
|
).first()
|
||||||
|
# 如果精确匹配失败,仅按 name 模糊匹配(取最新一条)
|
||||||
|
if not material:
|
||||||
|
material = MaterialBase.query.filter(
|
||||||
|
MaterialBase.name.ilike(f'%{p.name}%'),
|
||||||
|
MaterialBase.is_enabled == True
|
||||||
|
).order_by(MaterialBase.id.desc()).first()
|
||||||
|
|
||||||
|
if material:
|
||||||
|
item['material'] = {
|
||||||
|
'id': material.id,
|
||||||
|
'company_name': material.company_name or '',
|
||||||
|
'name': material.name,
|
||||||
|
'spec_model': material.spec_model or '',
|
||||||
|
'category': material.category or '',
|
||||||
|
'unit': material.unit or '',
|
||||||
|
'type': material.material_type or '',
|
||||||
|
'is_inspection_required': bool(material.is_inspection_required),
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
item['material'] = None
|
||||||
|
items.append(item)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'items': items,
|
||||||
|
'total': pagination.total,
|
||||||
|
'pages': pagination.pages,
|
||||||
|
'current_page': page
|
||||||
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def search_base_material(keyword: str, page: int = 1, limit: int = 20):
|
def search_base_material(keyword: str, page: int = 1, limit: int = 20):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -0,0 +1,79 @@
|
|||||||
|
"""打通采购申请与采购入库的按单入库链路
|
||||||
|
|
||||||
|
Revision ID: a1b2c3d4e5f6
|
||||||
|
Revises: None (或填当前最新 revision)
|
||||||
|
Create Date: 2026-07-14
|
||||||
|
|
||||||
|
变更内容:
|
||||||
|
1. purchase_request 表新增 base_id (FK → material_base.id)
|
||||||
|
2. stock_buy 表新增 request_id (FK → purchase_request.id)
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'a1b2c3d4e5f6'
|
||||||
|
down_revision = None # ← 替换为当前最新 revision ID
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ============================================================
|
||||||
|
# 1. purchase_request 新增 base_id
|
||||||
|
# ============================================================
|
||||||
|
with op.batch_alter_table('purchase_request', schema=None) as batch_op:
|
||||||
|
batch_op.add_column(
|
||||||
|
sa.Column('base_id', sa.Integer(), nullable=True,
|
||||||
|
comment='关联基础物料ID')
|
||||||
|
)
|
||||||
|
batch_op.create_foreign_key(
|
||||||
|
'fk_purchase_request_base_id',
|
||||||
|
'material_base',
|
||||||
|
['base_id'],
|
||||||
|
['id'],
|
||||||
|
ondelete='SET NULL'
|
||||||
|
)
|
||||||
|
batch_op.create_index(
|
||||||
|
'ix_purchase_request_base_id',
|
||||||
|
['base_id']
|
||||||
|
)
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# 2. stock_buy 新增 request_id
|
||||||
|
# ============================================================
|
||||||
|
with op.batch_alter_table('stock_buy', schema=None) as batch_op:
|
||||||
|
batch_op.add_column(
|
||||||
|
sa.Column('request_id', sa.Integer(), nullable=True,
|
||||||
|
comment='关联采购申请单ID')
|
||||||
|
)
|
||||||
|
batch_op.create_foreign_key(
|
||||||
|
'fk_stock_buy_request_id',
|
||||||
|
'purchase_request',
|
||||||
|
['request_id'],
|
||||||
|
['id'],
|
||||||
|
ondelete='SET NULL'
|
||||||
|
)
|
||||||
|
batch_op.create_index(
|
||||||
|
'ix_stock_buy_request_id',
|
||||||
|
['request_id']
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ============================================================
|
||||||
|
# 回滚:stock_buy 删除 request_id
|
||||||
|
# ============================================================
|
||||||
|
with op.batch_alter_table('stock_buy', schema=None) as batch_op:
|
||||||
|
batch_op.drop_constraint('fk_stock_buy_request_id', type_='foreignkey')
|
||||||
|
batch_op.drop_index('ix_stock_buy_request_id')
|
||||||
|
batch_op.drop_column('request_id')
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# 回滚:purchase_request 删除 base_id
|
||||||
|
# ============================================================
|
||||||
|
with op.batch_alter_table('purchase_request', schema=None) as batch_op:
|
||||||
|
batch_op.drop_constraint('fk_purchase_request_base_id', type_='foreignkey')
|
||||||
|
batch_op.drop_index('ix_purchase_request_base_id')
|
||||||
|
batch_op.drop_column('base_id')
|
||||||
93
inventory-backend/migrations_add_purchase_stockbuy_link.sql
Normal file
93
inventory-backend/migrations_add_purchase_stockbuy_link.sql
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
-- ============================================================
|
||||||
|
-- migrations_add_purchase_stockbuy_link.sql
|
||||||
|
-- 打通「采购申请」与「采购入库」的按单入库链路
|
||||||
|
--
|
||||||
|
-- 变更内容:
|
||||||
|
-- 1. purchase_request 表新增 base_id 列(FK → material_base.id)
|
||||||
|
-- 2. stock_buy 表新增 request_id 列(FK → purchase_request.id)
|
||||||
|
--
|
||||||
|
-- 执行方式(任选一种):
|
||||||
|
-- psql -h <host> -U postgres -d inventory_system -f migrations_add_purchase_stockbuy_link.sql
|
||||||
|
-- 或在 pgAdmin / Navicat / DBeaver 中打开执行
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
BEGIN;
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 1. purchase_request 新增 base_id
|
||||||
|
-- 硬关联基础物料表,使采购申请能精确锁定物料
|
||||||
|
-- ============================================================
|
||||||
|
ALTER TABLE purchase_request
|
||||||
|
ADD COLUMN IF NOT EXISTS base_id INTEGER;
|
||||||
|
|
||||||
|
-- 添加外键约束(如果列已存在但无约束,会补充)
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM information_schema.table_constraints
|
||||||
|
WHERE constraint_name = 'fk_purchase_request_base_id'
|
||||||
|
AND table_name = 'purchase_request'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE purchase_request
|
||||||
|
ADD CONSTRAINT fk_purchase_request_base_id
|
||||||
|
FOREIGN KEY (base_id) REFERENCES material_base(id)
|
||||||
|
ON DELETE SET NULL;
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
-- 为 base_id 添加索引(支持按物料筛选采购申请)
|
||||||
|
CREATE INDEX IF NOT EXISTS ix_purchase_request_base_id
|
||||||
|
ON purchase_request (base_id);
|
||||||
|
|
||||||
|
COMMENT ON COLUMN purchase_request.base_id IS '关联基础物料ID';
|
||||||
|
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 2. stock_buy 新增 request_id
|
||||||
|
-- 关联采购申请单,使入库记录可追溯到来源采购单
|
||||||
|
-- ============================================================
|
||||||
|
ALTER TABLE stock_buy
|
||||||
|
ADD COLUMN IF NOT EXISTS request_id INTEGER;
|
||||||
|
|
||||||
|
-- 添加外键约束
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM information_schema.table_constraints
|
||||||
|
WHERE constraint_name = 'fk_stock_buy_request_id'
|
||||||
|
AND table_name = 'stock_buy'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE stock_buy
|
||||||
|
ADD CONSTRAINT fk_stock_buy_request_id
|
||||||
|
FOREIGN KEY (request_id) REFERENCES purchase_request(id)
|
||||||
|
ON DELETE SET NULL;
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
-- 为 request_id 添加索引(支持"按单查入库"的追溯查询)
|
||||||
|
CREATE INDEX IF NOT EXISTS ix_stock_buy_request_id
|
||||||
|
ON stock_buy (request_id);
|
||||||
|
|
||||||
|
COMMENT ON COLUMN stock_buy.request_id IS '关联采购申请单ID';
|
||||||
|
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 3. 验证结果
|
||||||
|
-- ============================================================
|
||||||
|
SELECT
|
||||||
|
column_name,
|
||||||
|
data_type,
|
||||||
|
is_nullable,
|
||||||
|
pg_catalog.col_description(
|
||||||
|
(SELECT c.oid FROM pg_catalog.pg_class c
|
||||||
|
WHERE c.relname = table_name),
|
||||||
|
ordinal_position
|
||||||
|
) AS comment
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_name IN ('purchase_request', 'stock_buy')
|
||||||
|
AND column_name IN ('base_id', 'request_id')
|
||||||
|
ORDER BY table_name, column_name;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
@ -3,6 +3,7 @@ import request from '@/utils/request'
|
|||||||
export interface PurchaseItem {
|
export interface PurchaseItem {
|
||||||
id?: number
|
id?: number
|
||||||
request_no?: string
|
request_no?: string
|
||||||
|
base_id?: number
|
||||||
name: string
|
name: string
|
||||||
spec_model?: string
|
spec_model?: string
|
||||||
quantity: number
|
quantity: number
|
||||||
@ -22,6 +23,17 @@ export interface PurchaseItem {
|
|||||||
reject_reason?: string
|
reject_reason?: string
|
||||||
created_at?: string
|
created_at?: string
|
||||||
updated_at?: string
|
updated_at?: string
|
||||||
|
// [新增] 关联物料基础信息(已审批未入库列表返回)
|
||||||
|
material?: {
|
||||||
|
id: number
|
||||||
|
company_name: string
|
||||||
|
name: string
|
||||||
|
spec_model: string
|
||||||
|
category: string
|
||||||
|
unit: string
|
||||||
|
type: string
|
||||||
|
is_inspection_required: boolean
|
||||||
|
} | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Approver {
|
export interface Approver {
|
||||||
@ -98,3 +110,16 @@ export function searchMaterialPurchase(keyword: string, page: number = 1) {
|
|||||||
params: { keyword, page }
|
params: { keyword, page }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [新增] 获取已审批通过且未入库的采购单列表(供库管按单入库使用)
|
||||||
|
export function getApprovedUnstockedRequests(params: {
|
||||||
|
page?: number
|
||||||
|
limit?: number
|
||||||
|
keyword?: string
|
||||||
|
}) {
|
||||||
|
return request({
|
||||||
|
url: '/purchase/approved-unstocked',
|
||||||
|
method: 'get',
|
||||||
|
params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@ -280,6 +280,18 @@
|
|||||||
<el-icon class="icon"><Box/></el-icon>
|
<el-icon class="icon"><Box/></el-icon>
|
||||||
<span>1. 基础信息</span>
|
<span>1. 基础信息</span>
|
||||||
|
|
||||||
|
<!-- [新增] 从采购单导入按钮(仅新增模式显示) -->
|
||||||
|
<el-button
|
||||||
|
v-if="dialogStatus === 'create'"
|
||||||
|
type="success"
|
||||||
|
plain
|
||||||
|
size="small"
|
||||||
|
style="margin-left: 15px;"
|
||||||
|
@click="openPurchaseImport"
|
||||||
|
>
|
||||||
|
<el-icon><Download /></el-icon> 从采购单导入
|
||||||
|
</el-button>
|
||||||
|
|
||||||
<el-link
|
<el-link
|
||||||
v-if="form.base_id"
|
v-if="form.base_id"
|
||||||
type="primary"
|
type="primary"
|
||||||
@ -292,6 +304,15 @@
|
|||||||
</div>
|
</div>
|
||||||
<span class="sub-title" v-if="dialogStatus === 'create'"> (请先搜索锁定物料)</span>
|
<span class="sub-title" v-if="dialogStatus === 'create'"> (请先搜索锁定物料)</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- [新增] 已关联采购单提示 -->
|
||||||
|
<div v-if="form.request_id" style="padding: 8px 20px; background: #f0f9eb; border-bottom: 1px solid #c2e7b0; display: flex; align-items: center; gap: 8px;">
|
||||||
|
<el-tag type="success" effect="dark" size="small">按单入库</el-tag>
|
||||||
|
<span style="font-size: 13px; color: #67C23A;">
|
||||||
|
已关联采购申请单,入库后将自动标记为「已完成」
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<el-row :gutter="24" v-if="dialogStatus === 'create'" style="margin-bottom: 20px;">
|
<el-row :gutter="24" v-if="dialogStatus === 'create'" style="margin-bottom: 20px;">
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
@ -649,6 +670,89 @@
|
|||||||
/>
|
/>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- [新增] 从采购单导入弹窗 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="purchaseImportVisible"
|
||||||
|
title="从采购单导入"
|
||||||
|
width="900px"
|
||||||
|
destroy-on-close
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
>
|
||||||
|
<div style="margin-bottom: 15px;">
|
||||||
|
<el-input
|
||||||
|
v-model="purchaseImportKeyword"
|
||||||
|
placeholder="搜索采购单号 / 名称 / 规格..."
|
||||||
|
clearable
|
||||||
|
style="width: 300px;"
|
||||||
|
@keyup.enter="fetchPurchaseImportList"
|
||||||
|
@clear="fetchPurchaseImportList"
|
||||||
|
>
|
||||||
|
<template #prefix><el-icon><Search /></el-icon></template>
|
||||||
|
</el-input>
|
||||||
|
<el-button type="primary" style="margin-left: 10px;" @click="fetchPurchaseImportList">搜索</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-table
|
||||||
|
v-loading="purchaseImportLoading"
|
||||||
|
:data="purchaseImportList"
|
||||||
|
border
|
||||||
|
stripe
|
||||||
|
:row-class-name="getPurchaseRowClassName"
|
||||||
|
@row-click="onPurchaseImportSelect"
|
||||||
|
max-height="400px"
|
||||||
|
style="width: 100%; cursor: pointer;"
|
||||||
|
>
|
||||||
|
<el-table-column prop="request_no" label="采购单号" min-width="200" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="name" label="物品名称" min-width="160" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="spec_model" label="规格型号" min-width="110" show-overflow-tooltip>
|
||||||
|
<template #default="scope">
|
||||||
|
{{ scope.row.spec_model || '-' }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="quantity" label="申请数量" width="85" align="right" />
|
||||||
|
<el-table-column prop="unit_price" label="单价" width="100" align="right">
|
||||||
|
<template #default="scope">
|
||||||
|
{{ scope.row.unit_price ? '¥' + Number(scope.row.unit_price).toFixed(2) : '-' }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="物料信息" min-width="160" show-overflow-tooltip>
|
||||||
|
<template #default="scope">
|
||||||
|
<template v-if="scope.row.material">
|
||||||
|
<el-tag size="small" type="info" effect="plain">{{ scope.row.material.company_name }}</el-tag>
|
||||||
|
<span style="margin-left: 4px; font-size: 12px; color: #909399;">{{ scope.row.material.type || '-' }}</span>
|
||||||
|
</template>
|
||||||
|
<span v-else class="text-placeholder">未关联物料</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="approved_at" label="审批时间" min-width="155" />
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<div style="margin-top: 12px; display: flex; justify-content: flex-end;">
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="purchaseImportPage"
|
||||||
|
:page-size="20"
|
||||||
|
:total="purchaseImportTotal"
|
||||||
|
layout="total, prev, pager, next"
|
||||||
|
background
|
||||||
|
small
|
||||||
|
@current-change="fetchPurchaseImportList"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<el-button @click="purchaseImportVisible = false">取消</el-button>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
:disabled="!purchaseImportSelected"
|
||||||
|
@click="confirmPurchaseImport"
|
||||||
|
>
|
||||||
|
确认导入
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
<el-dialog v-model="printVisible" title="标签打印预览" width="400px" destroy-on-close append-to-body :close-on-click-modal="false" :close-on-press-escape="false">
|
<el-dialog v-model="printVisible" title="标签打印预览" width="400px" destroy-on-close append-to-body :close-on-click-modal="false" :close-on-press-escape="false">
|
||||||
<div style="text-align: center;">
|
<div style="text-align: center;">
|
||||||
<div v-loading="printLoading" class="preview-box">
|
<div v-loading="printLoading" class="preview-box">
|
||||||
@ -678,7 +782,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {ref, reactive, onMounted, watch, computed} from 'vue'
|
import {ref, reactive, onMounted, watch, computed} from 'vue'
|
||||||
import {Plus, Setting, Refresh, Search, Lock, Box, House, InfoFilled, Link, Printer, Camera, Delete, Picture, EditPen} from '@element-plus/icons-vue'
|
import {Plus, Setting, Refresh, Search, Lock, Box, House, InfoFilled, Link, Printer, Camera, Delete, Picture, EditPen, Download} from '@element-plus/icons-vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
import {ElMessage, ElMessageBox, ElLoading} from 'element-plus'
|
import {ElMessage, ElMessageBox, ElLoading} from 'element-plus'
|
||||||
@ -698,6 +802,7 @@ import {
|
|||||||
getLocationSuggestions,
|
getLocationSuggestions,
|
||||||
getFilterOptions
|
getFilterOptions
|
||||||
} from '@/api/inbound/buy'
|
} from '@/api/inbound/buy'
|
||||||
|
import { getApprovedUnstockedRequests } from '@/api/purchase'
|
||||||
import {getLabelPreview, executePrint} from '@/api/common/print'
|
import {getLabelPreview, executePrint} from '@/api/common/print'
|
||||||
import { getWarehouseTree } from '@/api/common/warehouse'
|
import { getWarehouseTree } from '@/api/common/warehouse'
|
||||||
import { usePasteUpload } from '@/hooks/usePasteUpload'
|
import { usePasteUpload } from '@/hooks/usePasteUpload'
|
||||||
@ -864,6 +969,15 @@ const inspection_report_url = ref('')
|
|||||||
// 智能扫码弹窗
|
// 智能扫码弹窗
|
||||||
const scannerDialogVisible = ref(false)
|
const scannerDialogVisible = ref(false)
|
||||||
|
|
||||||
|
// [新增] 从采购单导入弹窗
|
||||||
|
const purchaseImportVisible = ref(false)
|
||||||
|
const purchaseImportLoading = ref(false)
|
||||||
|
const purchaseImportList = ref<any[]>([])
|
||||||
|
const purchaseImportTotal = ref(0)
|
||||||
|
const purchaseImportPage = ref(1)
|
||||||
|
const purchaseImportKeyword = ref('')
|
||||||
|
const purchaseImportSelected = ref<any>(null)
|
||||||
|
|
||||||
// 库位级联选择器数据
|
// 库位级联选择器数据
|
||||||
const warehouseOptions = ref<any[]>([])
|
const warehouseOptions = ref<any[]>([])
|
||||||
|
|
||||||
@ -1062,7 +1176,8 @@ const form = reactive({
|
|||||||
currency: 'CNY', exchange_rate: 1.00,
|
currency: 'CNY', exchange_rate: 1.00,
|
||||||
supplier_name: '', purchaser: '', purchaser_email: '', source_link: '', detail_link: '',
|
supplier_name: '', purchaser: '', purchaser_email: '', source_link: '', detail_link: '',
|
||||||
arrival_photo: [] as string[], inspection_report: [] as string[],
|
arrival_photo: [] as string[], inspection_report: [] as string[],
|
||||||
print_copies: 1
|
print_copies: 1,
|
||||||
|
request_id: undefined as number | undefined // [新增] 关联采购申请单
|
||||||
})
|
})
|
||||||
|
|
||||||
// ------------------------------------
|
// ------------------------------------
|
||||||
@ -1164,16 +1279,7 @@ const onMaterialSelected = async (item: any) => {
|
|||||||
isCurrentMaterialInspectionRequired.value = item.isInspectionRequired || false
|
isCurrentMaterialInspectionRequired.value = item.isInspectionRequired || false
|
||||||
updateInspectionRules()
|
updateInspectionRules()
|
||||||
checkHistoryAndSetMode(item.id)
|
checkHistoryAndSetMode(item.id)
|
||||||
|
fetchLastLocation(item.id)
|
||||||
try {
|
|
||||||
const res = await request.get('/v1/inbound/buy/last-location', { params: { base_id: item.id } })
|
|
||||||
if (res.code === 200 && res.data.location) {
|
|
||||||
form.warehouse_location = res.data.location
|
|
||||||
ElMessage.info(`已自动带入该物料历史库位:【${res.data.location}】,请核对。`)
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('获取历史库位失败', e)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 动态更新质检相关校验规则
|
// 动态更新质检相关校验规则
|
||||||
@ -1449,7 +1555,8 @@ const handleUpdate = (row: any) => {
|
|||||||
currency: row.currency, exchange_rate: Number(row.exchange_rate),
|
currency: row.currency, exchange_rate: Number(row.exchange_rate),
|
||||||
supplier_name: row.supplier_name, purchaser: row.purchaser, purchaser_email: row.purchaser_email,
|
supplier_name: row.supplier_name, purchaser: row.purchaser, purchaser_email: row.purchaser_email,
|
||||||
source_link: row.source_link, detail_link: row.detail_link,
|
source_link: row.source_link, detail_link: row.detail_link,
|
||||||
arrival_photo: row.arrival_photo || [], inspection_report: row.inspection_report || []
|
arrival_photo: row.arrival_photo || [], inspection_report: row.inspection_report || [],
|
||||||
|
request_id: row.request_id || undefined
|
||||||
})
|
})
|
||||||
// 计算含税单价
|
// 计算含税单价
|
||||||
if (form.unit_price !== undefined && form.unit_price !== null) {
|
if (form.unit_price !== undefined && form.unit_price !== null) {
|
||||||
@ -1663,6 +1770,147 @@ const handleScannerConfirm = (result: string) => {
|
|||||||
ElMessage.success('序列号已提取')
|
ElMessage.success('序列号已提取')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// [新增] 从采购单导入逻辑
|
||||||
|
// ==========================================
|
||||||
|
|
||||||
|
// 打开采购单导入弹窗
|
||||||
|
const openPurchaseImport = () => {
|
||||||
|
purchaseImportSelected.value = null
|
||||||
|
purchaseImportKeyword.value = ''
|
||||||
|
purchaseImportPage.value = 1
|
||||||
|
purchaseImportVisible.value = true
|
||||||
|
fetchPurchaseImportList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拉取已审批未入库的采购单列表
|
||||||
|
const fetchPurchaseImportList = async () => {
|
||||||
|
purchaseImportLoading.value = true
|
||||||
|
try {
|
||||||
|
const res: any = await getApprovedUnstockedRequests({
|
||||||
|
page: purchaseImportPage.value,
|
||||||
|
limit: 20,
|
||||||
|
keyword: purchaseImportKeyword.value || undefined
|
||||||
|
})
|
||||||
|
if (res.code === 200) {
|
||||||
|
purchaseImportList.value = res.data.items || []
|
||||||
|
purchaseImportTotal.value = res.data.total || 0
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.msg || '获取采购单列表失败')
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
ElMessage.error(e.response?.data?.msg || '获取采购单列表失败')
|
||||||
|
} finally {
|
||||||
|
purchaseImportLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表格选中行变化(点击已选中行可取消)
|
||||||
|
const onPurchaseImportSelect = (row: any) => {
|
||||||
|
if (purchaseImportSelected.value?.id === row.id) {
|
||||||
|
purchaseImportSelected.value = null // 再次点击取消选中
|
||||||
|
} else {
|
||||||
|
purchaseImportSelected.value = row
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选中行高亮样式 class
|
||||||
|
const getPurchaseRowClassName = ({ row }: { row: any }) => {
|
||||||
|
return purchaseImportSelected.value?.id === row.id ? 'purchase-row-selected' : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确认从采购单导入
|
||||||
|
const confirmPurchaseImport = () => {
|
||||||
|
const po = purchaseImportSelected.value
|
||||||
|
if (!po) {
|
||||||
|
ElMessage.warning('请先选择一条采购单')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 从采购单的 material 信息填充基础物料(触发级联填充)
|
||||||
|
if (po.material) {
|
||||||
|
const mat = po.material
|
||||||
|
form.base_id = mat.id
|
||||||
|
form.company_name = mat.company_name
|
||||||
|
form.material_name = mat.name
|
||||||
|
form.spec_model = mat.spec_model
|
||||||
|
form.category = mat.category
|
||||||
|
form.unit = mat.unit
|
||||||
|
form.material_type = mat.type
|
||||||
|
materialNameInput.value = mat.name
|
||||||
|
isCurrentMaterialInspectionRequired.value = mat.is_inspection_required || false
|
||||||
|
updateInspectionRules()
|
||||||
|
|
||||||
|
// 异步加载历史库位和记录模式
|
||||||
|
checkHistoryAndSetMode(mat.id)
|
||||||
|
fetchLastLocation(mat.id)
|
||||||
|
|
||||||
|
materialOptions.value = [{
|
||||||
|
id: mat.id, name: mat.name, spec: mat.spec_model,
|
||||||
|
category: mat.category, company_name: mat.company_name,
|
||||||
|
type: mat.type, unit: mat.unit,
|
||||||
|
isInspectionRequired: mat.is_inspection_required
|
||||||
|
}]
|
||||||
|
} else if (po.base_id) {
|
||||||
|
// 如果 material 为空但 base_id 有值,尝试通过 base_id 查询
|
||||||
|
// 这里简单处理:直接设置 base_id 让用户手动搜索
|
||||||
|
form.base_id = po.base_id
|
||||||
|
ElMessage.info('该采购单未附带物料详情,已填充 base_id,请手动搜索物料确认')
|
||||||
|
} else {
|
||||||
|
ElMessage.warning('该采购单未关联基础物料,无法自动填充物料信息')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 填充采购商务信息(单价/总价互相推算,缺哪个补哪个)
|
||||||
|
const qty = Number(po.quantity) || 1
|
||||||
|
form.in_quantity = qty
|
||||||
|
|
||||||
|
const hasUnitPrice = po.unit_price !== null && po.unit_price !== undefined && Number(po.unit_price) > 0
|
||||||
|
const hasTotalPrice = po.total_price !== null && po.total_price !== undefined && Number(po.total_price) > 0
|
||||||
|
|
||||||
|
if (hasUnitPrice && hasTotalPrice) {
|
||||||
|
// 两者都有,直接使用
|
||||||
|
form.unit_price = Number(po.unit_price)
|
||||||
|
form.total_price = Number(po.total_price)
|
||||||
|
} else if (hasUnitPrice) {
|
||||||
|
// 只有单价 → 用数量推算总价
|
||||||
|
form.unit_price = Number(po.unit_price)
|
||||||
|
form.total_price = Number((qty * form.unit_price).toFixed(2))
|
||||||
|
} else if (hasTotalPrice) {
|
||||||
|
// 只有总价 → 用数量反推单价
|
||||||
|
form.total_price = Number(po.total_price)
|
||||||
|
form.unit_price = Number((form.total_price / qty).toFixed(4))
|
||||||
|
}
|
||||||
|
// 都没有就都不填
|
||||||
|
if (po.supplier_link) {
|
||||||
|
form.source_link = po.supplier_link
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 关联采购单号
|
||||||
|
form.request_id = po.id
|
||||||
|
|
||||||
|
// 4. 触发价格联动
|
||||||
|
updatePrices('pre')
|
||||||
|
|
||||||
|
// 5. 显示关联提示
|
||||||
|
ElMessage.success(`已从采购单【${po.request_no}】导入物料及商务信息,请核对后补充入库详情`)
|
||||||
|
|
||||||
|
purchaseImportVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 异步获取历史库位(从 onMaterialSelected 中抽离)
|
||||||
|
const fetchLastLocation = async (baseId: number) => {
|
||||||
|
try {
|
||||||
|
const res = await request.get('/v1/inbound/buy/last-location', { params: { base_id: baseId } })
|
||||||
|
if (res.code === 200 && res.data.location) {
|
||||||
|
form.warehouse_location = res.data.location
|
||||||
|
ElMessage.info(`已自动带入该物料历史库位:【${res.data.location}】,请核对。`)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('获取历史库位失败', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const addCondition = () => {
|
const addCondition = () => {
|
||||||
advancedConditions.value.push({ field: '', operator: '', value: '' })
|
advancedConditions.value.push({ field: '', operator: '', value: '' })
|
||||||
}
|
}
|
||||||
@ -1771,7 +2019,8 @@ const resetForm = () => {
|
|||||||
unit_price: undefined, post_tax_unit_price: undefined, total_price: undefined,
|
unit_price: undefined, post_tax_unit_price: undefined, total_price: undefined,
|
||||||
tax_rate: 0,
|
tax_rate: 0,
|
||||||
currency: 'CNY', exchange_rate: 1.00, supplier_name: '', purchaser: '', purchaser_email: '', source_link: '', detail_link: '', arrival_photo: [], inspection_report: [],
|
currency: 'CNY', exchange_rate: 1.00, supplier_name: '', purchaser: '', purchaser_email: '', source_link: '', detail_link: '', arrival_photo: [], inspection_report: [],
|
||||||
print_copies: 1
|
print_copies: 1,
|
||||||
|
request_id: undefined
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const getStatusType = (status: string) => { const map: any = {'在库': 'success', '出库': 'info', '损耗': 'danger'}; return map[status] || 'warning' }
|
const getStatusType = (status: string) => { const map: any = {'在库': 'success', '出库': 'info', '损耗': 'danger'}; return map[status] || 'warning' }
|
||||||
@ -1974,6 +2223,39 @@ onMounted(() => {
|
|||||||
:deep(.el-input-number .el-input__inner) {
|
:deep(.el-input-number .el-input__inner) {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ==========================================
|
||||||
|
[新增] 采购单导入弹窗 - 选中行高亮样式
|
||||||
|
========================================== */
|
||||||
|
/* 注意: row-class-name 直接加在 <tr> 上,需要用 :deep 穿透 scoped */
|
||||||
|
:deep(.purchase-row-selected) {
|
||||||
|
background-color: #ecf5ff !important;
|
||||||
|
}
|
||||||
|
:deep(.purchase-row-selected) td {
|
||||||
|
background-color: #ecf5ff !important;
|
||||||
|
border-bottom-color: #b3d8ff !important;
|
||||||
|
}
|
||||||
|
/* 左侧蓝色高亮指示条 */
|
||||||
|
:deep(.purchase-row-selected) td:first-child {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
:deep(.purchase-row-selected) td:first-child::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 3px;
|
||||||
|
background: #409EFF;
|
||||||
|
}
|
||||||
|
/* stripe 条纹表选中时保持蓝色,不被交替色覆盖 */
|
||||||
|
:deep(.el-table--striped .el-table__body tr.purchase-row-selected td) {
|
||||||
|
background-color: #ecf5ff !important;
|
||||||
|
}
|
||||||
|
/* hover 时只对普通行做浅灰提示,选中行不变 */
|
||||||
|
:deep(.el-table__body tr:not(.purchase-row-selected):hover) td {
|
||||||
|
background-color: #f5f7fa !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
Reference in New Issue
Block a user