ETest-Vue-FastAPI/ruoyi-fastapi-backend/module_admin/service/warehouse_receipt_service.py

330 lines
15 KiB
Python
Raw Normal View History

2025-12-19 10:12:59 +08:00
from sqlalchemy.ext.asyncio import AsyncSession
from module_admin.dao.warehouse_receipt_dao import WarehouseReceiptDao
from module_admin.dao.warehouse_sample_dao import WarehouseSampleDao
2026-04-15 19:06:01 +08:00
from module_admin.system.dao.contact_dao import ContactDao
from module_admin.system.entity.vo.contact_vo import ContactModel
2025-12-19 10:12:59 +08:00
from module_admin.entity.do.warehouse_receipt_do import WarehouseReceipt
from module_admin.entity.do.warehouse_sample_do import WarehouseSample
from module_admin.entity.vo.warehouse_receipt_vo import (
WarehouseReceiptModel, WarehouseReceiptPageQueryModel,
AddWarehouseReceiptModel, EditWarehouseReceiptModel, DeleteWarehouseReceiptModel
)
from module_admin.entity.vo.common_vo import CrudResponseModel
from exceptions.exception import ServiceException
from utils.common_util import CamelCaseUtil
from datetime import datetime
class WarehouseReceiptService:
"""
入库单业务逻辑层
"""
2026-04-15 19:06:01 +08:00
@classmethod
async def _auto_create_contact(cls, db: AsyncSession, client_unit: str, client_contact: str, create_by: str):
"""
自动创建联系人如果不存在
防止重复同单位+同名视为同一联系人
"""
if not client_contact:
return
# 检查是否已存在
existing = await ContactDao.get_contact_by_name_company(
db,
name=client_contact,
company=client_unit or None
)
if not existing:
# 创建新联系人
contact = ContactModel(
name=client_contact,
company=client_unit or None,
create_by=create_by
)
await ContactDao.add_contact_dao(db, contact)
# 不提交,由外层事务统一提交
2025-12-19 10:12:59 +08:00
@classmethod
async def get_receipt_list(cls, db: AsyncSession, query_object: WarehouseReceiptPageQueryModel, is_page: bool = False):
"""
获取入库单列表
"""
try:
receipt_list = await WarehouseReceiptDao.get_receipt_list(db, query_object, is_page)
# 转换为字典并添加样品数量
result_list = []
for receipt in receipt_list:
receipt_dict = CamelCaseUtil.transform_result(receipt)
# 获取样品数量
sample_count = await WarehouseSampleDao.get_sample_count_by_receipt(db, receipt.receipt_id)
receipt_dict['sample_count'] = sample_count if sample_count else 0
result_list.append(receipt_dict)
if is_page:
total = await WarehouseReceiptDao.get_receipt_count(db, query_object)
return {'rows': result_list, 'total': total if total else 0}
else:
return result_list
except Exception as e:
raise ServiceException(message=f'获取入库单列表失败: {str(e)}')
2025-12-19 10:12:59 +08:00
@classmethod
async def get_receipt_detail(cls, db: AsyncSession, receipt_id: int):
"""
获取入库单详情
"""
2026-01-04 23:56:11 +08:00
import sys
print(f"DEBUG Service START: get_receipt_detail called with receipt_id={receipt_id}", flush=True)
sys.stdout.flush()
try:
receipt = await WarehouseReceiptDao.get_receipt_by_id(db, receipt_id)
if not receipt:
raise ServiceException(message='入库单不存在')
2026-01-04 23:56:11 +08:00
print(f"DEBUG Service: receipt found, receipt.receipt_id={receipt.receipt_id}, receipt_no={receipt.receipt_no}", flush=True)
sys.stdout.flush()
receipt_dict = CamelCaseUtil.transform_result(receipt)
# 获取样品列表
from module_admin.entity.vo.warehouse_sample_vo import WarehouseSamplePageQueryModel
2026-01-04 23:56:11 +08:00
# 使用 camelCase 字段名来构造查询对象
sample_query = WarehouseSamplePageQueryModel(receiptId=receipt.receipt_id, pageNum=1, pageSize=1000)
# 添加日志
2026-01-04 23:56:11 +08:00
print(f"DEBUG: 查询入库单 {receipt_id} 的样品receipt.receipt_id={receipt.receipt_id}", flush=True)
print(f"DEBUG: sample_query.receipt_id={sample_query.receipt_id}", flush=True)
sys.stdout.flush()
samples = await WarehouseSampleDao.get_sample_list(db, sample_query, is_page=False)
# 添加日志
2026-01-04 23:56:11 +08:00
print(f"DEBUG: 查询到 {len(samples) if samples else 0} 个样品", flush=True)
if samples:
for sample in samples[:3]: # 只打印前3个
2026-01-04 23:56:11 +08:00
print(f"DEBUG: 样品 ID={sample.sample_id}, receipt_id={sample.receipt_id}, SN={sample.sample_sn}", flush=True)
sys.stdout.flush()
# 转换样品列表
samples_list = [CamelCaseUtil.transform_result(sample) for sample in samples] if samples else []
2026-04-15 19:06:01 +08:00
# 处理日期格式,确保可 JSON 序列化
def serialize_dates(obj):
if isinstance(obj, dict):
for key, value in obj.items():
if hasattr(value, 'isoformat'): # datetime/date 对象
obj[key] = value.isoformat()
elif isinstance(value, list):
for item in value:
serialize_dates(item)
return obj
receipt_dict = serialize_dates(receipt_dict)
samples_list = [serialize_dates(sample) for sample in samples_list]
receipt_dict['samples'] = samples_list
receipt_dict['sample_count'] = len(samples_list)
return receipt_dict
except ServiceException:
raise
except Exception as e:
2026-01-04 23:56:11 +08:00
print(f"DEBUG Service ERROR: {str(e)}", flush=True)
raise ServiceException(message=f'获取入库单详情失败: {str(e)}')
2025-12-19 10:12:59 +08:00
@classmethod
async def add_receipt(cls, db: AsyncSession, receipt_model: AddWarehouseReceiptModel):
"""
2026-04-15 19:06:01 +08:00
新增入库单同时创建关联的样品自动创建联系人自动生成单号
支持重试机制防止单号重复
2025-12-19 10:12:59 +08:00
"""
2026-04-15 19:06:01 +08:00
max_retries = 5
last_error = None
for attempt in range(max_retries):
try:
# 自动生成入库单号(带重试序号)
receipt_no = await WarehouseReceiptDao.generate_receipt_no(db, datetime.now().year, attempt)
receipt_model.receipt_no = receipt_no
2026-04-15 19:06:01 +08:00
# 自动创建联系人(如果不存在)
await cls._auto_create_contact(
db,
receipt_model.client_unit,
receipt_model.client_contact,
receipt_model.create_by
)
2026-04-15 19:06:01 +08:00
# 获取样品数据
samples_data = receipt_model.samples if receipt_model.samples else []
# 创建入库单对象排除samples字段
receipt_dict = receipt_model.model_dump(exclude_unset=True, exclude={'samples'})
receipt = WarehouseReceipt(**receipt_dict)
receipt.create_time = datetime.now()
receipt.update_time = datetime.now()
# 保存入库单
await WarehouseReceiptDao.add_receipt(db, receipt)
await db.flush() # 刷新以获取receipt_id
# 创建样品对象并关联到入库单
for sample_data in samples_data:
sample_dict = sample_data.model_dump(exclude_unset=True)
sample = WarehouseSample(**sample_dict)
sample.receipt_id = receipt.receipt_id
sample.receipt_no = receipt.receipt_no
sample.create_by = receipt.create_by
sample.create_time = datetime.now()
sample.update_time = datetime.now()
sample.del_flag = '0'
await WarehouseSampleDao.add_sample(db, sample)
await db.flush()
# 成功,返回结果
return CrudResponseModel(
is_success=True,
message=f'新增入库单成功',
data={'receipt_no': receipt.receipt_no}
)
except Exception as e:
last_error = e
error_msg = str(e)
# 检查是否是重复键错误
if 'Duplicate entry' in error_msg and 'receipt_no' in error_msg:
# 单号重复,继续重试
await db.rollback()
if attempt < max_retries - 1:
continue
# 其他错误,直接抛出
await db.rollback()
raise ServiceException(message=f'新增入库单失败: {error_msg}')
# 所有重试都失败
raise ServiceException(message=f'新增入库单失败: 无法生成唯一单号,请重试')
2025-12-19 10:12:59 +08:00
@classmethod
async def edit_receipt(cls, db: AsyncSession, receipt_model: EditWarehouseReceiptModel):
"""
2026-04-15 19:06:01 +08:00
编辑入库单同时更新关联的样品自动创建联系人
2025-12-19 10:12:59 +08:00
"""
try:
# 检查入库单是否存在
receipt = await WarehouseReceiptDao.get_receipt_by_id(db, receipt_model.receipt_id)
if not receipt:
raise ServiceException(message='入库单不存在')
2025-12-19 10:12:59 +08:00
2026-04-15 19:06:01 +08:00
# 自动创建联系人(如果不存在)
await cls._auto_create_contact(
db,
receipt_model.client_unit,
receipt_model.client_contact,
receipt_model.update_by
)
# 如果修改了入库单号,检查新单号是否已存在
if receipt_model.receipt_no and receipt_model.receipt_no != receipt.receipt_no:
existing = await WarehouseReceiptDao.get_receipt_by_no(db, receipt_model.receipt_no)
if existing:
raise ServiceException(message=f'入库单号【{receipt_model.receipt_no}】已存在')
2025-12-19 10:12:59 +08:00
# 更新入库单基本信息
receipt_model.update_time = datetime.now()
await WarehouseReceiptDao.edit_receipt(db, receipt_model)
2025-12-19 10:12:59 +08:00
# 如果提供了样品列表,则同步更新样品
if receipt_model.samples is not None:
# 获取现有样品列表
from module_admin.entity.vo.warehouse_sample_vo import WarehouseSamplePageQueryModel
2026-01-04 23:56:11 +08:00
# 使用 camelCase 字段名来构造查询对象
sample_query = WarehouseSamplePageQueryModel(receiptId=receipt_model.receipt_id, pageNum=1, pageSize=1000)
existing_samples = await WarehouseSampleDao.get_sample_list(db, sample_query, is_page=False)
existing_sample_ids = {sample.sample_id for sample in existing_samples} if existing_samples else set()
2025-12-19 10:12:59 +08:00
# 处理前端传来的样品
submitted_sample_ids = set()
for sample_data in receipt_model.samples:
sample_dict = sample_data.model_dump(exclude_unset=True, by_alias=True)
# 如果有sample_id说明是更新现有样品
if 'sampleId' in sample_dict and sample_dict['sampleId']:
sample_id = sample_dict['sampleId']
submitted_sample_ids.add(sample_id)
# 更新样品
from module_admin.entity.vo.warehouse_sample_vo import EditWarehouseSampleModel
edit_sample = EditWarehouseSampleModel(**sample_dict)
edit_sample.update_time = datetime.now()
edit_sample.update_by = receipt_model.update_by
await WarehouseSampleDao.edit_sample(db, edit_sample)
else:
# 新增样品 - 转换为snake_case用于创建DO对象
sample_dict_snake = sample_data.model_dump(exclude_unset=True)
sample = WarehouseSample(**sample_dict_snake)
sample.receipt_id = receipt_model.receipt_id
sample.receipt_no = receipt_model.receipt_no or receipt.receipt_no
sample.create_by = receipt_model.update_by
sample.create_time = datetime.now()
sample.update_time = datetime.now()
await WarehouseSampleDao.add_sample(db, sample)
# 删除前端没有提交的样品(逻辑删除)
samples_to_delete = existing_sample_ids - submitted_sample_ids
if samples_to_delete:
await WarehouseSampleDao.delete_sample(db, list(samples_to_delete))
2025-12-19 10:12:59 +08:00
await db.commit()
2025-12-19 10:12:59 +08:00
return CrudResponseModel(is_success=True, message='更新成功')
except ServiceException:
await db.rollback()
raise
except Exception as e:
await db.rollback()
raise ServiceException(message=f'更新入库单失败: {str(e)}')
2025-12-19 10:12:59 +08:00
@classmethod
async def delete_receipt(cls, db: AsyncSession, delete_model: DeleteWarehouseReceiptModel):
"""
删除入库单同时删除关联的样品
"""
try:
receipt_ids = [int(id_str) for id_str in delete_model.receipt_ids.split(',')]
# 检查入库单是否存在
for receipt_id in receipt_ids:
receipt = await WarehouseReceiptDao.get_receipt_by_id(db, receipt_id)
if not receipt:
raise ServiceException(message=f'入库单ID【{receipt_id}】不存在')
# 删除入库单(级联删除样品)
await WarehouseReceiptDao.delete_receipt(db, receipt_ids)
await db.commit()
return CrudResponseModel(is_success=True, message='删除成功')
except ServiceException:
await db.rollback()
raise
except Exception as e:
await db.rollback()
raise ServiceException(message=f'删除入库单失败: {str(e)}')
2025-12-19 10:12:59 +08:00
@classmethod
async def generate_receipt_no(cls, db: AsyncSession):
"""
生成入库单号
"""
try:
year = datetime.now().year
receipt_no = await WarehouseReceiptDao.generate_receipt_no(db, year)
return {'receiptNo': receipt_no}
except Exception as e:
raise ServiceException(message=f'生成入库单号失败: {str(e)}')
2025-12-19 10:12:59 +08:00