Files
KCGL/inventory-backend/app/services/inbound/buy_service.py

184 lines
7.7 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.

from app.extensions import db
from app.models.material import MaterialBase
from app.models.stock import StockBuy
from datetime import datetime
import traceback
class BuyInboundService:
@staticmethod
def handle_inbound(data):
"""新增入库:自动关联/创建基础信息 + 创建库存记录"""
try:
# 0. 基础校验
if not data.get('spec_model') or not data.get('material_name'):
raise ValueError("缺少必要的物料名称或规格型号")
# 1. 关联逻辑:通过规格型号(spec_model)查找基础库
material = MaterialBase.query.filter_by(spec_model=data['spec_model']).first()
# 如果不存在,则新建 MaterialBase
if not material:
material = MaterialBase(
name=data['material_name'],
spec_model=data['spec_model'],
category=data.get('category'),
material_type='采购件',
unit=data.get('unit'),
visibility_level=data.get('visibility_level', 0),
manual_link=data.get('manual_link'),
product_image=data.get('product_image'),
is_enabled=True
)
db.session.add(material)
db.session.flush() # 立即执行,拿到 material.id
# 2. 处理日期 (兼容性处理)
in_date_val = None
if data.get('in_date'):
try:
in_date_val = datetime.strptime(data['in_date'], '%Y-%m-%d %H:%M:%S').date()
except ValueError:
try:
in_date_val = datetime.strptime(data['in_date'], '%Y-%m-%d').date()
except ValueError:
in_date_val = datetime.utcnow().date()
# --- 修改部分:增加字段兼容性,确保前端传参能被正确读取 ---
in_qty = float(data.get('in_quantity') or data.get('qty_inbound') or 0)
u_price = float(data.get('unit_price') or data.get('price_unit') or 0)
# ---------------------------------------------------
# 3. 创建 StockBuy
new_stock = StockBuy(
base_id=material.id,
sku=data.get('sku'),
in_date=in_date_val,
serial_number=data.get('serial_number'),
batch_number=data.get('batch_number'),
status='在库',
inspection_status=data.get('inspection_status'),
in_quantity=in_qty,
stock_quantity=in_qty,
available_quantity=in_qty,
warehouse_location=data.get('warehouse_location') or data.get('warehouse_loc'),
unit_price=u_price,
total_price=in_qty * u_price,
currency=data.get('currency', 'CNY'),
exchange_rate=data.get('exchange_rate', 1.0),
supplier_name=data.get('supplier_name'),
buyer_name=data.get('buyer_name'),
buyer_email=data.get('buyer_email'),
original_link=data.get('original_link'),
detail_link=data.get('detail_link'),
arrival_photo=data.get('arrival_photo')
)
db.session.add(new_stock)
db.session.commit()
return new_stock
except Exception as e:
db.session.rollback()
raise e
@staticmethod
def update_inbound(stock_id, data):
"""更新入库:支持级联更新基础信息 + 自动重算总价"""
try:
stock = StockBuy.query.get(stock_id)
if not stock:
raise ValueError("记录不存在")
# 1. 更新普通字段 (增加对 warehouse_loc 的兼容)
if 'serial_number' in data: stock.serial_number = data['serial_number']
if 'batch_number' in data: stock.batch_number = data['batch_number']
if 'warehouse_location' in data: stock.warehouse_location = data['warehouse_location']
if 'warehouse_loc' in data: stock.warehouse_location = data['warehouse_loc']
if 'supplier_name' in data: stock.supplier_name = data['supplier_name']
if 'status' in data: stock.status = data['status']
if 'inspection_status' in data: stock.inspection_status = data['inspection_status']
if 'arrival_photo' in data: stock.arrival_photo = data['arrival_photo']
if 'remark' in data: stock.remark = data['remark']
# 2. 级联更新基础信息 (MaterialBase)
if stock.material:
if 'material_name' in data: stock.material.name = data['material_name']
if 'category' in data: stock.material.category = data['category']
if 'unit' in data: stock.material.unit = data['unit']
# 3. 核心逻辑:数量与价格联动 (增加对前端返回字段名 qty_inbound 和 price_unit 的识别)
qty_changed = False
price_changed = False
# (A) 数量变更 -> 更新库存和可用量
new_qty_input = data.get('in_quantity') or data.get('qty_inbound')
if new_qty_input is not None:
new_qty = float(new_qty_input)
old_qty = float(stock.in_quantity)
diff = new_qty - old_qty
if diff != 0:
stock.in_quantity = new_qty
stock.stock_quantity = float(stock.stock_quantity) + diff
stock.available_quantity = float(stock.available_quantity) + diff
qty_changed = True
# (B) 单价变更
new_price_input = data.get('unit_price') or data.get('price_unit')
if new_price_input is not None:
new_price = float(new_price_input)
if new_price != float(stock.unit_price):
stock.unit_price = new_price
price_changed = True
# (C) 重算总价
if qty_changed or price_changed:
stock.total_price = float(stock.in_quantity) * float(stock.unit_price)
db.session.commit()
return stock
except Exception as e:
db.session.rollback()
raise e
@staticmethod
def delete_inbound(stock_id):
"""删除逻辑孤儿策略如果MaterialBase无其他引用则一并删除"""
try:
stock = StockBuy.query.get(stock_id)
if not stock:
raise ValueError("记录不存在")
# 1. 记下 base_id
material_id = stock.base_id
# 2. 删除库存记录
db.session.delete(stock)
db.session.flush()
# 3. 检查是否还有残留
remaining_count = StockBuy.query.filter_by(base_id=material_id).count()
if remaining_count == 0:
print(f"触发级联删除: MaterialBase ID {material_id} 已无关联,执行清理。")
material = MaterialBase.query.get(material_id)
if material:
db.session.delete(material)
db.session.commit()
return True
except Exception as e:
db.session.rollback()
print(f"删除失败: {e}")
raise e
@staticmethod
def get_list(page, limit):
try:
pagination = StockBuy.query.order_by(StockBuy.id.desc()).paginate(page=page, per_page=limit)
items = [item.to_dict() for item in pagination.items]
return {"total": pagination.total, "items": items}
except Exception as e:
print(f"查询列表失败: {e}")
return {"total": 0, "items": []}