借库逻辑实现
This commit is contained in:
@ -0,0 +1,185 @@
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from app.extensions import db
|
||||
from app.models.transaction import TransBorrow
|
||||
from app.models.inbound.buy import StockBuy
|
||||
from app.models.inbound.semi import StockSemi
|
||||
from app.models.inbound.product import StockProduct
|
||||
from sqlalchemy import desc, func
|
||||
|
||||
|
||||
class TransService:
|
||||
|
||||
@staticmethod
|
||||
def generate_borrow_no():
|
||||
"""
|
||||
生成借用单号: BOR-yyyyMMdd-0001 (按日流水)
|
||||
逻辑:统计当天已存在的不同借用单号数量,+1 作为新序号
|
||||
"""
|
||||
now = datetime.now()
|
||||
date_str = now.strftime('%Y%m%d')
|
||||
prefix = f"BOR-{date_str}-"
|
||||
|
||||
# 使用 count distinct 来计算当天有多少个不同的借用单 (因为一单多货会占多行)
|
||||
count = db.session.query(func.count(func.distinct(TransBorrow.borrow_no))) \
|
||||
.filter(TransBorrow.borrow_no.like(f"{prefix}%")).scalar()
|
||||
|
||||
sequence = count + 1
|
||||
return f"{prefix}{sequence:04d}"
|
||||
|
||||
@staticmethod
|
||||
def create_borrow(data, operator_name='System'):
|
||||
"""
|
||||
借库逻辑:减少可用库存,不减总库存
|
||||
"""
|
||||
items = data.get('items', [])
|
||||
borrower_name = data.get('borrower_name')
|
||||
signature = data.get('signature_path') # 借用人签字
|
||||
|
||||
if not items: raise ValueError("物品列表为空")
|
||||
if not borrower_name: raise ValueError("请输入借用人")
|
||||
if not signature: raise ValueError("借用人必须签字")
|
||||
|
||||
borrow_no = TransService.generate_borrow_no()
|
||||
model_map = {'stock_buy': StockBuy, 'stock_semi': StockSemi, 'stock_product': StockProduct}
|
||||
|
||||
try:
|
||||
for item in items:
|
||||
source_table = item.get('source_table')
|
||||
stock_id = item.get('id')
|
||||
qty = float(item.get('out_quantity', 0))
|
||||
|
||||
ModelClass = model_map.get(source_table)
|
||||
if not ModelClass: continue
|
||||
|
||||
stock = ModelClass.query.with_for_update().get(stock_id)
|
||||
if not stock: raise ValueError(f"库存不存在 ID:{stock_id}")
|
||||
|
||||
if float(stock.available_quantity) < qty:
|
||||
raise ValueError(f"SKU {stock.sku} 可用库存不足")
|
||||
|
||||
# 1. 冻结库存 (只减可用)
|
||||
stock.available_quantity = float(stock.available_quantity) - qty
|
||||
|
||||
# 2. 创建借用单
|
||||
record = TransBorrow(
|
||||
borrow_no=borrow_no,
|
||||
sku=stock.sku,
|
||||
source_table=source_table,
|
||||
stock_id=stock.id,
|
||||
barcode=stock.barcode,
|
||||
quantity=qty,
|
||||
borrower_name=borrower_name,
|
||||
borrow_signature=signature,
|
||||
remark=data.get('remark'),
|
||||
expected_return_time=data.get('expected_return_time'),
|
||||
status='borrowed',
|
||||
is_returned=False
|
||||
)
|
||||
db.session.add(record)
|
||||
|
||||
db.session.commit()
|
||||
return borrow_no
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
raise e
|
||||
|
||||
@staticmethod
|
||||
def scan_for_return(barcode):
|
||||
"""
|
||||
扫码还库:查找未归还记录,并返回当前物品的库位
|
||||
"""
|
||||
records = TransBorrow.query.filter_by(barcode=barcode, is_returned=False).all()
|
||||
if not records:
|
||||
return None
|
||||
|
||||
# 取第一条未还记录
|
||||
record = records[0]
|
||||
|
||||
# 获取当前库存表中的实时库位
|
||||
current_location = ""
|
||||
model_map = {'stock_buy': StockBuy, 'stock_semi': StockSemi, 'stock_product': StockProduct}
|
||||
ModelClass = model_map.get(record.source_table)
|
||||
|
||||
if ModelClass:
|
||||
stock = ModelClass.query.get(record.stock_id)
|
||||
if stock:
|
||||
current_location = stock.warehouse_location
|
||||
|
||||
res_dict = record.to_dict()
|
||||
res_dict['current_location'] = current_location # 用于前端对比和预填
|
||||
return res_dict
|
||||
|
||||
@staticmethod
|
||||
def process_return(data, operator_name):
|
||||
"""
|
||||
还库逻辑:
|
||||
1. 恢复可用库存
|
||||
2. 更新库位 (如果有变动)
|
||||
3. 记录库管签字
|
||||
"""
|
||||
items = data.get('items', [])
|
||||
signature = data.get('signature_path') # 库管签字
|
||||
|
||||
if not items: raise ValueError("还库列表为空")
|
||||
if not signature: raise ValueError("库管必须签字确认")
|
||||
|
||||
model_map = {'stock_buy': StockBuy, 'stock_semi': StockSemi, 'stock_product': StockProduct}
|
||||
|
||||
try:
|
||||
for item in items:
|
||||
borrow_id = item.get('id')
|
||||
# 前端如果没有填 return_location,应该在提交前处理好,或者这里做 fallback
|
||||
# 这里假设前端传来的 return_location 就是最终要保存的库位
|
||||
final_location = item.get('return_location')
|
||||
|
||||
record = TransBorrow.query.with_for_update().get(borrow_id)
|
||||
if not record or record.is_returned:
|
||||
continue
|
||||
|
||||
ModelClass = model_map.get(record.source_table)
|
||||
if ModelClass:
|
||||
stock = ModelClass.query.with_for_update().get(record.stock_id)
|
||||
if stock:
|
||||
# 1. 恢复可用库存
|
||||
stock.available_quantity = float(stock.available_quantity) + float(record.quantity)
|
||||
|
||||
# 2. 更新库位 (如果提供了有效值)
|
||||
if final_location:
|
||||
stock.warehouse_location = final_location
|
||||
|
||||
# 3. 更新借用单状态
|
||||
record.is_returned = True
|
||||
record.status = 'returned'
|
||||
record.return_time = datetime.now()
|
||||
record.return_operator = operator_name
|
||||
record.return_signature = signature
|
||||
record.return_location = final_location
|
||||
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
raise e
|
||||
|
||||
@staticmethod
|
||||
def get_records(page=1, limit=10, status='all', keyword=None):
|
||||
q = TransBorrow.query
|
||||
if status == 'borrowed':
|
||||
q = q.filter(TransBorrow.is_returned == False)
|
||||
elif status == 'returned':
|
||||
q = q.filter(TransBorrow.is_returned == True)
|
||||
|
||||
if keyword:
|
||||
q = q.filter(TransBorrow.borrower_name.ilike(f'%{keyword}%') |
|
||||
TransBorrow.sku.ilike(f'%{keyword}%') |
|
||||
TransBorrow.borrow_no.ilike(f'%{keyword}%'))
|
||||
|
||||
q = q.order_by(desc(TransBorrow.borrow_time))
|
||||
pagination = q.paginate(page=page, per_page=limit, error_out=False)
|
||||
|
||||
return {
|
||||
'items': [r.to_dict() for r in pagination.items],
|
||||
'total': pagination.total,
|
||||
'page': page,
|
||||
'limit': limit
|
||||
}
|
||||
Reference in New Issue
Block a user