206 lines
8.1 KiB
Python
206 lines
8.1 KiB
Python
from sqlalchemy import select, func, and_, or_, update, delete
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
from module_admin.entity.do.warehouse_receipt_do import WarehouseReceipt
|
||
from module_admin.entity.vo.warehouse_receipt_vo import WarehouseReceiptPageQueryModel, WarehouseReceiptModel
|
||
from typing import List, Optional
|
||
from datetime import datetime
|
||
|
||
|
||
class WarehouseReceiptDao:
|
||
"""
|
||
入库单数据访问层
|
||
"""
|
||
|
||
@classmethod
|
||
async def get_receipt_by_id(cls, db: AsyncSession, receipt_id: int):
|
||
"""
|
||
根据入库单ID获取入库单信息
|
||
"""
|
||
query = select(WarehouseReceipt).where(
|
||
WarehouseReceipt.receipt_id == receipt_id,
|
||
WarehouseReceipt.del_flag == '0'
|
||
)
|
||
result = await db.execute(query)
|
||
return result.scalars().first()
|
||
|
||
@classmethod
|
||
async def get_receipt_by_no(cls, db: AsyncSession, receipt_no: str):
|
||
"""
|
||
根据入库单号获取入库单信息
|
||
"""
|
||
query = select(WarehouseReceipt).where(
|
||
WarehouseReceipt.receipt_no == receipt_no,
|
||
WarehouseReceipt.del_flag == '0'
|
||
)
|
||
result = await db.execute(query)
|
||
return result.scalars().first()
|
||
|
||
@classmethod
|
||
async def get_receipt_list(cls, db: AsyncSession, query_object: WarehouseReceiptPageQueryModel, is_page: bool = False):
|
||
"""
|
||
获取入库单列表
|
||
"""
|
||
print(f"DEBUG DAO: page_num={query_object.page_num}, page_size={query_object.page_size}, is_page={is_page}")
|
||
query = select(WarehouseReceipt).where(WarehouseReceipt.del_flag == '0')
|
||
|
||
# 条件筛选
|
||
if query_object.receipt_no:
|
||
query = query.where(WarehouseReceipt.receipt_no.like(f'%{query_object.receipt_no}%'))
|
||
if query_object.client_unit:
|
||
query = query.where(WarehouseReceipt.client_unit.like(f'%{query_object.client_unit}%'))
|
||
if query_object.client_contact:
|
||
query = query.where(WarehouseReceipt.client_contact.like(f'%{query_object.client_contact}%'))
|
||
if query_object.receipt_date_start and query_object.receipt_date_end:
|
||
query = query.where(and_(
|
||
WarehouseReceipt.receipt_date >= query_object.receipt_date_start,
|
||
WarehouseReceipt.receipt_date <= query_object.receipt_date_end
|
||
))
|
||
if query_object.purpose:
|
||
query = query.where(WarehouseReceipt.purpose == query_object.purpose)
|
||
if query_object.status:
|
||
query = query.where(WarehouseReceipt.status == query_object.status)
|
||
|
||
# 排序
|
||
query = query.order_by(WarehouseReceipt.create_time.desc())
|
||
|
||
# 分页
|
||
if is_page:
|
||
offset = (query_object.page_num - 1) * query_object.page_size
|
||
query = query.offset(offset).limit(query_object.page_size)
|
||
|
||
result = await db.execute(query)
|
||
return result.scalars().all()
|
||
|
||
@classmethod
|
||
async def get_receipt_count(cls, db: AsyncSession, query_object: WarehouseReceiptPageQueryModel):
|
||
"""
|
||
获取入库单总数
|
||
"""
|
||
query = select(func.count()).select_from(WarehouseReceipt).where(WarehouseReceipt.del_flag == '0')
|
||
|
||
# 条件筛选(与列表查询保持一致)
|
||
if query_object.receipt_no:
|
||
query = query.where(WarehouseReceipt.receipt_no.like(f'%{query_object.receipt_no}%'))
|
||
if query_object.client_unit:
|
||
query = query.where(WarehouseReceipt.client_unit.like(f'%{query_object.client_unit}%'))
|
||
if query_object.client_contact:
|
||
query = query.where(WarehouseReceipt.client_contact.like(f'%{query_object.client_contact}%'))
|
||
if query_object.receipt_date_start and query_object.receipt_date_end:
|
||
query = query.where(and_(
|
||
WarehouseReceipt.receipt_date >= query_object.receipt_date_start,
|
||
WarehouseReceipt.receipt_date <= query_object.receipt_date_end
|
||
))
|
||
if query_object.purpose:
|
||
query = query.where(WarehouseReceipt.purpose == query_object.purpose)
|
||
if query_object.status:
|
||
query = query.where(WarehouseReceipt.status == query_object.status)
|
||
|
||
result = await db.execute(query)
|
||
return result.scalar()
|
||
|
||
@classmethod
|
||
async def add_receipt(cls, db: AsyncSession, receipt: WarehouseReceipt):
|
||
"""
|
||
新增入库单
|
||
"""
|
||
db.add(receipt)
|
||
await db.flush()
|
||
return receipt
|
||
|
||
@classmethod
|
||
async def edit_receipt(cls, db: AsyncSession, receipt: WarehouseReceiptModel):
|
||
"""
|
||
编辑入库单
|
||
"""
|
||
update_data = receipt.model_dump(exclude_unset=True, exclude={'receipt_id', 'samples', 'sample_count'})
|
||
stmt = update(WarehouseReceipt).where(WarehouseReceipt.receipt_id == receipt.receipt_id).values(**update_data)
|
||
await db.execute(stmt)
|
||
await db.flush()
|
||
|
||
@classmethod
|
||
async def delete_receipt(cls, db: AsyncSession, receipt_ids: List[int]):
|
||
"""
|
||
删除入库单(逻辑删除)
|
||
"""
|
||
stmt = update(WarehouseReceipt).where(WarehouseReceipt.receipt_id.in_(receipt_ids)).values(del_flag='1')
|
||
await db.execute(stmt)
|
||
await db.flush()
|
||
|
||
@classmethod
|
||
async def generate_receipt_no(cls, db: AsyncSession, year: int, retry_count: int = 0) -> str:
|
||
"""
|
||
生成入库单号
|
||
格式:2025RJ001 (年份+RJ+三位序号)
|
||
retry_count: 重试次数,用于生成不同的序号
|
||
|
||
注意:需要同时考虑旧格式(2026内检xxx)和新格式(2026RJxxx)
|
||
使用数据库行锁防止并发冲突
|
||
"""
|
||
type_code = 'RJ' # 入库拼音首字母,避免中文
|
||
|
||
try:
|
||
# 使用 FOR UPDATE 锁定查询,防止并发冲突
|
||
# 先查询当前年份最大的入库单号(包括新旧两种格式)
|
||
from sqlalchemy import text
|
||
|
||
# 使用原生 SQL 带 FOR UPDATE 锁定
|
||
lock_sql = text(f"""
|
||
SELECT receipt_no FROM warehouse_receipt
|
||
WHERE (receipt_no LIKE '{year}{type_code}%' OR receipt_no LIKE '{year}内检%')
|
||
AND del_flag = '0'
|
||
ORDER BY receipt_id DESC
|
||
LIMIT 1
|
||
FOR UPDATE
|
||
""")
|
||
|
||
try:
|
||
result = await db.execute(lock_sql)
|
||
max_no_row = result.fetchone()
|
||
max_no = max_no_row[0] if max_no_row else None
|
||
except Exception:
|
||
# 如果 FOR UPDATE 失败(如某些数据库不支持),回退到普通查询
|
||
query = select(WarehouseReceipt.receipt_no).where(
|
||
or_(
|
||
WarehouseReceipt.receipt_no.like(f'{year}{type_code}%'),
|
||
WarehouseReceipt.receipt_no.like(f'{year}内检%')
|
||
),
|
||
WarehouseReceipt.del_flag == '0'
|
||
).order_by(WarehouseReceipt.receipt_id.desc()).limit(1)
|
||
result = await db.execute(query)
|
||
max_no = result.scalar()
|
||
|
||
if max_no:
|
||
# 提取序号部分
|
||
try:
|
||
# 新格式: 2026RJ001 -> 提取 001
|
||
if f'{year}{type_code}' in max_no:
|
||
seq_str = max_no[len(str(year)) + len(type_code):]
|
||
seq = int(seq_str)
|
||
# 旧格式: 2026内检001 -> 提取 001
|
||
elif f'{year}内检' in max_no:
|
||
seq_str = max_no[len(str(year)) + 2:] # "内检" 是2个字符
|
||
seq = int(seq_str)
|
||
else:
|
||
seq = 0
|
||
except (ValueError, IndexError):
|
||
seq = 0
|
||
else:
|
||
seq = 0
|
||
|
||
# 如果有重试,增加序号避免冲突
|
||
seq = seq + 1 + retry_count
|
||
|
||
return f'{year}{type_code}{seq:03d}'
|
||
|
||
except Exception as e:
|
||
# 如果出错,返回带重试序号的默认编号
|
||
import time
|
||
timestamp = int(time.time()) % 1000 + retry_count
|
||
return f'{year}{type_code}{timestamp:03d}'
|
||
|
||
|
||
|
||
|
||
|
||
|