feat: add partial return support with returned_quantity tracking
This commit is contained in:
@ -13,6 +13,7 @@ class TransBorrow(db.Model):
|
|||||||
stock_id = db.Column(db.Integer)
|
stock_id = db.Column(db.Integer)
|
||||||
barcode = db.Column(db.String(100))
|
barcode = db.Column(db.String(100))
|
||||||
quantity = db.Column(db.Numeric(19, 4))
|
quantity = db.Column(db.Numeric(19, 4))
|
||||||
|
returned_quantity = db.Column(db.Numeric(19, 4), default=0)
|
||||||
borrower_name = db.Column(db.String(100))
|
borrower_name = db.Column(db.String(100))
|
||||||
borrow_time = db.Column(db.DateTime, default=beijing_time)
|
borrow_time = db.Column(db.DateTime, default=beijing_time)
|
||||||
borrow_signature = db.Column(db.Text)
|
borrow_signature = db.Column(db.Text)
|
||||||
@ -26,6 +27,9 @@ class TransBorrow(db.Model):
|
|||||||
remark = db.Column(db.Text)
|
remark = db.Column(db.Text)
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
|
returned_qty = float(self.returned_quantity) if self.returned_quantity is not None else 0
|
||||||
|
total_qty = float(self.quantity) if self.quantity is not None else 0
|
||||||
|
pending_qty = total_qty - returned_qty
|
||||||
return {
|
return {
|
||||||
'id': self.id,
|
'id': self.id,
|
||||||
'borrow_no': self.borrow_no,
|
'borrow_no': self.borrow_no,
|
||||||
@ -33,7 +37,9 @@ class TransBorrow(db.Model):
|
|||||||
'source_table': self.source_table,
|
'source_table': self.source_table,
|
||||||
'stock_id': self.stock_id,
|
'stock_id': self.stock_id,
|
||||||
'barcode': self.barcode,
|
'barcode': self.barcode,
|
||||||
'quantity': float(self.quantity) if self.quantity is not None else None,
|
'quantity': total_qty,
|
||||||
|
'returned_quantity': returned_qty,
|
||||||
|
'pending_quantity': pending_qty,
|
||||||
'borrower_name': self.borrower_name,
|
'borrower_name': self.borrower_name,
|
||||||
'borrow_time': self.borrow_time.strftime('%Y-%m-%d %H:%M') if self.borrow_time else None,
|
'borrow_time': self.borrow_time.strftime('%Y-%m-%d %H:%M') if self.borrow_time else None,
|
||||||
'borrow_signature': self.borrow_signature,
|
'borrow_signature': self.borrow_signature,
|
||||||
|
|||||||
@ -113,10 +113,12 @@ class TransService:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def process_return(data, operator_name):
|
def process_return(data, operator_name):
|
||||||
"""
|
"""
|
||||||
还库逻辑:
|
还库逻辑(支持部分归还):
|
||||||
1. 恢复可用库存
|
1. 校验本次归还数量不能大于待还数量
|
||||||
2. 更新库位 (如果有变动)
|
2. 恢复可用库存(按本次归还数量)
|
||||||
3. 记录库管签字
|
3. 更新库位 (如果有变动)
|
||||||
|
4. 记录库管签字
|
||||||
|
5. 更新归还数量和状态(部分归还/全部归还)
|
||||||
"""
|
"""
|
||||||
items = data.get('items', [])
|
items = data.get('items', [])
|
||||||
signature = data.get('signature_path') # 库管签字
|
signature = data.get('signature_path') # 库管签字
|
||||||
@ -129,31 +131,54 @@ class TransService:
|
|||||||
try:
|
try:
|
||||||
for item in items:
|
for item in items:
|
||||||
borrow_id = item.get('id')
|
borrow_id = item.get('id')
|
||||||
|
# 前端传入的本次归还数量
|
||||||
|
return_qty = float(item.get('return_qty', 0))
|
||||||
# 前端如果没有填 return_location,应该在提交前处理好,或者这里做 fallback
|
# 前端如果没有填 return_location,应该在提交前处理好,或者这里做 fallback
|
||||||
# 这里假设前端传来的 return_location 就是最终要保存的库位
|
# 这里假设前端传来的 return_location 就是最终要保存的库位
|
||||||
final_location = item.get('return_location')
|
final_location = item.get('return_location')
|
||||||
|
|
||||||
record = TransBorrow.query.with_for_update().get(borrow_id)
|
record = TransBorrow.query.with_for_update().get(borrow_id)
|
||||||
if not record or record.is_returned:
|
if not record:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# 计算待还数量
|
||||||
|
returned_qty = float(record.returned_quantity) if record.returned_quantity else 0
|
||||||
|
total_qty = float(record.quantity) if record.quantity else 0
|
||||||
|
pending_qty = total_qty - returned_qty
|
||||||
|
|
||||||
|
# 校验归还数量
|
||||||
|
if return_qty <= 0:
|
||||||
|
raise ValueError(f"归还数量必须大于0")
|
||||||
|
if return_qty > pending_qty:
|
||||||
|
raise ValueError(f"本次归还数量({return_qty})不能大于待还数量({pending_qty})")
|
||||||
|
|
||||||
ModelClass = model_map.get(record.source_table)
|
ModelClass = model_map.get(record.source_table)
|
||||||
if ModelClass:
|
if ModelClass:
|
||||||
stock = ModelClass.query.with_for_update().get(record.stock_id)
|
stock = ModelClass.query.with_for_update().get(record.stock_id)
|
||||||
if stock:
|
if stock:
|
||||||
# 1. 恢复可用库存
|
# 1. 恢复可用库存(按本次归还数量)
|
||||||
stock.available_quantity = float(stock.available_quantity) + float(record.quantity)
|
stock.available_quantity = float(stock.available_quantity) + return_qty
|
||||||
|
|
||||||
# 2. 更新库位 (如果提供了有效值)
|
# 2. 更新库位 (如果提供了有效值)
|
||||||
if final_location:
|
if final_location:
|
||||||
stock.warehouse_location = final_location
|
stock.warehouse_location = final_location
|
||||||
|
|
||||||
# 3. 更新借用单状态
|
# 3. 更新归还数量
|
||||||
|
new_returned_qty = returned_qty + return_qty
|
||||||
|
record.returned_quantity = new_returned_qty
|
||||||
|
|
||||||
|
# 4. 更新状态
|
||||||
|
if new_returned_qty >= total_qty:
|
||||||
record.is_returned = True
|
record.is_returned = True
|
||||||
record.status = 'returned'
|
record.status = 'returned'
|
||||||
|
else:
|
||||||
|
record.is_returned = False
|
||||||
|
record.status = 'partial_returned'
|
||||||
|
|
||||||
record.return_time = datetime.now()
|
record.return_time = datetime.now()
|
||||||
record.return_operator = operator_name
|
record.return_operator = operator_name
|
||||||
record.return_signature = signature
|
record.return_signature = signature
|
||||||
|
if final_location:
|
||||||
record.return_location = final_location
|
record.return_location = final_location
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|||||||
@ -22,6 +22,22 @@
|
|||||||
<el-table-column v-if="hasColumnPermission('borrow_no')" prop="borrow_no" label="单号" width="180" show-overflow-tooltip />
|
<el-table-column v-if="hasColumnPermission('borrow_no')" prop="borrow_no" label="单号" width="180" show-overflow-tooltip />
|
||||||
<el-table-column v-if="hasColumnPermission('borrower_name')" prop="borrower_name" label="借用人" width="100" />
|
<el-table-column v-if="hasColumnPermission('borrower_name')" prop="borrower_name" label="借用人" width="100" />
|
||||||
<el-table-column v-if="hasColumnPermission('sku')" prop="sku" label="SKU" width="120" show-overflow-tooltip />
|
<el-table-column v-if="hasColumnPermission('sku')" prop="sku" label="SKU" width="120" show-overflow-tooltip />
|
||||||
|
<el-table-column label="借出数量" width="90" align="center">
|
||||||
|
<template #default="{row}">
|
||||||
|
<el-tag type="info">{{ row.quantity }}</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="已还数量" width="90" align="center">
|
||||||
|
<template #default="{row}">
|
||||||
|
<el-tag type="success">{{ row.returned_quantity || 0 }}</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="待还数量" width="90" align="center">
|
||||||
|
<template #default="{row}">
|
||||||
|
<el-tag v-if="row.pending_quantity > 0" type="warning">{{ row.pending_quantity }}</el-tag>
|
||||||
|
<el-tag v-else type="success">0</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column v-if="hasColumnPermission('borrow_time')" prop="borrow_time" label="借出时间" width="160" sortable />
|
<el-table-column v-if="hasColumnPermission('borrow_time')" prop="borrow_time" label="借出时间" width="160" sortable />
|
||||||
|
|
||||||
<el-table-column v-if="hasColumnPermission('expected_return_time') || hasColumnPermission('return_time')" label="归还时间 / 预计" min-width="200">
|
<el-table-column v-if="hasColumnPermission('expected_return_time') || hasColumnPermission('return_time')" label="归还时间 / 预计" min-width="200">
|
||||||
@ -42,9 +58,9 @@
|
|||||||
|
|
||||||
<el-table-column v-if="hasColumnPermission('status')" label="状态" width="100" align="center">
|
<el-table-column v-if="hasColumnPermission('status')" label="状态" width="100" align="center">
|
||||||
<template #default="{row}">
|
<template #default="{row}">
|
||||||
<el-tag :type="row.status==='returned'?'success':'warning'">
|
<el-tag v-if="row.status === 'returned'" type="success">已还</el-tag>
|
||||||
{{ row.status==='returned'?'已还':'借出中' }}
|
<el-tag v-else-if="row.status === 'partial_returned'" type="warning">部分归还</el-tag>
|
||||||
</el-tag>
|
<el-tag v-else type="danger">未还</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
|
||||||
|
|||||||
@ -47,6 +47,33 @@
|
|||||||
<el-table :data="returnList" border stripe style="width: 100%">
|
<el-table :data="returnList" border stripe style="width: 100%">
|
||||||
<el-table-column v-if="hasColumnPermission('borrower_name')" prop="borrower_name" label="借用人" width="90" show-overflow-tooltip />
|
<el-table-column v-if="hasColumnPermission('borrower_name')" prop="borrower_name" label="借用人" width="90" show-overflow-tooltip />
|
||||||
<el-table-column v-if="hasColumnPermission('sku')" prop="sku" label="SKU" width="120" show-overflow-tooltip />
|
<el-table-column v-if="hasColumnPermission('sku')" prop="sku" label="SKU" width="120" show-overflow-tooltip />
|
||||||
|
<el-table-column label="借出数量" width="80" align="center">
|
||||||
|
<template #default="{row}">
|
||||||
|
<el-tag type="info">{{ row.quantity }}</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="已还数量" width="80" align="center">
|
||||||
|
<template #default="{row}">
|
||||||
|
<el-tag type="success">{{ row.returned_quantity || 0 }}</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="待还数量" width="80" align="center">
|
||||||
|
<template #default="{row}">
|
||||||
|
<el-tag type="warning">{{ row.pending_quantity }}</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column label="本次归还数" width="120" align="center">
|
||||||
|
<template #default="{row}">
|
||||||
|
<el-input-number
|
||||||
|
v-model="row.return_qty"
|
||||||
|
:min="1"
|
||||||
|
:max="row.pending_quantity"
|
||||||
|
size="small"
|
||||||
|
:disabled="!userStore.hasPermission('op_return:operation')"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
<el-table-column v-if="hasColumnPermission('return_location')" label="归还库位(可改)" min-width="160">
|
<el-table-column v-if="hasColumnPermission('return_location')" label="归还库位(可改)" min-width="160">
|
||||||
<template #default="{row}">
|
<template #default="{row}">
|
||||||
@ -253,6 +280,8 @@ const scanItem = async () => {
|
|||||||
const item = res.data
|
const item = res.data
|
||||||
// 默认将归还库位填为当前库位
|
// 默认将归还库位填为当前库位
|
||||||
item.return_location = item.current_location || ''
|
item.return_location = item.current_location || ''
|
||||||
|
// 默认归还数量为待还数量
|
||||||
|
item.return_qty = item.pending_quantity
|
||||||
|
|
||||||
returnList.value.push(item)
|
returnList.value.push(item)
|
||||||
barcode.value = ''
|
barcode.value = ''
|
||||||
|
|||||||
Reference in New Issue
Block a user