diff --git a/inventory-backend/app/api/v1/stock/adjustment.py b/inventory-backend/app/api/v1/stock/adjustment.py index a319d9e..1986821 100644 --- a/inventory-backend/app/api/v1/stock/adjustment.py +++ b/inventory-backend/app/api/v1/stock/adjustment.py @@ -2,7 +2,7 @@ from flask import Blueprint, request, jsonify from flask_jwt_extended import jwt_required, get_jwt_identity, get_jwt from app.utils.decorators import permission_required -from app.extensions import db +from app.extensions import db, beijing_time from app.models.stock.adjustment import StockAdjustment from app.models.base import MaterialBase from app.models.inbound.buy import StockBuy @@ -351,7 +351,7 @@ def import_from_stocktake(): # 生成调整单号 order_no = generate_order_no() - # 创建调整单 + # 创建调整单(使用北京时间) adjustment = StockAdjustment( order_no=order_no, base_id=base_id, @@ -363,7 +363,8 @@ def import_from_stocktake(): adjust_quantity=adjust_quantity, reason=reason, status='pending', - operator=operator + operator=operator, + create_time=beijing_time() ) db.session.add(adjustment) count += 1 @@ -374,3 +375,53 @@ def import_from_stocktake(): except Exception as e: db.session.rollback() return jsonify({'code': 500, 'msg': f'导入失败: {str(e)}'}), 500 + + +# -------------------------------------------------------- +# 7. 处理调整单(关联入库SKU或出库单号) +# POST /api/v1/stock/adjustment//process +# -------------------------------------------------------- +@adjustment_bp.route('//process', methods=['POST']) +@jwt_required() +@permission_required('stock_adjustment:operation') +def process_adjustment(id): + """处理待处理的调整单,关联入库SKU或出库单号""" + identity = get_jwt_identity() + operator = identity.get('username', 'system') if isinstance(identity, dict) else str(identity) + if not operator or operator == '0': + operator = identity if identity else 'system' + + data = request.get_json() + if not data: + return jsonify({'code': 400, 'msg': '缺少参数'}), 400 + + linked_sku = data.get('linked_sku', '') + linked_outbound_no = data.get('linked_outbound_no', '') + + try: + adjustment = StockAdjustment.query.get(id) + if not adjustment: + return jsonify({'code': 404, 'msg': '调整单不存在'}), 404 + + if adjustment.status != 'pending': + return jsonify({'code': 400, 'msg': '只能处理待处理状态的调整单'}), 400 + + # 根据调整类型校验必填项 + if adjustment.adjust_type == 'profit': + if not linked_sku: + return jsonify({'code': 400, 'msg': '盘盈调整必须关联入库SKU'}), 400 + adjustment.linked_sku = linked_sku + elif adjustment.adjust_type == 'loss': + if not linked_outbound_no: + return jsonify({'code': 400, 'msg': '盘亏调整必须关联出库单号'}), 400 + adjustment.linked_outbound_no = linked_outbound_no + + adjustment.status = 'completed' + adjustment.operator = operator + + db.session.commit() + return jsonify({'code': 200, 'msg': '处理成功', 'data': adjustment.to_dict()}) + + except Exception as e: + db.session.rollback() + return jsonify({'code': 500, 'msg': f'处理失败: {str(e)}'}), 500 diff --git a/inventory-backend/app/models/stock/adjustment.py b/inventory-backend/app/models/stock/adjustment.py index cf1cb1b..cf1c50c 100644 --- a/inventory-backend/app/models/stock/adjustment.py +++ b/inventory-backend/app/models/stock/adjustment.py @@ -34,6 +34,10 @@ class StockAdjustment(db.Model): reason = db.Column(db.String(500), nullable=False) # 状态:'pending' 待处理 / 'completed' 已完成 / 'cancelled' 已取消 status = db.Column(db.String(20), default='pending') + # 关联入库SKU(盘盈时填写) + linked_sku = db.Column(db.String(100), comment='关联入库SKU(盘盈时填写)') + # 关联出库单号(盘亏时填写) + linked_outbound_no = db.Column(db.String(100), comment='关联出库单号(盘亏时填写)') # 操作人/经办人 operator = db.Column(db.String(100)) # 创建时间 @@ -56,6 +60,8 @@ class StockAdjustment(db.Model): 'adjust_quantity': float(self.adjust_quantity or 0), 'reason': self.reason, 'status': self.status, + 'linked_sku': self.linked_sku, + 'linked_outbound_no': self.linked_outbound_no, 'operator': self.operator, 'create_time': self.create_time.strftime('%Y-%m-%d %H:%M:%S') if self.create_time else None, 'update_time': self.update_time.strftime('%Y-%m-%d %H:%M:%S') if self.update_time else None, diff --git a/inventory-web/src/views/outbound/create.vue b/inventory-web/src/views/outbound/create.vue index 76911ed..4078237 100644 --- a/inventory-web/src/views/outbound/create.vue +++ b/inventory-web/src/views/outbound/create.vue @@ -95,6 +95,7 @@ + diff --git a/inventory-web/src/views/stock/adjustment/index.vue b/inventory-web/src/views/stock/adjustment/index.vue index 8a95a73..90fd286 100644 --- a/inventory-web/src/views/stock/adjustment/index.vue +++ b/inventory-web/src/views/stock/adjustment/index.vue @@ -43,6 +43,18 @@ + + + + + + + + + @@ -173,6 +193,30 @@ + + + + + + + + + + {{ processForm.adjust_type === 'profit' ? '盘盈' : '盘亏' }} + + + + + + + + @@ -233,6 +277,68 @@ const selectedReviewRows = ref([]) const reviewTableRef = ref() const importLoading = ref(false) +// 处理调整单 +const showProcessDialog = ref(false) +const processLoading = ref(false) +const processForm = ref({ + id: null as number | null, + order_no: '', + adjust_type: '', + linked_value: '' +}) + +// 打开处理弹窗 +function openProcessDialog(row: any) { + processForm.value = { + id: row.id, + order_no: row.order_no, + adjust_type: row.adjust_type, + linked_value: '' + } + showProcessDialog.value = true +} + +// 提交处理 +async function handleProcessSubmit() { + if (!processForm.value.id) return + + if (processForm.value.adjust_type === 'profit' && !processForm.value.linked_value) { + ElMessage.warning('请输入已入库的SKU') + return + } + if (processForm.value.adjust_type === 'loss' && !processForm.value.linked_value) { + ElMessage.warning('请输入对应的出库单号') + return + } + + processLoading.value = true + try { + const data: any = {} + if (processForm.value.adjust_type === 'profit') { + data.linked_sku = processForm.value.linked_value + } else { + data.linked_outbound_no = processForm.value.linked_value + } + + const res = await request({ + url: `/v1/stock/adjustment/${processForm.value.id}/process`, + method: 'post', + data + }) + if (res.code === 200) { + ElMessage.success('处理成功') + showProcessDialog.value = false + fetchData() + } else { + ElMessage.error(res.msg || '处理失败') + } + } catch (e) { + ElMessage.error('处理失败') + } finally { + processLoading.value = false + } +} + // 获取盘点差异列表 async function fetchReviewList() { reviewLoading.value = true