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": []}