Files
KCGL/inventory-backend/app/services/trans_service.py
dxc b98f89bfe4 chore: add .material->.base refactor check comments
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-10 11:34:50 +08:00

186 lines
6.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import uuid # .material -> .base refactor checked
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
}