feat: 采购管理税率+分开单价总价列 + 按单出/借库锁定扫描 + 含税法计算
- purchase: 新增tax_rate字段(model+service+frontend), 表格拆分为不含税单价/总价/税率三列 - buy.vue: 含税法计算(含税总价=数量×含税单价), 含税总价自动计算可手动覆盖 - create.vue: 按单出库模式下未选审批单时锁定摄像头和SKU输入 - borrow.vue: 未选审批单时锁定摄像头和SKU输入 - deploy_production.sql: 整合全部数据库变更(表结构+权限码+角色分配)
This commit is contained in:
@ -12,10 +12,13 @@ BEGIN;
|
|||||||
-- 1. 采购申请表新增 base_id(关联物料基础信息)
|
-- 1. 采购申请表新增 base_id(关联物料基础信息)
|
||||||
ALTER TABLE purchase_request ADD COLUMN IF NOT EXISTS base_id INTEGER;
|
ALTER TABLE purchase_request ADD COLUMN IF NOT EXISTS base_id INTEGER;
|
||||||
|
|
||||||
-- 2. 采购入库表新增 request_id(关联采购申请单)
|
-- 2. 采购申请表新增 tax_rate(税率)
|
||||||
|
ALTER TABLE purchase_request ADD COLUMN IF NOT EXISTS tax_rate NUMERIC(5,2) DEFAULT 0;
|
||||||
|
|
||||||
|
-- 3. 采购入库表新增 request_id(关联采购申请单)
|
||||||
ALTER TABLE stock_buy ADD COLUMN IF NOT EXISTS request_id INTEGER;
|
ALTER TABLE stock_buy ADD COLUMN IF NOT EXISTS request_id INTEGER;
|
||||||
|
|
||||||
-- 3. 角色权限表新增 company_name(支持公司级别权限隔离)
|
-- 4. 角色权限表新增 company_name(支持公司级别权限隔离)
|
||||||
ALTER TABLE sys_role_permission ADD COLUMN IF NOT EXISTS company_name VARCHAR(255);
|
ALTER TABLE sys_role_permission ADD COLUMN IF NOT EXISTS company_name VARCHAR(255);
|
||||||
|
|
||||||
|
|
||||||
@ -188,6 +191,57 @@ FROM sys_menu
|
|||||||
WHERE code IN ('system_mgmt', 'system_permission', 'system_audit')
|
WHERE code IN ('system_mgmt', 'system_permission', 'system_audit')
|
||||||
AND NOT EXISTS (SELECT 1 FROM sys_role_permission rp WHERE rp.role_code = 'SUPERVISOR' AND rp.target_code = sys_menu.code AND rp.type = 'menu');
|
AND NOT EXISTS (SELECT 1 FROM sys_role_permission rp WHERE rp.role_code = 'SUPERVISOR' AND rp.target_code = sys_menu.code AND rp.type = 'menu');
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 三补、借库管理菜单 + 权限码 + 角色分配
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
-- 借库选单 & 借库审批 菜单
|
||||||
|
INSERT INTO sys_menu (code, name, parent_id, is_visible)
|
||||||
|
SELECT 'op_borrow_apply', '借库选单', m.id, true
|
||||||
|
FROM sys_menu m WHERE m.code = 'operation_mgmt'
|
||||||
|
AND NOT EXISTS (SELECT 1 FROM sys_menu WHERE code = 'op_borrow_apply');
|
||||||
|
|
||||||
|
INSERT INTO sys_menu (code, name, parent_id, is_visible)
|
||||||
|
SELECT 'op_borrow_approval', '借库审批', m.id, true
|
||||||
|
FROM sys_menu m WHERE m.code = 'operation_mgmt'
|
||||||
|
AND NOT EXISTS (SELECT 1 FROM sys_menu WHERE code = 'op_borrow_approval');
|
||||||
|
|
||||||
|
-- 借库选单元素权限
|
||||||
|
INSERT INTO sys_element (menu_code, name, code, element_type) SELECT 'op_borrow_apply', '物品名称', 'op_borrow_apply:name', 'column' WHERE NOT EXISTS (SELECT 1 FROM sys_element WHERE code = 'op_borrow_apply:name');
|
||||||
|
INSERT INTO sys_element (menu_code, name, code, element_type) SELECT 'op_borrow_apply', '规格型号', 'op_borrow_apply:spec', 'column' WHERE NOT EXISTS (SELECT 1 FROM sys_element WHERE code = 'op_borrow_apply:spec');
|
||||||
|
INSERT INTO sys_element (menu_code, name, code, element_type) SELECT 'op_borrow_apply', '可用库存', 'op_borrow_apply:available_quantity', 'column' WHERE NOT EXISTS (SELECT 1 FROM sys_element WHERE code = 'op_borrow_apply:available_quantity');
|
||||||
|
INSERT INTO sys_element (menu_code, name, code, element_type) SELECT 'op_borrow_apply', '操作/编辑', 'op_borrow_apply:operation', 'column' WHERE NOT EXISTS (SELECT 1 FROM sys_element WHERE code = 'op_borrow_apply:operation');
|
||||||
|
|
||||||
|
-- 角色分配:有 operation_mgmt 的角色 → 获得借库选单/审批菜单 + 元素
|
||||||
|
INSERT INTO sys_role_permission (role_code, target_code, type)
|
||||||
|
SELECT DISTINCT rp.role_code, 'op_borrow_apply', 'menu'
|
||||||
|
FROM sys_role_permission rp WHERE rp.target_code = 'operation_mgmt'
|
||||||
|
AND NOT EXISTS (SELECT 1 FROM sys_role_permission WHERE role_code = rp.role_code AND target_code = 'op_borrow_apply');
|
||||||
|
|
||||||
|
INSERT INTO sys_role_permission (role_code, target_code, type)
|
||||||
|
SELECT DISTINCT rp.role_code, 'op_borrow_approval', 'menu'
|
||||||
|
FROM sys_role_permission rp WHERE rp.target_code = 'operation_mgmt'
|
||||||
|
AND NOT EXISTS (SELECT 1 FROM sys_role_permission WHERE role_code = rp.role_code AND target_code = 'op_borrow_approval');
|
||||||
|
|
||||||
|
INSERT INTO sys_role_permission (role_code, target_code, type)
|
||||||
|
SELECT rp.role_code, elem.code, 'element'
|
||||||
|
FROM (SELECT DISTINCT role_code FROM sys_role_permission WHERE target_code = 'operation_mgmt') rp
|
||||||
|
CROSS JOIN (SELECT unnest(ARRAY['op_borrow_apply:name','op_borrow_apply:spec','op_borrow_apply:available_quantity','op_borrow_apply:operation']) AS code) elem
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM sys_role_permission rp2 WHERE rp2.role_code = rp.role_code AND rp2.target_code = elem.code);
|
||||||
|
|
||||||
|
-- 补借库管理菜单给 SUPERVISOR(如缺)
|
||||||
|
INSERT INTO sys_role_permission (role_code, target_code, type) SELECT 'SUPERVISOR', 'operation_mgmt', 'menu' WHERE NOT EXISTS (SELECT 1 FROM sys_role_permission WHERE role_code = 'SUPERVISOR' AND target_code = 'operation_mgmt');
|
||||||
|
INSERT INTO sys_role_permission (role_code, target_code, type) SELECT 'SUPERVISOR', 'op_borrow_apply', 'menu' WHERE NOT EXISTS (SELECT 1 FROM sys_role_permission WHERE role_code = 'SUPERVISOR' AND target_code = 'op_borrow_apply');
|
||||||
|
INSERT INTO sys_role_permission (role_code, target_code, type) SELECT 'SUPERVISOR', 'op_borrow_approval', 'menu' WHERE NOT EXISTS (SELECT 1 FROM sys_role_permission WHERE role_code = 'SUPERVISOR' AND target_code = 'op_borrow_approval');
|
||||||
|
INSERT INTO sys_role_permission (role_code, target_code, type) SELECT 'SUPERVISOR', 'op_borrow', 'menu' WHERE NOT EXISTS (SELECT 1 FROM sys_role_permission WHERE role_code = 'SUPERVISOR' AND target_code = 'op_borrow');
|
||||||
|
INSERT INTO sys_role_permission (role_code, target_code, type) SELECT 'SUPERVISOR', 'op_return', 'menu' WHERE NOT EXISTS (SELECT 1 FROM sys_role_permission WHERE role_code = 'SUPERVISOR' AND target_code = 'op_return');
|
||||||
|
INSERT INTO sys_role_permission (role_code, target_code, type) SELECT 'SUPERVISOR', 'op_records', 'menu' WHERE NOT EXISTS (SELECT 1 FROM sys_role_permission WHERE role_code = 'SUPERVISOR' AND target_code = 'op_records');
|
||||||
|
|
||||||
|
-- 出库管理菜单补 SUPERVISOR
|
||||||
|
INSERT INTO sys_role_permission (role_code, target_code, type) SELECT 'SUPERVISOR', 'outbound_mgmt', 'menu' WHERE NOT EXISTS (SELECT 1 FROM sys_role_permission WHERE role_code = 'SUPERVISOR' AND target_code = 'outbound_mgmt');
|
||||||
|
INSERT INTO sys_role_permission (role_code, target_code, type) SELECT 'SUPERVISOR', 'outbound_approval', 'menu' WHERE NOT EXISTS (SELECT 1 FROM sys_role_permission WHERE role_code = 'SUPERVISOR' AND target_code = 'outbound_approval');
|
||||||
|
INSERT INTO sys_role_permission (role_code, target_code, type) SELECT 'SUPERVISOR', elem.code, 'element' FROM (SELECT unnest(ARRAY['outbound_approval:request_no','outbound_approval:applicant','outbound_approval:approver','outbound_approval:status','outbound_approval:operation']) AS code) elem WHERE NOT EXISTS (SELECT 1 FROM sys_role_permission WHERE role_code = 'SUPERVISOR' AND target_code = elem.code);
|
||||||
|
|
||||||
-- ============================================================
|
-- ============================================================
|
||||||
-- 四、验证
|
-- 四、验证
|
||||||
-- ============================================================
|
-- ============================================================
|
||||||
@ -196,7 +250,7 @@ SELECT '=== 验证 ===' AS info;
|
|||||||
SELECT '表结构' AS item, column_name, data_type
|
SELECT '表结构' AS item, column_name, data_type
|
||||||
FROM information_schema.columns
|
FROM information_schema.columns
|
||||||
WHERE table_name IN ('purchase_request', 'stock_buy', 'sys_role_permission')
|
WHERE table_name IN ('purchase_request', 'stock_buy', 'sys_role_permission')
|
||||||
AND column_name IN ('base_id', 'request_id', 'company_name')
|
AND column_name IN ('base_id', 'request_id', 'company_name', 'tax_rate')
|
||||||
ORDER BY table_name, column_name;
|
ORDER BY table_name, column_name;
|
||||||
|
|
||||||
SELECT '权限码总数' AS item, count(*) FROM sys_element;
|
SELECT '权限码总数' AS item, count(*) FROM sys_element;
|
||||||
|
|||||||
@ -20,8 +20,9 @@ class PurchaseRequest(db.Model):
|
|||||||
supplier_link = db.Column(db.String(500), comment='商家地址链接')
|
supplier_link = db.Column(db.String(500), comment='商家地址链接')
|
||||||
remark = db.Column(db.Text, comment='备注信息')
|
remark = db.Column(db.Text, comment='备注信息')
|
||||||
images = db.Column(db.Text, comment='图片列表JSON')
|
images = db.Column(db.Text, comment='图片列表JSON')
|
||||||
unit_price = db.Column(db.Numeric(19, 4), default=0, comment='单价')
|
unit_price = db.Column(db.Numeric(19, 4), default=0, comment='不含税单价')
|
||||||
total_price = db.Column(db.Numeric(19, 4), default=0, comment='总价')
|
total_price = db.Column(db.Numeric(19, 4), default=0, comment='不含税总价')
|
||||||
|
tax_rate = db.Column(db.Numeric(5, 2), default=0, comment='税率')
|
||||||
status = db.Column(db.Integer, default=0, nullable=False)
|
status = db.Column(db.Integer, default=0, nullable=False)
|
||||||
requester_id = db.Column(db.Integer, nullable=False, index=True)
|
requester_id = db.Column(db.Integer, nullable=False, index=True)
|
||||||
approver_id = db.Column(db.Integer, index=True)
|
approver_id = db.Column(db.Integer, index=True)
|
||||||
@ -88,6 +89,7 @@ class PurchaseRequest(db.Model):
|
|||||||
'images': self.get_images(),
|
'images': self.get_images(),
|
||||||
'unit_price': float(self.unit_price) if self.unit_price else 0,
|
'unit_price': float(self.unit_price) if self.unit_price else 0,
|
||||||
'total_price': float(self.total_price) if self.total_price else 0,
|
'total_price': float(self.total_price) if self.total_price else 0,
|
||||||
|
'tax_rate': float(self.tax_rate) if self.tax_rate else 0,
|
||||||
'status': self.status,
|
'status': self.status,
|
||||||
'status_text': ['待审批', '已通过', '已驳回', '已完成'][self.status] if self.status in [0, 1, 2, 3] else '未知',
|
'status_text': ['待审批', '已通过', '已驳回', '已完成'][self.status] if self.status in [0, 1, 2, 3] else '未知',
|
||||||
'requester_id': self.requester_id,
|
'requester_id': self.requester_id,
|
||||||
|
|||||||
@ -79,6 +79,7 @@ class PurchaseService:
|
|||||||
images=json.dumps(data.get('images', []), ensure_ascii=False) if data.get('images') else '[]',
|
images=json.dumps(data.get('images', []), ensure_ascii=False) if data.get('images') else '[]',
|
||||||
unit_price=float(data.get('unit_price', 0) or 0),
|
unit_price=float(data.get('unit_price', 0) or 0),
|
||||||
total_price=float(data.get('total_price', 0) or 0),
|
total_price=float(data.get('total_price', 0) or 0),
|
||||||
|
tax_rate=float(data.get('tax_rate', 0) or 0),
|
||||||
requester_id=requester_id,
|
requester_id=requester_id,
|
||||||
approver_id=data.get('approver_id'),
|
approver_id=data.get('approver_id'),
|
||||||
status=0
|
status=0
|
||||||
|
|||||||
@ -13,6 +13,7 @@ export interface PurchaseItem {
|
|||||||
images?: string[]
|
images?: string[]
|
||||||
unit_price?: number
|
unit_price?: number
|
||||||
total_price?: number
|
total_price?: number
|
||||||
|
tax_rate?: number
|
||||||
status: number
|
status: number
|
||||||
status_text?: string
|
status_text?: string
|
||||||
requester_id?: number
|
requester_id?: number
|
||||||
|
|||||||
@ -79,25 +79,37 @@
|
|||||||
|
|
||||||
<div class="scan-section">
|
<div class="scan-section">
|
||||||
|
|
||||||
<div v-if="userStore.hasPermission('outbound_create:operation')" class="camera-placeholder" @click="showCamera = true">
|
<template v-if="outboundMode === 'by-request' && !selectedRequestId">
|
||||||
<el-icon :size="40" color="#409EFF"><CameraFilled /></el-icon>
|
<div class="camera-placeholder" style="background-color: #f5f5f5; cursor: not-allowed;">
|
||||||
<span class="text">点击开启全屏扫码</span>
|
<el-icon :size="40" color="#909399"><CameraFilled /></el-icon>
|
||||||
</div>
|
<span class="text">请先选择审批单</span>
|
||||||
<div v-else class="camera-placeholder" style="background-color: #f5f5f5; cursor: not-allowed;">
|
</div>
|
||||||
<el-icon :size="40" color="#909399"><CameraFilled /></el-icon>
|
<div class="input-box">
|
||||||
<span class="text">无扫码权限</span>
|
<el-input v-model="barcodeInput" placeholder="请先选择审批单" disabled size="large">
|
||||||
</div>
|
<template #prefix><el-icon><Scissor /></el-icon></template>
|
||||||
|
<template #append><el-button disabled>添加</el-button></template>
|
||||||
<div class="input-box">
|
</el-input>
|
||||||
<el-input
|
</div>
|
||||||
v-model="barcodeInput"
|
</template>
|
||||||
placeholder="扫描或输入条码回车"
|
<template v-else>
|
||||||
@keyup.enter="handleManualInput"
|
<div v-if="userStore.hasPermission('outbound_create:operation')" class="camera-placeholder" @click="showCamera = true">
|
||||||
clearable
|
<el-icon :size="40" color="#409EFF"><CameraFilled /></el-icon>
|
||||||
ref="barcodeRef"
|
<span class="text">点击开启全屏扫码</span>
|
||||||
size="large"
|
</div>
|
||||||
:disabled="!userStore.hasPermission('outbound_create:operation')"
|
<div v-else class="camera-placeholder" style="background-color: #f5f5f5; cursor: not-allowed;">
|
||||||
>
|
<el-icon :size="40" color="#909399"><CameraFilled /></el-icon>
|
||||||
|
<span class="text">无扫码权限</span>
|
||||||
|
</div>
|
||||||
|
<div class="input-box">
|
||||||
|
<el-input
|
||||||
|
v-model="barcodeInput"
|
||||||
|
placeholder="扫描或输入条码回车"
|
||||||
|
@keyup.enter="handleManualInput"
|
||||||
|
clearable
|
||||||
|
ref="barcodeRef"
|
||||||
|
size="large"
|
||||||
|
:disabled="!userStore.hasPermission('outbound_create:operation')"
|
||||||
|
>
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<el-icon><Scissor /></el-icon>
|
<el-icon><Scissor /></el-icon>
|
||||||
</template>
|
</template>
|
||||||
@ -106,6 +118,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="cart-section">
|
<div class="cart-section">
|
||||||
|
|||||||
@ -24,12 +24,19 @@
|
|||||||
<el-table-column prop="spec_model" label="规格型号" min-width="120" show-overflow-tooltip />
|
<el-table-column prop="spec_model" label="规格型号" min-width="120" show-overflow-tooltip />
|
||||||
<el-table-column prop="quantity" label="数量" width="80" align="center" />
|
<el-table-column prop="quantity" label="数量" width="80" align="center" />
|
||||||
<el-table-column prop="purchase_date" label="采购日期" width="110" />
|
<el-table-column prop="purchase_date" label="采购日期" width="110" />
|
||||||
<el-table-column label="单价/总价" width="120" align="right">
|
<el-table-column label="不含税单价" width="110" align="right">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<span v-if="row.unit_price || row.total_price">
|
{{ row.unit_price ? '¥' + Number(row.unit_price).toFixed(2) : '-' }}
|
||||||
{{ row.unit_price || '-' }} / {{ row.total_price || '-' }}
|
</template>
|
||||||
</span>
|
</el-table-column>
|
||||||
<span v-else>-</span>
|
<el-table-column label="不含税总价" width="120" align="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ row.total_price ? '¥' + Number(row.total_price).toFixed(2) : '-' }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="tax_rate" label="税率" width="70" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ row.tax_rate != null ? row.tax_rate + '%' : '-' }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="requester_name" label="申请人" width="100" />
|
<el-table-column prop="requester_name" label="申请人" width="100" />
|
||||||
@ -126,16 +133,26 @@
|
|||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="8">
|
||||||
<el-form-item label="单价">
|
<el-form-item label="不含税单价">
|
||||||
<el-input-number v-model="form.unit_price" :min="0" :precision="2" style="width: 100%;" @change="onUnitPriceChange" />
|
<el-input-number v-model="form.unit_price" :min="0" :precision="2" style="width: 100%;" @change="onUnitPriceChange" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="8">
|
||||||
<el-form-item label="总价">
|
<el-form-item label="不含税总价">
|
||||||
<el-input-number v-model="form.total_price" :min="0" :precision="2" style="width: 100%;" @change="onTotalPriceChange" />
|
<el-input-number v-model="form.total_price" :min="0" :precision="2" style="width: 100%;" @change="onTotalPriceChange" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="税率">
|
||||||
|
<el-select v-model="form.tax_rate" style="width: 100%;" @change="onTaxRateChange">
|
||||||
|
<el-option label="0%" :value="0" />
|
||||||
|
<el-option label="1%" :value="1" />
|
||||||
|
<el-option label="6%" :value="6" />
|
||||||
|
<el-option label="13%" :value="13" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
<el-col :span="24">
|
<el-col :span="24">
|
||||||
<el-form-item label="商家链接">
|
<el-form-item label="商家链接">
|
||||||
<el-input v-model="form.supplier_link" placeholder="商家地址链接" clearable />
|
<el-input v-model="form.supplier_link" placeholder="商家地址链接" clearable />
|
||||||
@ -311,6 +328,7 @@ const form = ref({
|
|||||||
remark: '',
|
remark: '',
|
||||||
unit_price: undefined as number | undefined,
|
unit_price: undefined as number | undefined,
|
||||||
total_price: undefined as number | undefined,
|
total_price: undefined as number | undefined,
|
||||||
|
tax_rate: 0,
|
||||||
approver_id: undefined as number | undefined,
|
approver_id: undefined as number | undefined,
|
||||||
images: [] as string[]
|
images: [] as string[]
|
||||||
})
|
})
|
||||||
@ -381,7 +399,7 @@ const openCreateDialog = (prefill?: { materialId?: number; name?: string; spec?:
|
|||||||
quantity: 1,
|
quantity: 1,
|
||||||
purchase_date: new Date().toISOString().split('T')[0],
|
purchase_date: new Date().toISOString().split('T')[0],
|
||||||
supplier_link: '', remark: '',
|
supplier_link: '', remark: '',
|
||||||
unit_price: undefined, total_price: undefined,
|
unit_price: undefined, total_price: undefined, tax_rate: 0,
|
||||||
approver_id: undefined, images: []
|
approver_id: undefined, images: []
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -506,11 +524,16 @@ const onUnitPriceChange = (val: number | undefined) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const onTotalPriceChange = (_val: number | undefined) => {
|
const onTotalPriceChange = (_val: number | undefined) => {
|
||||||
// 用户手动修改总价时:标记为已手动修改,不再反向强制覆盖单价
|
|
||||||
// 允许用户输入打包一口价(如单价3元,买2个算5元),保留用户填入的单价和总价
|
|
||||||
totalPriceManuallyEdited.value = true
|
totalPriceManuallyEdited.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onTaxRateChange = () => {
|
||||||
|
// 税率变化时:保持不含税单价不变,重算不含税总价 = 单价×数量
|
||||||
|
if (form.value.unit_price && form.value.quantity) {
|
||||||
|
form.value.total_price = Number((form.value.unit_price * form.value.quantity).toFixed(2))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- 上传 ---
|
// --- 上传 ---
|
||||||
const beforeAvatarUpload = (rawFile: any) => {
|
const beforeAvatarUpload = (rawFile: any) => {
|
||||||
const isTypeValid = ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/bmp'].includes(rawFile.type)
|
const isTypeValid = ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/bmp'].includes(rawFile.type)
|
||||||
|
|||||||
@ -580,14 +580,18 @@
|
|||||||
<el-col :span="8">
|
<el-col :span="8">
|
||||||
<el-form-item label="含税总价">
|
<el-form-item label="含税总价">
|
||||||
<el-input-number
|
<el-input-number
|
||||||
v-model="form._post_tax_total_price"
|
v-model="postTaxTotalInput"
|
||||||
:precision="2"
|
:precision="2"
|
||||||
:controls="false"
|
:controls="false"
|
||||||
style="width:100%"
|
style="width:100%"
|
||||||
class="total-price-input"
|
class="total-price-input"
|
||||||
placeholder="输入含税总价反算"
|
:placeholder="postTaxTotalAuto > 0 ? '自动: ¥' + postTaxTotalAuto.toFixed(2) : '自动计算'"
|
||||||
@change="updatePrices('post_total')"
|
@change="onPostTaxTotalChange"
|
||||||
|
@clear="onPostTaxTotalClear"
|
||||||
/>
|
/>
|
||||||
|
<span v-if="postTaxTotalAuto > 0 && !postTaxTotalManuallySet" style="font-size:11px;color:#909399;">
|
||||||
|
自动计算,点击输入可覆盖
|
||||||
|
</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
@ -1178,8 +1182,7 @@ const form = reactive({
|
|||||||
supplier_name: '', purchaser: '', purchaser_email: '', source_link: '', detail_link: '',
|
supplier_name: '', purchaser: '', purchaser_email: '', source_link: '', detail_link: '',
|
||||||
arrival_photo: [] as string[], inspection_report: [] as string[],
|
arrival_photo: [] as string[], inspection_report: [] as string[],
|
||||||
print_copies: 1,
|
print_copies: 1,
|
||||||
request_id: undefined as number | undefined, // [新增] 关联采购申请单
|
request_id: undefined as number | undefined // [新增] 关联采购申请单
|
||||||
_post_tax_total_price: undefined as number | undefined // 含税总价(仅前端输入用,不提交后端)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// ------------------------------------
|
// ------------------------------------
|
||||||
@ -1382,15 +1385,49 @@ const handleEntryModeChange = (val: string) => {
|
|||||||
if(formRef.value) formRef.value.clearValidate('batch_number')
|
if(formRef.value) formRef.value.clearValidate('batch_number')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 价格联动:含税单价为主输入,不含税价格自动反算
|
// 价格联动:含税法计算
|
||||||
// 追踪用户最后编辑的是含税还是不含税('pre' | 'post')
|
// 含税总价 = 数量 × 含税单价
|
||||||
const lastPriceEdit = ref<'pre' | 'post'>('post') // 默认以含税为主
|
// 不含税总价 = 含税总价 ÷ (1 + 税率)
|
||||||
|
// 不含税单价 = 不含税总价 ÷ 数量
|
||||||
|
const lastPriceEdit = ref<'pre' | 'post'>('post')
|
||||||
|
|
||||||
|
// 含税总价:自动计算(含税单价 × 数量)+ 支持手动覆盖
|
||||||
|
const postTaxTotalManuallySet = ref(false)
|
||||||
|
const postTaxTotalInput = ref<number | undefined>(undefined)
|
||||||
|
const postTaxTotalAuto = computed(() => {
|
||||||
|
if (form.post_tax_unit_price != null && form.in_quantity > 0) {
|
||||||
|
return Number((form.post_tax_unit_price * form.in_quantity).toFixed(2))
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
})
|
||||||
|
|
||||||
|
const onPostTaxTotalChange = (val: number | undefined) => {
|
||||||
|
if (val != null && val > 0 && form.in_quantity > 0) {
|
||||||
|
postTaxTotalManuallySet.value = true
|
||||||
|
// 含税总价 → 含税单价 → 不含税单价/总价
|
||||||
|
form.post_tax_unit_price = Number((val / form.in_quantity).toFixed(2))
|
||||||
|
const taxMultiplier = 1 + (form.tax_rate || 0) / 100
|
||||||
|
form.unit_price = Number((form.post_tax_unit_price / taxMultiplier).toFixed(4))
|
||||||
|
form.total_price = Number((form.unit_price * form.in_quantity).toFixed(2))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onPostTaxTotalClear = () => {
|
||||||
|
postTaxTotalManuallySet.value = false
|
||||||
|
postTaxTotalInput.value = undefined
|
||||||
|
// 重新用含税单价反算
|
||||||
|
if (form.post_tax_unit_price != null && form.in_quantity > 0) {
|
||||||
|
const taxMultiplier = 1 + (form.tax_rate || 0) / 100
|
||||||
|
form.unit_price = Number((form.post_tax_unit_price / taxMultiplier).toFixed(4))
|
||||||
|
form.total_price = Number((form.unit_price * form.in_quantity).toFixed(2))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const updatePrices = (source: string) => {
|
const updatePrices = (source: string) => {
|
||||||
const taxMultiplier = 1 + (form.tax_rate || 0) / 100;
|
const taxMultiplier = 1 + (form.tax_rate || 0) / 100;
|
||||||
|
|
||||||
if (source === 'pre') {
|
if (source === 'pre') {
|
||||||
// 用户编辑了不含税单价 → 反推含税
|
// 编辑了不含税单价 → 反推含税单价
|
||||||
lastPriceEdit.value = 'pre'
|
lastPriceEdit.value = 'pre'
|
||||||
if (form.unit_price != null) {
|
if (form.unit_price != null) {
|
||||||
form.post_tax_unit_price = Number((form.unit_price * taxMultiplier).toFixed(2));
|
form.post_tax_unit_price = Number((form.unit_price * taxMultiplier).toFixed(2));
|
||||||
@ -1398,7 +1435,7 @@ const updatePrices = (source: string) => {
|
|||||||
form.post_tax_unit_price = undefined;
|
form.post_tax_unit_price = undefined;
|
||||||
}
|
}
|
||||||
} else if (source === 'post') {
|
} else if (source === 'post') {
|
||||||
// 用户编辑了含税单价 → 反推不含税
|
// 编辑了含税单价 → 反推不含税
|
||||||
lastPriceEdit.value = 'post'
|
lastPriceEdit.value = 'post'
|
||||||
if (form.post_tax_unit_price != null) {
|
if (form.post_tax_unit_price != null) {
|
||||||
form.unit_price = Number((form.post_tax_unit_price / taxMultiplier).toFixed(4));
|
form.unit_price = Number((form.post_tax_unit_price / taxMultiplier).toFixed(4));
|
||||||
@ -1406,43 +1443,51 @@ const updatePrices = (source: string) => {
|
|||||||
form.unit_price = undefined;
|
form.unit_price = undefined;
|
||||||
}
|
}
|
||||||
} else if (source === 'tax') {
|
} else if (source === 'tax') {
|
||||||
// 税率变化:保持用户最后编辑的那个价格不变,重算另一个
|
// 税率变化:保持最后编辑的价格不变,重算另一个
|
||||||
if (lastPriceEdit.value === 'post' && form.post_tax_unit_price != null) {
|
if (lastPriceEdit.value === 'post' && form.post_tax_unit_price != null) {
|
||||||
form.unit_price = Number((form.post_tax_unit_price / taxMultiplier).toFixed(4));
|
form.unit_price = Number((form.post_tax_unit_price / taxMultiplier).toFixed(4));
|
||||||
} else if (form.unit_price != null) {
|
} else if (form.unit_price != null) {
|
||||||
form.post_tax_unit_price = Number((form.unit_price * taxMultiplier).toFixed(2));
|
form.post_tax_unit_price = Number((form.unit_price * taxMultiplier).toFixed(2));
|
||||||
}
|
}
|
||||||
} else if (source === 'post_total') {
|
|
||||||
// 用户输入了含税总价 → 反推不含税总价和单价
|
|
||||||
lastPriceEdit.value = 'post'
|
|
||||||
const postTaxTotal = form._post_tax_total_price
|
|
||||||
if (postTaxTotal != null && form.in_quantity > 0) {
|
|
||||||
const preTaxTotal = postTaxTotal / taxMultiplier
|
|
||||||
form.total_price = Number(preTaxTotal.toFixed(2))
|
|
||||||
form.unit_price = Number((preTaxTotal / form.in_quantity).toFixed(4))
|
|
||||||
form.post_tax_unit_price = Number((form.unit_price * taxMultiplier).toFixed(2))
|
|
||||||
}
|
|
||||||
return // 已完整计算,跳过下面的总价逻辑
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算不含税总价
|
// 含税单价优先计算总价
|
||||||
if (form.in_quantity > 0 && form.unit_price != null) {
|
// 不含税总价 = (含税单价 / (1+税率)) × 数量
|
||||||
|
if (form.in_quantity > 0 && form.post_tax_unit_price != null) {
|
||||||
|
const preTax = form.post_tax_unit_price / taxMultiplier
|
||||||
|
form.unit_price = Number(preTax.toFixed(4))
|
||||||
|
form.total_price = Number((preTax * form.in_quantity).toFixed(2))
|
||||||
|
} else if (form.in_quantity > 0 && form.unit_price != null) {
|
||||||
form.total_price = Number((form.in_quantity * form.unit_price).toFixed(2));
|
form.total_price = Number((form.in_quantity * form.unit_price).toFixed(2));
|
||||||
} else {
|
} else {
|
||||||
form.total_price = undefined;
|
form.total_price = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (postTaxTotalManuallySet.value && postTaxTotalInput.value != null) {
|
||||||
|
onPostTaxTotalChange(postTaxTotalInput.value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!postTaxTotalManuallySet.value) {
|
||||||
|
postTaxTotalInput.value = undefined
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 数量或不含税单价变化 → 自动推总价和含税单价
|
// 数量或单价变化 → 以含税单价为主计算
|
||||||
watch(() => [form.in_quantity, form.unit_price], () => {
|
watch(() => [form.in_quantity, form.post_tax_unit_price], () => {
|
||||||
if (form.unit_price != null && form.in_quantity > 0) {
|
if (form.post_tax_unit_price != null && form.in_quantity > 0) {
|
||||||
form.total_price = Number((form.in_quantity * form.unit_price).toFixed(2));
|
const taxMultiplier = 1 + (form.tax_rate || 0) / 100
|
||||||
const taxMultiplier = 1 + (form.tax_rate || 0) / 100;
|
form.unit_price = Number((form.post_tax_unit_price / taxMultiplier).toFixed(4))
|
||||||
form.post_tax_unit_price = Number((form.unit_price * taxMultiplier).toFixed(2));
|
form.total_price = Number((form.unit_price * form.in_quantity).toFixed(2))
|
||||||
|
} else if (form.unit_price != null && form.in_quantity > 0) {
|
||||||
|
form.total_price = Number((form.in_quantity * form.unit_price).toFixed(2))
|
||||||
|
form.post_tax_unit_price = Number((form.unit_price * (1 + (form.tax_rate || 0) / 100)).toFixed(2))
|
||||||
} else {
|
} else {
|
||||||
form.total_price = undefined;
|
form.total_price = undefined;
|
||||||
form.post_tax_unit_price = undefined;
|
form.post_tax_unit_price = undefined;
|
||||||
}
|
}
|
||||||
|
if (!postTaxTotalManuallySet.value) {
|
||||||
|
postTaxTotalInput.value = undefined
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 注意:入库数量和打印份数现在是独立的字段,不再自动同步
|
// 注意:入库数量和打印份数现在是独立的字段,不再自动同步
|
||||||
@ -2046,9 +2091,10 @@ const resetForm = () => {
|
|||||||
tax_rate: 0,
|
tax_rate: 0,
|
||||||
currency: 'CNY', exchange_rate: 1.00, supplier_name: '', purchaser: '', purchaser_email: '', source_link: '', detail_link: '', arrival_photo: [], inspection_report: [],
|
currency: 'CNY', exchange_rate: 1.00, supplier_name: '', purchaser: '', purchaser_email: '', source_link: '', detail_link: '', arrival_photo: [], inspection_report: [],
|
||||||
print_copies: 1,
|
print_copies: 1,
|
||||||
request_id: undefined,
|
request_id: undefined
|
||||||
_post_tax_total_price: undefined
|
|
||||||
})
|
})
|
||||||
|
postTaxTotalInput.value = undefined
|
||||||
|
postTaxTotalManuallySet.value = false
|
||||||
}
|
}
|
||||||
const getStatusType = (status: string) => { const map: any = {'在库': 'success', '出库': 'info', '损耗': 'danger'}; return map[status] || 'warning' }
|
const getStatusType = (status: string) => { const map: any = {'在库': 'success', '出库': 'info', '损耗': 'danger'}; return map[status] || 'warning' }
|
||||||
|
|
||||||
|
|||||||
@ -64,25 +64,37 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="scan-section">
|
<div class="scan-section">
|
||||||
<div v-if="userStore.hasPermission('op_borrow:operation')" class="camera-placeholder" @click="showCamera = true">
|
<template v-if="!selectedApprovalId">
|
||||||
<el-icon :size="40" color="#409EFF"><CameraFilled /></el-icon>
|
<div class="camera-placeholder" style="background-color: #f5f5f5; cursor: not-allowed;">
|
||||||
<span class="text">点击开启全屏扫码</span>
|
<el-icon :size="40" color="#909399"><CameraFilled /></el-icon>
|
||||||
</div>
|
<span class="text">请先选择审批单</span>
|
||||||
<div v-else class="camera-placeholder" style="background-color: #f5f5f5; cursor: not-allowed;">
|
</div>
|
||||||
<el-icon :size="40" color="#909399"><CameraFilled /></el-icon>
|
<div class="input-box">
|
||||||
<span class="text">无扫码权限</span>
|
<el-input v-model="barcodeInput" placeholder="请先选择审批单" disabled size="large">
|
||||||
</div>
|
<template #prefix><el-icon><Scissor /></el-icon></template>
|
||||||
|
<template #append><el-button disabled>添加</el-button></template>
|
||||||
<div class="input-box">
|
</el-input>
|
||||||
<el-input
|
</div>
|
||||||
v-model="barcodeInput"
|
</template>
|
||||||
placeholder="扫描或输入条码回车"
|
<template v-else>
|
||||||
@keyup.enter="handleManualInput"
|
<div v-if="userStore.hasPermission('op_borrow:operation')" class="camera-placeholder" @click="showCamera = true">
|
||||||
clearable
|
<el-icon :size="40" color="#409EFF"><CameraFilled /></el-icon>
|
||||||
ref="barcodeRef"
|
<span class="text">点击开启全屏扫码</span>
|
||||||
size="large"
|
</div>
|
||||||
:disabled="!userStore.hasPermission('op_borrow:operation')"
|
<div v-else class="camera-placeholder" style="background-color: #f5f5f5; cursor: not-allowed;">
|
||||||
>
|
<el-icon :size="40" color="#909399"><CameraFilled /></el-icon>
|
||||||
|
<span class="text">无扫码权限</span>
|
||||||
|
</div>
|
||||||
|
<div class="input-box">
|
||||||
|
<el-input
|
||||||
|
v-model="barcodeInput"
|
||||||
|
placeholder="扫描或输入条码回车"
|
||||||
|
@keyup.enter="handleManualInput"
|
||||||
|
clearable
|
||||||
|
ref="barcodeRef"
|
||||||
|
size="large"
|
||||||
|
:disabled="!userStore.hasPermission('op_borrow:operation')"
|
||||||
|
>
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<el-icon><Scissor /></el-icon>
|
<el-icon><Scissor /></el-icon>
|
||||||
</template>
|
</template>
|
||||||
@ -91,6 +103,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="cart-section">
|
<div class="cart-section">
|
||||||
|
|||||||
Reference in New Issue
Block a user