修复一个权限旁路:待采购清单的「参考单价」原本后端无条件返回、前端列也没有 任何门控,而 material_list:referencePrice 只授予 SUPER_ADMIN 与 SUPERVISOR。 结果是 WAREHOUSE_MGR(库管) / INBOUND(入库员) / SALES(销售) 虽然都持有 inbound_purchase:pending_pool、能打开待采购清单,就会看到参考价格 —— 而这个 数字他们在物料列表里是被挡住的。等于本页面成了绕过该权限的后门。 参考价格来自 material_base.reference_price,与物料列表同源,因此复用同一个 权限码管控,不另造新码(同源数据用同码,口径才不会漂)。 改动: - 后端 get_pending_purchase_pool 新增 include_reference_price 参数, fail-closed 默认 False;无权限时**整个字段不返回**而不是给 None - 新增 _has_material_reference_price_perm(),判定方式与既有的 _filter_purchase_prices 保持一致(超管/主管放行 + 逐个权限码比对) - 前端「参考单价」列加 hasPermission 门控,TS 类型改为可选 ★ 只挡前端等于没挡(接口仍然裸奔),前后端必须同时改。
210 lines
4.9 KiB
TypeScript
210 lines
4.9 KiB
TypeScript
import request from '@/utils/request'
|
||
|
||
export interface PurchaseItem {
|
||
id?: number
|
||
request_no?: string
|
||
base_id?: number
|
||
name: string
|
||
spec_model?: string
|
||
quantity: number
|
||
purchase_date: string
|
||
supplier_link?: string
|
||
remark?: string
|
||
images?: string[]
|
||
unit_price?: number
|
||
total_price?: number
|
||
tax_rate?: number
|
||
status: number
|
||
status_text?: string
|
||
requester_id?: number
|
||
requester_name?: string
|
||
requester_email?: string
|
||
approver_id?: number
|
||
approver_name?: string
|
||
approved_at?: string
|
||
reject_reason?: string
|
||
created_at?: string
|
||
updated_at?: string
|
||
// [新增] 关联物料基础信息(已审批未入库列表返回)
|
||
material?: {
|
||
id: number
|
||
company_name: string
|
||
name: string
|
||
spec_model: string
|
||
category: string
|
||
unit: string
|
||
type: string
|
||
is_inspection_required: boolean
|
||
} | null
|
||
}
|
||
|
||
export interface Approver {
|
||
id: number
|
||
username: string
|
||
email: string
|
||
role: string
|
||
}
|
||
|
||
// 获取采购申请列表
|
||
//
|
||
// 搜索参数语义与出库/报废记录保持一致:
|
||
// search_type: all / no(单号) / name(物料名称) / spec_model / requester(申请人)
|
||
// start_date / end_date: 按采购日期过滤
|
||
export function getPurchaseList(params: {
|
||
page?: number
|
||
limit?: number
|
||
status?: number | ''
|
||
keyword?: string
|
||
search_type?: 'all' | 'no' | 'name' | 'spec_model' | 'requester'
|
||
start_date?: string
|
||
end_date?: string
|
||
}) {
|
||
return request({
|
||
url: '/purchase',
|
||
method: 'get',
|
||
params
|
||
})
|
||
}
|
||
|
||
// 创建采购申请
|
||
export function createPurchase(data: PurchaseItem) {
|
||
return request({
|
||
url: '/purchase',
|
||
method: 'post',
|
||
data
|
||
})
|
||
}
|
||
|
||
// 获取采购申请详情
|
||
export function getPurchaseDetail(id: number) {
|
||
return request({
|
||
url: `/purchase/${id}`,
|
||
method: 'get'
|
||
})
|
||
}
|
||
|
||
// 审批采购申请
|
||
export function approvePurchase(id: number, data: {
|
||
action: 'approve' | 'reject'
|
||
reject_reason?: string
|
||
}) {
|
||
return request({
|
||
url: `/purchase/${id}/approve`,
|
||
method: 'patch',
|
||
data
|
||
})
|
||
}
|
||
|
||
// 批量审批采购申请(循环提交产生的多条记录可一次审批)
|
||
export function batchApprovePurchase(data: {
|
||
ids?: number[]
|
||
batch_key?: string
|
||
action: 'approve' | 'reject'
|
||
reject_reason?: string
|
||
}) {
|
||
return request({
|
||
url: '/purchase/batch-approve',
|
||
method: 'post',
|
||
data
|
||
})
|
||
}
|
||
|
||
// 按批次前缀查询同批次的采购申请记录(批次=request_no去掉末尾流水号)
|
||
export function getPurchaseByBatch(batchKey: string) {
|
||
return request({
|
||
url: `/purchase/batch/${encodeURIComponent(batchKey)}`,
|
||
method: 'get'
|
||
})
|
||
}
|
||
|
||
// 获取今日下一个采购批次号(提交一批时调用,同批所有行共享)
|
||
export function getNextBatchSeq() {
|
||
return request({
|
||
url: '/purchase/next-batch-seq',
|
||
method: 'get'
|
||
})
|
||
}
|
||
|
||
// 获取可选审批人列表
|
||
export function getPurchaseApprovers() {
|
||
return request({
|
||
url: '/purchase/approvers',
|
||
method: 'get'
|
||
})
|
||
}
|
||
|
||
// 根据名称/规格自动补全
|
||
export function autoFillPurchase(keyword: string) {
|
||
return request({
|
||
url: '/purchase/auto-fill',
|
||
method: 'get',
|
||
params: { keyword }
|
||
})
|
||
}
|
||
|
||
// 物料基础信息搜索(分页),用于采购申请弹窗
|
||
export function searchMaterialPurchase(keyword: string, page: number = 1) {
|
||
return request({
|
||
url: '/purchase/search-material',
|
||
method: 'get',
|
||
params: { keyword, page }
|
||
})
|
||
}
|
||
|
||
// [新增] 获取已审批通过且未入库的采购单列表(供库管按单入库使用)
|
||
export function getApprovedUnstockedRequests(params: {
|
||
page?: number
|
||
limit?: number
|
||
keyword?: string
|
||
}) {
|
||
return request({
|
||
url: '/purchase/approved-unstocked',
|
||
method: 'get',
|
||
params
|
||
})
|
||
}
|
||
|
||
// 待采购池的单条物料(防重复采购)
|
||
export interface PendingPoolItem {
|
||
material_id: number
|
||
name: string
|
||
spec_model: string
|
||
unit: string
|
||
category: string
|
||
material_type: string
|
||
company_name: string
|
||
image: string
|
||
purchase_link: string
|
||
// 仅供参考展示,受 material_list:referencePrice 管控 —— 无权限时后端**不返回该字段**
|
||
reference_price?: number | null
|
||
inventory_count: number
|
||
available_count: number
|
||
// 在途量 = 该物料所有活跃采购单的剩余待入库量之和
|
||
in_transit_qty: number
|
||
// 有效供给 = 库存 + 在途。判缺货与算建议量都基于它
|
||
effective_supply: number
|
||
warning_status: number // 0=正常 1=黄色 2=红色(进池的必然非 0)
|
||
warning_red: number | null
|
||
warning_yellow: number | null
|
||
target_threshold: number | null
|
||
suggested_qty: number | null
|
||
is_ordered: boolean
|
||
}
|
||
|
||
// 待采购清单:库存已触及预警线、且没有活跃采购单的物料。
|
||
// 物料一旦有在途采购单(待审批/已通过/部分到货未齐)就会从这里消失。
|
||
export function getPendingPurchasePool(params: {
|
||
page?: number
|
||
limit?: number
|
||
keyword?: string
|
||
category?: string
|
||
type?: string
|
||
warning_status?: number
|
||
}) {
|
||
return request({
|
||
url: '/purchase/pending-pool',
|
||
method: 'get',
|
||
params
|
||
})
|
||
}
|