fix(inbound): 按单入库时从采购申请单补回成本价
库管角色没有 inbound_purchase:unit_price / total_price 权限,采购单接口 (api/v1/purchase.py:46-53)会把价格字段整个 pop 掉,前端导入时因此带不出价 —— buy.vue 的 hasUnitPrice / hasTotalPrice 双双落空,两个分支都不进,入库 成本被记成 0。 而前端 buy.vue:1936 的注释写得很清楚:"无论前端是否对当前角色显示价格,导入 都应把采购单价格带入(隐藏字段,用于成本)"。前端想带、后端不给,价格就断在 中间——不是谁写错了,是两边对"隐藏"的理解不同。 改为在落库前从采购申请单补回:价格不经过前端,库管依旧看不到采购价,权限 边界不变,但成本可追溯。仅在前端未带价时补,有权限的角色手动填过价则尊重 提交值。采购申请单的 unit_price 存的是**含税**单价(见 purchase/index.vue 列名"含税单价"),需反算不含税。 顺带清理 buy_service.py:221 的函数内局部 import —— 局部 import 会让 Python 把 PurchaseRequest 视为本函数的局部变量,导致位于其之前的引用抛 UnboundLocalError(本次新增的补价逻辑正踩到这个,测试时暴露)。改用文件顶部 的全局导入。 实测:测试申请单 含税 12.5 / 税率 13%,以库管身份提交且**故意不带任何价格 字段**,落库结果 税前 11.0619 (=12.5/1.13) / 含税 12.5 / 总价 110.6195 / 税率 13.00,全部正确;响应体经 filter_item_by_permissions 后仍不含价格字段, 权限边界未变。 另:已有 6 条历史入库单(走采购申请但单价为 0)已按「单价 × 实际入库数量」 回填,不回填总价。
This commit is contained in:
@ -4,6 +4,7 @@ from app.extensions import db
|
||||
from app.models.inbound.buy import StockBuy
|
||||
from app.models.inbound.product import StockProduct
|
||||
from app.models.base import MaterialBase
|
||||
from app.models.purchase import PurchaseRequest
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from sqlalchemy import or_, func, text, and_
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
@ -153,9 +154,27 @@ class BuyInboundService:
|
||||
in_qty = float(data.get('in_quantity') or 0)
|
||||
u_price = float(data.get('unit_price') or 0)
|
||||
tax_rate = float(data.get('tax_rate') or 0)
|
||||
|
||||
# 计算税后单价
|
||||
post_tax_price = float(data.get('post_tax_unit_price') or 0)
|
||||
|
||||
# [新增] 按单入库:关联采购申请单
|
||||
request_id = data.get('request_id')
|
||||
|
||||
# ★ 补价:库管角色没有 inbound_purchase:unit_price / total_price 权限,
|
||||
# 采购单接口(api/v1/purchase.py)会把价格字段整个 pop 掉,前端导入时
|
||||
# 因此带不出价(buy.vue 的 hasUnitPrice/hasTotalPrice 双双落空,
|
||||
# 两个分支都不进)→ 入库成本被记成 0。
|
||||
# 这里在落库前从采购申请单补回:价格**不经过前端**,库管依旧看不到
|
||||
# 采购价,权限边界不变,但成本可追溯。
|
||||
# 注:采购申请单的 unit_price 存的是**含税**单价(见 purchase/index.vue
|
||||
# 的列名"含税单价"),需要反算不含税单价。
|
||||
if request_id and u_price <= 0 and post_tax_price <= 0:
|
||||
_req = PurchaseRequest.query.get(request_id)
|
||||
if _req and float(_req.unit_price or 0) > 0:
|
||||
tax_rate = float(_req.tax_rate or 0)
|
||||
post_tax_price = float(_req.unit_price)
|
||||
u_price = post_tax_price / (1 + tax_rate / 100)
|
||||
|
||||
# 计算税后单价(保底:只给了不含税价时反推)
|
||||
if post_tax_price == 0 and u_price > 0:
|
||||
tax_multiplier = 1 + (tax_rate / 100)
|
||||
post_tax_price = u_price * tax_multiplier
|
||||
@ -170,9 +189,6 @@ class BuyInboundService:
|
||||
generated_sku = str(next_global_id).zfill(10) if next_global_id else datetime.now().strftime('%Y%m%d%H%M%S')
|
||||
final_barcode = data.get('barcode') or generated_sku
|
||||
|
||||
# [新增] 按单入库:关联采购申请单
|
||||
request_id = data.get('request_id')
|
||||
|
||||
new_stock = StockBuy(
|
||||
base_id=material.id, global_print_id=next_global_id, sku=generated_sku, barcode=final_barcode,
|
||||
in_date=in_date_val, serial_number=data.get('serial_number'), batch_number=data.get('batch_number'),
|
||||
@ -201,8 +217,10 @@ class BuyInboundService:
|
||||
db.session.flush() # 获取 new_stock.id
|
||||
|
||||
# [新增] 按单入库:反写采购申请单状态为"已完成"
|
||||
# 注:原先此处有一句局部 `from app.models.purchase import PurchaseRequest`,
|
||||
# 它会让 Python 把 PurchaseRequest 视为本函数的局部变量,导致上方补价
|
||||
# 逻辑(在 import 之前使用)抛 UnboundLocalError。已改用文件顶部的全局导入。
|
||||
if request_id:
|
||||
from app.models.purchase import PurchaseRequest
|
||||
purchase_req = db.session.get(PurchaseRequest, request_id)
|
||||
if purchase_req:
|
||||
if purchase_req.status != 1:
|
||||
|
||||
Reference in New Issue
Block a user