采购件入库与出库相关联

This commit is contained in:
dxc
2026-02-05 11:08:29 +08:00
parent f3b60dfc54
commit 273f20f5c3
2 changed files with 65 additions and 26 deletions

View File

@ -1,7 +1,10 @@
# app/services/inbound/buy_service.py
# 文件路径: inventory-backend/app/services/inbound/buy_service.py
from app.extensions import db
from app.models.inbound.buy import StockBuy
from app.models.base import MaterialBase
# 引入出库记录模型,用于查询流转历史
from app.models.outbound import TransOutbound
from datetime import datetime
from sqlalchemy import or_, func, text
import traceback
@ -9,6 +12,10 @@ import json
class BuyInboundService:
# ============================================================
# 1. 基础物料搜索 (供下拉框使用)
# ============================================================
@staticmethod
def search_base_material(keyword):
"""
@ -45,6 +52,9 @@ class BuyInboundService:
traceback.print_exc()
return []
# ============================================================
# 2. 新增入库逻辑
# ============================================================
@staticmethod
def handle_inbound(data):
"""
@ -73,32 +83,23 @@ class BuyInboundService:
in_qty = float(data.get('in_quantity') or 0)
u_price = float(data.get('unit_price') or 0)
# ------------------------------------------------------------------
# 1. 获取全局打印流水号 (跨表唯一)
# ------------------------------------------------------------------
seq_sql = text("SELECT nextval('global_print_seq')")
result = db.session.execute(seq_sql)
next_global_id = result.scalar()
# ------------------------------------------------------------------
# 2. 自动生成 SKU (格式: 00000001)
# ------------------------------------------------------------------
generated_sku = str(next_global_id).zfill(10)
# ------------------------------------------------------------------
# 3. 条码逻辑处理
# ------------------------------------------------------------------
final_barcode = data.get('barcode')
if not final_barcode:
final_barcode = generated_sku
# ------------------------------------------------------------------
# 4. 图片列表转 JSON 字符串处理
# ------------------------------------------------------------------
arrival_list = data.get('arrival_photo', [])
report_list = data.get('inspection_report', [])
# 确保是列表类型,防止前端传错导致报错
if not isinstance(arrival_list, list): arrival_list = []
if not isinstance(report_list, list): report_list = []
@ -127,7 +128,7 @@ class BuyInboundService:
original_link=data.get('source_link'),
detail_link=data.get('detail_link'),
# [核心修改] 将列表转为 JSON 字符串存储
# 将列表转为 JSON 字符串存储
arrival_photo=json.dumps(arrival_list),
inspection_report=json.dumps(report_list)
)
@ -142,6 +143,9 @@ class BuyInboundService:
db.session.rollback()
raise e
# ============================================================
# 3. 更新入库逻辑
# ============================================================
@staticmethod
def update_inbound(stock_id, data):
"""
@ -149,7 +153,6 @@ class BuyInboundService:
"""
try:
print(f"----- UPDATE DEBUG: ID={stock_id} -----")
stock = StockBuy.query.get(stock_id)
if not stock:
raise ValueError("记录不存在")
@ -176,7 +179,6 @@ class BuyInboundService:
if frontend_key in data:
setattr(stock, db_attr, data[frontend_key])
# [核心修改] 图片字段更新 (List -> JSON String)
if 'arrival_photo' in data:
imgs = data['arrival_photo']
if isinstance(imgs, list):
@ -197,6 +199,7 @@ class BuyInboundService:
if new_qty != old_qty:
diff = new_qty - old_qty
stock.in_quantity = new_qty
# 注意:手动修改入库量时,同步调整总库存和可用库存
stock.stock_quantity = float(stock.stock_quantity) + diff
stock.available_quantity = float(stock.available_quantity) + diff
qty_changed = True
@ -221,6 +224,9 @@ class BuyInboundService:
traceback.print_exc()
raise e
# ============================================================
# 4. 删除逻辑
# ============================================================
@staticmethod
def delete_inbound(stock_id):
"""
@ -237,13 +243,32 @@ class BuyInboundService:
db.session.rollback()
raise e
# ============================================================
# 5. [新增] 获取出库流转历史 (挂钩出库记录)
# ============================================================
@staticmethod
def get_list(page, limit, keyword=None):
def get_outbound_history(stock_id):
"""
获取分页列表
查询该入库单对应的所有出库记录
"""
try:
# 1. 查询分页数据
records = TransOutbound.query.filter_by(
source_table='stock_buy',
stock_id=stock_id
).order_by(TransOutbound.outbound_time.desc()).all()
return [r.to_dict() for r in records]
except Exception as e:
traceback.print_exc()
return []
# ============================================================
# 6. 获取列表 (含动态状态计算)
# ============================================================
@staticmethod
def get_list(page, limit, keyword=None):
try:
# 1. 联表查询StockBuy join MaterialBase
query = db.session.query(StockBuy).outerjoin(MaterialBase, StockBuy.base_id == MaterialBase.id)
if keyword:
@ -253,18 +278,17 @@ class BuyInboundService:
MaterialBase.spec_model.ilike(f'%{keyword}%'),
StockBuy.batch_number.ilike(f'%{keyword}%'),
StockBuy.serial_number.ilike(f'%{keyword}%'),
StockBuy.sku.ilike(f'%{keyword}%')
StockBuy.sku.ilike(f'%{keyword}%'),
StockBuy.supplier_name.ilike(f'%{keyword}%')
)
)
pagination = query.order_by(StockBuy.id.desc()).paginate(page=page, per_page=limit, error_out=False)
# ---------------------------------------------------------------------
# 计算总库存 (聚合)
# ---------------------------------------------------------------------
current_items = pagination.items
base_ids = list(set([item.base_id for item in current_items if item.base_id]))
# 2. 聚合统计 (计算该种物料的总库存)
stock_map = {}
if base_ids:
aggregates = db.session.query(
@ -284,7 +308,6 @@ class BuyInboundService:
if not json_str:
return []
try:
# 兼容旧数据:如果不是 JSON 格式(比如是单个 URL则包装成 list
if not json_str.startswith('['):
return [json_str]
return json.loads(json_str)
@ -293,6 +316,20 @@ class BuyInboundService:
items = []
for item in current_items:
# -------------------------------------------------------------
# [核心逻辑] 动态计算状态
# -------------------------------------------------------------
qty_in = float(item.in_quantity or 0)
qty_avail = float(item.available_quantity or 0)
# 默认使用数据库字段
current_status = item.status
# 如果有入库量但可用量为0说明已经全部出库
if qty_in > 0 and qty_avail <= 0:
current_status = '出库'
# 获取聚合数据
mat_name = item.material.name if item.material else '未知物料'
mat_spec = item.material.spec_model if item.material else ''
mat_cat = item.material.category if item.material else ''
@ -315,12 +352,15 @@ class BuyInboundService:
'barcode': item.barcode,
'serial_number': item.serial_number,
'batch_number': item.batch_number,
'status': item.status,
# 使用动态计算的状态
'status': current_status,
'inspection_status': item.inspection_status,
'qty_inbound': float(item.in_quantity or 0),
'qty_inbound': qty_in,
'qty_stock': float(item.stock_quantity or 0),
'qty_available': float(item.available_quantity or 0),
'qty_available': qty_avail,
'sum_stock': stats['total_stock'],
'sum_available': stats['total_avail'],
@ -336,7 +376,6 @@ class BuyInboundService:
'source_link': item.original_link,
'detail_link': item.detail_link,
# [核心修改] 解析 JSON 字符串为数组返回给前端
'arrival_photo': parse_img_list(item.arrival_photo),
'inspection_report': parse_img_list(item.inspection_report),

View File

@ -17,7 +17,7 @@ export default defineConfig({
// 允许局域网访问前端页面
host: '0.0.0.0',
port: 5173,
https: false, // ★ [新增] 强制开启 HTTPS否则浏览器会拦截摄像头
https: true, // ★ [新增] 强制开启 HTTPS否则浏览器会拦截摄像头
proxy: {
// 拦截所有以 /api 开头的请求
'/api': {