采购件入库与出库相关联
This commit is contained in:
@ -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),
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ export default defineConfig({
|
||||
// 允许局域网访问前端页面
|
||||
host: '0.0.0.0',
|
||||
port: 5173,
|
||||
https: false, // ★ [新增] 强制开启 HTTPS,否则浏览器会拦截摄像头
|
||||
https: true, // ★ [新增] 强制开启 HTTPS,否则浏览器会拦截摄像头
|
||||
proxy: {
|
||||
// 拦截所有以 /api 开头的请求
|
||||
'/api': {
|
||||
|
||||
Reference in New Issue
Block a user