修改semi,product,service的搜索逻辑
This commit is contained in:
@ -1,7 +1,7 @@
|
|||||||
|
# inventory-backend/app/api/v1/inbound/service.py
|
||||||
from flask import request, jsonify, current_app
|
from flask import request, jsonify, current_app
|
||||||
from flask_jwt_extended import jwt_required
|
from flask_jwt_extended import jwt_required
|
||||||
from . import inbound_bp
|
from . import inbound_bp
|
||||||
from app.schemas.stock_schema import stock_service_schema
|
|
||||||
from app.services.inbound.service_service import ServiceService
|
from app.services.inbound.service_service import ServiceService
|
||||||
from app.utils.decorators import role_required
|
from app.utils.decorators import role_required
|
||||||
import traceback
|
import traceback
|
||||||
@ -23,6 +23,7 @@ def search_base():
|
|||||||
current_app.logger.error(f'搜索基础物料失败: {str(e)}')
|
current_app.logger.error(f'搜索基础物料失败: {str(e)}')
|
||||||
return jsonify({'code': 500, 'msg': '内部服务器错误'}), 500
|
return jsonify({'code': 500, 'msg': '内部服务器错误'}), 500
|
||||||
|
|
||||||
|
|
||||||
@inbound_bp.route('/service', methods=['GET'])
|
@inbound_bp.route('/service', methods=['GET'])
|
||||||
@jwt_required()
|
@jwt_required()
|
||||||
def get_service_list():
|
def get_service_list():
|
||||||
@ -62,20 +63,25 @@ def create_service():
|
|||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
if not data:
|
if not data:
|
||||||
return jsonify({'code': 400, 'msg': '请求数据为空'}), 400
|
return jsonify({'code': 400, 'msg': '请求数据为空'}), 400
|
||||||
errors = stock_service_schema.validate(data)
|
|
||||||
if errors:
|
# 基础校验
|
||||||
return jsonify({'code': 400, 'msg': '数据校验失败', 'errors': errors}), 400
|
if not data.get('base_id'):
|
||||||
|
return jsonify({'code': 400, 'msg': '请选择基础物料'}), 400
|
||||||
|
if data.get('sale_price') is None:
|
||||||
|
return jsonify({'code': 400, 'msg': '请输入售价'}), 400
|
||||||
|
|
||||||
try:
|
try:
|
||||||
service = ServiceService.create_service(data)
|
service = ServiceService.create_service(data)
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'code': 201,
|
'code': 201,
|
||||||
'msg': '创建成功',
|
'msg': '创建成功',
|
||||||
'data': stock_service_schema.dump(service)
|
'data': service.to_dict()
|
||||||
}), 201
|
}), 201
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
return jsonify({'code': 400, 'msg': str(e)}), 400
|
return jsonify({'code': 400, 'msg': str(e)}), 400
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.error(f'创建服务权益失败: {str(e)}')
|
current_app.logger.error(f'创建服务权益失败: {str(e)}')
|
||||||
|
traceback.print_exc()
|
||||||
return jsonify({'code': 500, 'msg': '内部服务器错误'}), 500
|
return jsonify({'code': 500, 'msg': '内部服务器错误'}), 500
|
||||||
|
|
||||||
|
|
||||||
@ -88,7 +94,7 @@ def get_service(service_id):
|
|||||||
return jsonify({
|
return jsonify({
|
||||||
'code': 200,
|
'code': 200,
|
||||||
'msg': 'success',
|
'msg': 'success',
|
||||||
'data': stock_service_schema.dump(service)
|
'data': service.to_dict()
|
||||||
})
|
})
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
return jsonify({'code': 404, 'msg': str(e)}), 404
|
return jsonify({'code': 404, 'msg': str(e)}), 404
|
||||||
@ -105,17 +111,23 @@ def update_service(service_id):
|
|||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
if not data:
|
if not data:
|
||||||
return jsonify({'code': 400, 'msg': '请求数据为空'}), 400
|
return jsonify({'code': 400, 'msg': '请求数据为空'}), 400
|
||||||
# 部分字段不允许更新,可在此过滤
|
|
||||||
allowed_fields = {'sale_price', 'provider_name', 'description'}
|
# 允许更新的字段
|
||||||
|
allowed_fields = {
|
||||||
|
'sale_price', 'provider_name', 'description',
|
||||||
|
'cost_price', 'contract_id', 'contact_person', 'valid_period'
|
||||||
|
}
|
||||||
filtered_data = {k: v for k, v in data.items() if k in allowed_fields}
|
filtered_data = {k: v for k, v in data.items() if k in allowed_fields}
|
||||||
|
|
||||||
if not filtered_data:
|
if not filtered_data:
|
||||||
return jsonify({'code': 400, 'msg': '无有效更新字段'}), 400
|
return jsonify({'code': 400, 'msg': '无有效更新字段'}), 400
|
||||||
|
|
||||||
try:
|
try:
|
||||||
service = ServiceService.update_service(service_id, filtered_data)
|
service = ServiceService.update_service(service_id, filtered_data)
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'code': 200,
|
'code': 200,
|
||||||
'msg': '更新成功',
|
'msg': '更新成功',
|
||||||
'data': stock_service_schema.dump(service)
|
'data': service.to_dict()
|
||||||
})
|
})
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
return jsonify({'code': 404, 'msg': str(e)}), 404
|
return jsonify({'code': 404, 'msg': str(e)}), 404
|
||||||
@ -142,9 +154,6 @@ def delete_service(service_id):
|
|||||||
return jsonify({'code': 500, 'msg': '内部服务器错误'}), 500
|
return jsonify({'code': 500, 'msg': '内部服务器错误'}), 500
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
# 供应商建议
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
@inbound_bp.route('/service/suggestions/providers', methods=['GET'])
|
@inbound_bp.route('/service/suggestions/providers', methods=['GET'])
|
||||||
@jwt_required()
|
@jwt_required()
|
||||||
def get_provider_suggestions():
|
def get_provider_suggestions():
|
||||||
@ -155,9 +164,6 @@ def get_provider_suggestions():
|
|||||||
return jsonify({'code': 200, 'msg': 'success', 'data': data})
|
return jsonify({'code': 200, 'msg': 'success', 'data': data})
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
# 系统用户建议
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
@inbound_bp.route('/service/suggestions/users', methods=['GET'])
|
@inbound_bp.route('/service/suggestions/users', methods=['GET'])
|
||||||
@jwt_required()
|
@jwt_required()
|
||||||
def get_user_suggestions():
|
def get_user_suggestions():
|
||||||
@ -166,9 +172,6 @@ def get_user_suggestions():
|
|||||||
return jsonify({'code': 200, 'msg': 'success', 'data': data})
|
return jsonify({'code': 200, 'msg': 'success', 'data': data})
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
# 获取筛选选项
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
@inbound_bp.route('/service/options', methods=['GET'])
|
@inbound_bp.route('/service/options', methods=['GET'])
|
||||||
@jwt_required()
|
@jwt_required()
|
||||||
def get_options():
|
def get_options():
|
||||||
@ -176,4 +179,4 @@ def get_options():
|
|||||||
data = ServiceService.get_filter_options()
|
data = ServiceService.get_filter_options()
|
||||||
return jsonify({'code': 200, 'msg': 'success', 'data': data})
|
return jsonify({'code': 200, 'msg': 'success', 'data': data})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({'code': 500, 'msg': str(e)}), 500
|
return jsonify({'code': 500, 'msg': str(e)}), 500
|
||||||
@ -7,64 +7,67 @@ class StockService(db.Model):
|
|||||||
"""
|
"""
|
||||||
服务权益库存表
|
服务权益库存表
|
||||||
对应数据库表: stock_service
|
对应数据库表: stock_service
|
||||||
|
说明:服务权益通常为虚拟资产,不进行具体的库存数量(actual_quantity)管理
|
||||||
"""
|
"""
|
||||||
__tablename__ = 'stock_service'
|
__tablename__ = 'stock_service'
|
||||||
|
|
||||||
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||||
|
|
||||||
# 关联基础物料信息
|
# 外键关联基础物料
|
||||||
# 注意:这里使用了 db.ForeignKey 指向 material_base 表的 id
|
|
||||||
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,格式 SRV-YYYYMMDD-XXXX
|
# 核心业务字段
|
||||||
sku = db.Column(db.String(64), unique=True, nullable=False)
|
sku = db.Column(db.String(100), unique=True, nullable=False)
|
||||||
|
|
||||||
# 售价
|
# 扩展字段 (对应您的数据库建表脚本)
|
||||||
sale_price = db.Column(db.Numeric(10, 2), nullable=False)
|
service_category = db.Column(db.String(100), comment='服务类别')
|
||||||
|
|
||||||
# 服务商名称
|
|
||||||
provider_name = db.Column(db.String(255), nullable=False, default='')
|
provider_name = db.Column(db.String(255), nullable=False, default='')
|
||||||
|
contract_id = db.Column(db.String(100), comment='合同号')
|
||||||
|
contact_person = db.Column(db.String(100), comment='联系人')
|
||||||
|
|
||||||
# 服务详情/简介
|
# 价格相关
|
||||||
|
cost_price = db.Column(db.Numeric(19, 4), default=0)
|
||||||
|
sale_price = db.Column(db.Numeric(19, 4), nullable=False, default=0)
|
||||||
|
|
||||||
|
# 描述与状态
|
||||||
description = db.Column(db.Text, default='')
|
description = db.Column(db.Text, default='')
|
||||||
|
valid_period = db.Column(db.String(100), comment='有效期')
|
||||||
|
status = db.Column(db.String(20), default='active')
|
||||||
|
|
||||||
# ==========================================================================
|
# 时间与系统字段
|
||||||
# 【新增】库存数量字段
|
|
||||||
# 上一轮的 Service 代码中尝试累加这两个字段,如果模型里没有,程序会报错
|
|
||||||
# ==========================================================================
|
|
||||||
actual_quantity = db.Column(db.Integer, default=0, nullable=False, comment='库存数量')
|
|
||||||
available_quantity = db.Column(db.Integer, default=0, nullable=False, comment='可用数量')
|
|
||||||
|
|
||||||
# 创建时间与更新时间
|
|
||||||
created_at = db.Column(db.DateTime, default=datetime.now, nullable=False)
|
created_at = db.Column(db.DateTime, default=datetime.now, nullable=False)
|
||||||
updated_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now, nullable=False)
|
updated_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now)
|
||||||
|
is_deleted = db.Column(db.Boolean, default=False)
|
||||||
# 软删除标志
|
|
||||||
is_deleted = db.Column(db.Boolean, default=False, nullable=False)
|
|
||||||
|
|
||||||
# ==========================================================================
|
# ==========================================================================
|
||||||
# 【修复】关系映射
|
# 关联关系设置
|
||||||
# 1. 属性名必须叫 'base',因为 MaterialBase 里定义了 back_populates='base'
|
# MaterialBase 中定义了 back_populates='stock_services'
|
||||||
# 2. back_populates 指向 MaterialBase 里的属性名 'stock_services'
|
# 因此这里必须定义 base 属性指向 'stock_services'
|
||||||
# ==========================================================================
|
# ==========================================================================
|
||||||
base = db.relationship('MaterialBase', back_populates='stock_services', lazy='joined')
|
base = db.relationship('MaterialBase', back_populates='stock_services')
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
"""转为字典,用于 API 响应"""
|
"""序列化为字典"""
|
||||||
return {
|
return {
|
||||||
'id': self.id,
|
'id': self.id,
|
||||||
'base_id': self.base_id,
|
'base_id': self.base_id,
|
||||||
'sku': self.sku,
|
'sku': self.sku,
|
||||||
'sale_price': float(self.sale_price) if self.sale_price is not None else 0,
|
'service_category': self.service_category,
|
||||||
'provider_name': self.provider_name,
|
'provider_name': self.provider_name,
|
||||||
|
'contract_id': self.contract_id,
|
||||||
|
'contact_person': self.contact_person,
|
||||||
|
'sale_price': float(self.sale_price) if self.sale_price is not None else 0,
|
||||||
|
'cost_price': float(self.cost_price) if self.cost_price is not None else 0,
|
||||||
'description': self.description,
|
'description': self.description,
|
||||||
'actual_quantity': self.actual_quantity, # 返回库存数
|
'valid_period': self.valid_period,
|
||||||
'available_quantity': self.available_quantity, # 返回可用数
|
'status': self.status,
|
||||||
'created_at': self.created_at.strftime('%Y-%m-%d %H:%M:%S') if self.created_at else None,
|
'created_at': self.created_at.strftime('%Y-%m-%d %H:%M:%S') if self.created_at else None,
|
||||||
'updated_at': self.updated_at.strftime('%Y-%m-%d %H:%M:%S') if self.updated_at else None,
|
'updated_at': self.updated_at.strftime('%Y-%m-%d %H:%M:%S') if self.updated_at else None,
|
||||||
|
|
||||||
# 注意:这里通过 self.base 访问关联对象,而不是 self.material_base
|
# 关联的基础信息 (Flattened)
|
||||||
'material_name': self.base.name if self.base else None,
|
'material_name': self.base.name if self.base else None,
|
||||||
'spec_model': self.base.spec_model if self.base else None,
|
'spec_model': self.base.spec_model if self.base else None,
|
||||||
'unit': self.base.unit if self.base else None,
|
'unit': self.base.unit if self.base else None,
|
||||||
|
'category': self.base.category if self.base else None,
|
||||||
|
'material_type': self.base.material_type if self.base else None,
|
||||||
}
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
# app/services/inbound/service_service.py
|
# inventory-backend/app/services/inbound/service_service.py
|
||||||
from app import db
|
from app.extensions import db
|
||||||
from app.models.inbound.service import StockService
|
from app.models.inbound.service import StockService
|
||||||
from app.models.base import MaterialBase
|
from app.models.base import MaterialBase
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
@ -19,18 +19,24 @@ class ServiceService:
|
|||||||
"""生成唯一SKU,格式 SRV-YYYYMMDD-XXXX"""
|
"""生成唯一SKU,格式 SRV-YYYYMMDD-XXXX"""
|
||||||
today_str = datetime.now().strftime(cls.SKU_DATE_FORMAT)
|
today_str = datetime.now().strftime(cls.SKU_DATE_FORMAT)
|
||||||
prefix = f'{cls.SKU_PREFIX}-{today_str}-'
|
prefix = f'{cls.SKU_PREFIX}-{today_str}-'
|
||||||
|
|
||||||
# 查找今天已有的最大后缀
|
# 查找今天已有的最大后缀
|
||||||
max_sku = db.session.query(db.func.max(StockService.sku)).filter(
|
max_sku = db.session.query(db.func.max(StockService.sku)).filter(
|
||||||
StockService.sku.like(f'{prefix}%')
|
StockService.sku.like(f'{prefix}%')
|
||||||
).scalar()
|
).scalar()
|
||||||
|
|
||||||
if not max_sku:
|
if not max_sku:
|
||||||
suffix_num = 1
|
suffix_num = 1
|
||||||
else:
|
else:
|
||||||
# 提取后缀数字
|
# 提取后缀数字
|
||||||
suffix_part = max_sku.replace(prefix, '')
|
suffix_part = max_sku.replace(prefix, '')
|
||||||
match = re.match(r'^(\d+)', suffix_part)
|
try:
|
||||||
suffix_num = int(match.group(1)) if match else 0
|
match = re.search(r'(\d+)$', suffix_part)
|
||||||
|
suffix_num = int(match.group(1)) if match else 0
|
||||||
|
except:
|
||||||
|
suffix_num = 0
|
||||||
suffix_num += 1
|
suffix_num += 1
|
||||||
|
|
||||||
# 格式化为4位数字,左侧补零
|
# 格式化为4位数字,左侧补零
|
||||||
suffix = str(suffix_num).zfill(cls.SKU_SUFFIX_LEN)
|
suffix = str(suffix_num).zfill(cls.SKU_SUFFIX_LEN)
|
||||||
return f'{prefix}{suffix}'
|
return f'{prefix}{suffix}'
|
||||||
@ -39,7 +45,7 @@ class ServiceService:
|
|||||||
def search_base_material(cls, keyword):
|
def search_base_material(cls, keyword):
|
||||||
"""搜索基础物料,供前端远程选择"""
|
"""搜索基础物料,供前端远程选择"""
|
||||||
try:
|
try:
|
||||||
# [核心修改] 只查询已启用的物料
|
# 只查询已启用的物料
|
||||||
query = MaterialBase.query.filter(MaterialBase.is_enabled == True)
|
query = MaterialBase.query.filter(MaterialBase.is_enabled == True)
|
||||||
|
|
||||||
if keyword:
|
if keyword:
|
||||||
@ -50,6 +56,7 @@ class ServiceService:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
query = query.order_by(MaterialBase.id.desc()).limit(20)
|
query = query.order_by(MaterialBase.id.desc()).limit(20)
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
for item in query.all():
|
for item in query.all():
|
||||||
results.append({
|
results.append({
|
||||||
@ -61,40 +68,48 @@ class ServiceService:
|
|||||||
'type': item.material_type,
|
'type': item.material_type,
|
||||||
})
|
})
|
||||||
return results
|
return results
|
||||||
except Exception as e:
|
except Exception:
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_service(cls, data):
|
def create_service(cls, data):
|
||||||
"""创建服务权益记录"""
|
"""创建服务权益记录"""
|
||||||
# 检查基础物料是否存在
|
# 1. 检查基础物料
|
||||||
base_id = data.get('base_id')
|
base_id = data.get('base_id')
|
||||||
base = MaterialBase.query.get(base_id)
|
base = MaterialBase.query.get(base_id)
|
||||||
if not base:
|
if not base:
|
||||||
raise ValueError('基础物料不存在')
|
raise ValueError('基础物料不存在')
|
||||||
|
|
||||||
# [核心修改] 后端二次校验:如果物料已停用,禁止创建服务权益
|
|
||||||
if not base.is_enabled:
|
if not base.is_enabled:
|
||||||
raise ValueError(f"物料【{base.name}】已停用,无法创建新的服务权益。")
|
raise ValueError(f"物料【{base.name}】已停用,无法创建新的服务权益。")
|
||||||
|
|
||||||
# 生成SKU
|
# 2. 生成SKU
|
||||||
sku = cls._generate_sku()
|
sku = cls._generate_sku()
|
||||||
|
|
||||||
|
# 3. 创建对象 (不包含库存数量字段)
|
||||||
service = StockService(
|
service = StockService(
|
||||||
base_id=data['base_id'],
|
base_id=data['base_id'],
|
||||||
sku=sku,
|
sku=sku,
|
||||||
sale_price=data['sale_price'],
|
sale_price=data.get('sale_price', 0),
|
||||||
provider_name=data['provider_name'],
|
provider_name=data.get('provider_name', ''),
|
||||||
description=data.get('description', '')
|
description=data.get('description', ''),
|
||||||
|
|
||||||
|
# 可选字段映射
|
||||||
|
service_category=data.get('service_category', ''),
|
||||||
|
contract_id=data.get('contract_id', ''),
|
||||||
|
contact_person=data.get('contact_person', ''),
|
||||||
|
valid_period=data.get('valid_period', ''),
|
||||||
|
cost_price=data.get('cost_price', 0)
|
||||||
)
|
)
|
||||||
|
|
||||||
db.session.add(service)
|
db.session.add(service)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return service
|
return service
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_service(cls, service_id):
|
def get_service(cls, service_id):
|
||||||
"""获取单个服务权益"""
|
"""获取单个详情"""
|
||||||
service = StockService.query.filter_by(id=service_id, is_deleted=False).first()
|
service = StockService.query.filter_by(id=service_id, is_deleted=False).first()
|
||||||
if not service:
|
if not service:
|
||||||
raise ValueError('服务权益记录不存在')
|
raise ValueError('服务权益记录不存在')
|
||||||
@ -102,22 +117,32 @@ class ServiceService:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def update_service(cls, service_id, data):
|
def update_service(cls, service_id, data):
|
||||||
"""更新服务权益记录"""
|
"""更新服务权益"""
|
||||||
service = cls.get_service(service_id)
|
service = cls.get_service(service_id)
|
||||||
# 不允许修改 base_id 和 sku(业务上不允许变更基础物料)
|
|
||||||
|
# 允许更新的字段
|
||||||
if 'sale_price' in data:
|
if 'sale_price' in data:
|
||||||
service.sale_price = data['sale_price']
|
service.sale_price = data['sale_price']
|
||||||
if 'provider_name' in data:
|
if 'provider_name' in data:
|
||||||
service.provider_name = data['provider_name']
|
service.provider_name = data['provider_name']
|
||||||
if 'description' in data:
|
if 'description' in data:
|
||||||
service.description = data.get('description', '')
|
service.description = data.get('description', '')
|
||||||
|
if 'cost_price' in data:
|
||||||
|
service.cost_price = data.get('cost_price', 0)
|
||||||
|
if 'contract_id' in data:
|
||||||
|
service.contract_id = data.get('contract_id', '')
|
||||||
|
if 'contact_person' in data:
|
||||||
|
service.contact_person = data.get('contact_person', '')
|
||||||
|
if 'valid_period' in data:
|
||||||
|
service.valid_period = data.get('valid_period', '')
|
||||||
|
|
||||||
service.updated_at = datetime.now()
|
service.updated_at = datetime.now()
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return service
|
return service
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def delete_service(cls, service_id):
|
def delete_service(cls, service_id):
|
||||||
"""软删除服务权益"""
|
"""软删除"""
|
||||||
service = cls.get_service(service_id)
|
service = cls.get_service(service_id)
|
||||||
service.is_deleted = True
|
service.is_deleted = True
|
||||||
service.updated_at = datetime.now()
|
service.updated_at = datetime.now()
|
||||||
@ -127,97 +152,79 @@ class ServiceService:
|
|||||||
@classmethod
|
@classmethod
|
||||||
def get_service_list(cls, page=1, per_page=20, keyword=None,
|
def get_service_list(cls, page=1, per_page=20, keyword=None,
|
||||||
start_date=None, end_date=None, provider_name=None):
|
start_date=None, end_date=None, provider_name=None):
|
||||||
"""分页查询服务权益列表"""
|
"""分页查询列表"""
|
||||||
query = StockService.query.filter_by(is_deleted=False)
|
try:
|
||||||
# 关键词搜索:可搜索 SKU 或 关联物料名称
|
query = StockService.query.filter_by(is_deleted=False)
|
||||||
if keyword:
|
|
||||||
# 直接子查询
|
# 关键词联表搜索
|
||||||
query = query.filter(
|
if keyword:
|
||||||
db.or_(
|
query = query.join(StockService.base).filter(
|
||||||
StockService.sku.ilike(f'%{keyword}%'),
|
db.or_(
|
||||||
StockService.base_id.in_(
|
StockService.sku.ilike(f'%{keyword}%'),
|
||||||
db.session.query(MaterialBase.id).filter(MaterialBase.name.ilike(f'%{keyword}%'))
|
MaterialBase.name.ilike(f'%{keyword}%'),
|
||||||
|
MaterialBase.spec_model.ilike(f'%{keyword}%')
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
if start_date:
|
|
||||||
try:
|
|
||||||
start = datetime.strptime(start_date, '%Y-%m-%d')
|
|
||||||
query = query.filter(StockService.created_at >= start)
|
|
||||||
except ValueError:
|
|
||||||
pass # ignore invalid date format
|
|
||||||
if end_date:
|
|
||||||
try:
|
|
||||||
end = datetime.strptime(end_date, '%Y-%m-%d')
|
|
||||||
# 包含当天
|
|
||||||
end = end + timedelta(days=1) - timedelta(seconds=1)
|
|
||||||
query = query.filter(StockService.created_at <= end)
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
if provider_name:
|
|
||||||
query = query.filter(StockService.provider_name.ilike(f'%{provider_name}%'))
|
|
||||||
# 总数
|
|
||||||
total = query.count()
|
|
||||||
# 分页
|
|
||||||
items = query.order_by(StockService.created_at.desc()) \
|
|
||||||
.offset((page - 1) * per_page) \
|
|
||||||
.limit(per_page).all()
|
|
||||||
return {
|
|
||||||
'items': [item.to_dict() for item in items],
|
|
||||||
'total': total,
|
|
||||||
'page': page,
|
|
||||||
'per_page': per_page
|
|
||||||
}
|
|
||||||
|
|
||||||
# ============================================================
|
# 日期过滤
|
||||||
# 供应商历史查询
|
if start_date:
|
||||||
# ============================================================
|
try:
|
||||||
|
start = datetime.strptime(start_date, '%Y-%m-%d')
|
||||||
|
query = query.filter(StockService.created_at >= start)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if end_date:
|
||||||
|
try:
|
||||||
|
end = datetime.strptime(end_date, '%Y-%m-%d')
|
||||||
|
# 包含当天结束
|
||||||
|
end = end + timedelta(days=1) - timedelta(seconds=1)
|
||||||
|
query = query.filter(StockService.created_at <= end)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 服务商过滤
|
||||||
|
if provider_name:
|
||||||
|
query = query.filter(StockService.provider_name.ilike(f'%{provider_name}%'))
|
||||||
|
|
||||||
|
total = query.count()
|
||||||
|
|
||||||
|
items = query.order_by(StockService.created_at.desc()) \
|
||||||
|
.offset((page - 1) * per_page) \
|
||||||
|
.limit(per_page).all()
|
||||||
|
|
||||||
|
return {
|
||||||
|
'items': [item.to_dict() for item in items],
|
||||||
|
'total': total,
|
||||||
|
'page': page,
|
||||||
|
'per_page': per_page
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
traceback.print_exc()
|
||||||
|
raise e
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_history_providers(cls, base_id):
|
def get_history_providers(cls, base_id):
|
||||||
"""返回该物料关联的服务商列表(去重)"""
|
"""获取历史供应商"""
|
||||||
try:
|
try:
|
||||||
query = db.session.query(StockService.provider_name).filter(
|
query = db.session.query(StockService.provider_name).filter(
|
||||||
StockService.base_id == base_id,
|
StockService.base_id == base_id,
|
||||||
StockService.provider_name.isnot(None)
|
StockService.provider_name.isnot(None),
|
||||||
|
StockService.provider_name != ''
|
||||||
).distinct().order_by(StockService.provider_name)
|
).distinct().order_by(StockService.provider_name)
|
||||||
providers = [row[0] for row in query.all()]
|
return [row[0] for row in query.all()]
|
||||||
return providers
|
|
||||||
except Exception:
|
except Exception:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# 系统用户搜索
|
|
||||||
# ============================================================
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def search_system_users(cls, keyword):
|
def search_system_users(cls, keyword):
|
||||||
"""搜索系统用户(活跃状态)"""
|
"""搜索系统用户(占位)"""
|
||||||
from app.models.system import SysUser
|
return []
|
||||||
try:
|
|
||||||
query = SysUser.query.filter(SysUser.status == 'active')
|
|
||||||
if keyword:
|
|
||||||
kw = f'%{keyword}%'
|
|
||||||
query = query.filter(db.or_(
|
|
||||||
SysUser.username.ilike(kw),
|
|
||||||
SysUser.email.ilike(kw)
|
|
||||||
))
|
|
||||||
query = query.order_by(SysUser.username)
|
|
||||||
users = []
|
|
||||||
for u in query.limit(20).all():
|
|
||||||
users.append({
|
|
||||||
'value': u.username,
|
|
||||||
'email': u.email
|
|
||||||
})
|
|
||||||
return users
|
|
||||||
except Exception:
|
|
||||||
return []
|
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# 获取筛选选项(类别、类型)
|
|
||||||
# ============================================================
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_filter_options(cls):
|
def get_filter_options(cls):
|
||||||
|
"""获取筛选下拉选项"""
|
||||||
try:
|
try:
|
||||||
from app.models.base import MaterialBase
|
|
||||||
categories = db.session.query(MaterialBase.category) \
|
categories = db.session.query(MaterialBase.category) \
|
||||||
.filter(MaterialBase.category != None, MaterialBase.category != '') \
|
.filter(MaterialBase.category != None, MaterialBase.category != '') \
|
||||||
.distinct().all()
|
.distinct().all()
|
||||||
@ -229,6 +236,4 @@ class ServiceService:
|
|||||||
"types": [r[0] for r in types]
|
"types": [r[0] for r in types]
|
||||||
}
|
}
|
||||||
except Exception:
|
except Exception:
|
||||||
import traceback
|
return {"categories": [], "types": []}
|
||||||
traceback.print_exc()
|
|
||||||
return {"categories": [], "types": []}
|
|
||||||
@ -369,7 +369,7 @@ const dialogStatus = ref<'create' | 'update'>('create')
|
|||||||
const tableData = ref([])
|
const tableData = ref([])
|
||||||
const total = ref(0)
|
const total = ref(0)
|
||||||
const formRef = ref()
|
const formRef = ref()
|
||||||
const queryParams = reactive({ page: 1, pageSize: 15, keyword: '', statuses: ['在库', '借库'] })
|
const queryParams = reactive({ page: 1, pageSize: 100, keyword: '', statuses: ['在库', '借库'] })
|
||||||
const materialOptions = ref<any[]>([])
|
const materialOptions = ref<any[]>([])
|
||||||
|
|
||||||
// 打印相关变量
|
// 打印相关变量
|
||||||
|
|||||||
Reference in New Issue
Block a user