feat(cost): 成本模型加研发归集维度 research_flag/project_type (cma-rd-dimension-20260905)
- StandardCost/ActualCost 增加 research_flag(int,idx)/project_type(varchar30,idx) project_type: platform_dev平台研发/client_delivery客户交付/internal内部管理 - 生产库两表 ALTER 完成, 存量 29+134 行默认 internal(保守防误归研发) - API: create/update 支持写入维度, 未传默认 client_delivery(录入默认), research_flag 未显式传入按 project_type 联动(platform_dev=1); create/update/delete 绑定 entity_id(token优先, 防跨账套) - API: standard/actual 列表支持 project_type/research_flag 过滤 - 前端 CostDashboard: 表单加归集类型下拉(默认客户交付+研发提示), 列表加归集类型列(标签), 工具栏按归集类型过滤 - tests: test_cost.py +6 用例(默认值/联动/过滤/更新) - scripts: verify_cost_research_dimension_e2e.py 生产E2E(标记+清理+回查)
This commit is contained in:
@@ -75,10 +75,13 @@
|
||||
<!-- Tab 2: 标准成本管理 -->
|
||||
<el-tab-pane label="标准成本" name="standard">
|
||||
<div style="margin-bottom:12px;">
|
||||
<el-button type="primary" @click="showStdForm = true; stdForm = {}">+ 新建标准成本</el-button>
|
||||
<el-button type="primary" @click="resetStdForm()">+ 新建标准成本</el-button>
|
||||
<el-select v-model="stdProductFilter" placeholder="产品" clearable style="width:150px;margin-left:8px;" @change="loadStandardCosts">
|
||||
<el-option v-for="p in productOptions" :key="p" :label="p" :value="p" />
|
||||
</el-select>
|
||||
<el-select v-model="stdTypeFilter" placeholder="归集类型" clearable style="width:130px;margin-left:8px;" @change="loadStandardCosts">
|
||||
<el-option v-for="(label, val) in projectTypeOptions" :key="val" :label="label" :value="val" />
|
||||
</el-select>
|
||||
</div>
|
||||
<el-table :data="standardCosts" v-loading="stdLoading" border stripe size="small" style="width:100%;">
|
||||
<el-table-column prop="product_code" label="产品编码" width="100" />
|
||||
@@ -96,6 +99,9 @@
|
||||
<template #default="{ row }"><span style="font-weight:500;">{{ formatMoney(row.standard_cost) }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="version" label="版本" width="60" />
|
||||
<el-table-column prop="project_type" label="归集类型" width="95">
|
||||
<template #default="{ row }"><el-tag size="small" :type="projectTypeTag(row.project_type)">{{ projectTypeLabel(row.project_type) }}</el-tag></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="120" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button size="small" @click="editStd(row)">编辑</el-button>
|
||||
@@ -108,10 +114,13 @@
|
||||
<!-- Tab 3: 成本录入 -->
|
||||
<el-tab-pane label="成本录入" name="entry">
|
||||
<div style="margin-bottom:12px;">
|
||||
<el-button type="primary" @click="showActualForm = true; actualForm = {}">+ 录入实际成本</el-button>
|
||||
<el-button type="primary" @click="showActualForm = true; actualForm = { ...actualFormDefaults }">+ 录入实际成本</el-button>
|
||||
<el-select v-model="entryPeriod" placeholder="期间" style="width:110px;margin-left:8px;" @change="loadActualCosts">
|
||||
<el-option v-for="p in periodOptions" :key="p" :label="p" :value="p" />
|
||||
</el-select>
|
||||
<el-select v-model="entryTypeFilter" placeholder="归集类型" clearable style="width:130px;margin-left:8px;" @change="loadActualCosts">
|
||||
<el-option v-for="(label, val) in projectTypeOptions" :key="val" :label="label" :value="val" />
|
||||
</el-select>
|
||||
</div>
|
||||
<el-table :data="actualCosts" v-loading="actualLoading" border stripe size="small" style="width:100%;">
|
||||
<el-table-column prop="period" label="期间" width="80" />
|
||||
@@ -129,6 +138,9 @@
|
||||
<template #default="{ row }"><span style="font-weight:500;">{{ formatMoney(row.actual_cost) }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="source" label="来源" width="70" />
|
||||
<el-table-column prop="project_type" label="归集类型" width="95">
|
||||
<template #default="{ row }"><el-tag size="small" :type="projectTypeTag(row.project_type)">{{ projectTypeLabel(row.project_type) }}</el-tag></template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
|
||||
@@ -291,6 +303,12 @@
|
||||
<el-form-item label="标准用量"><el-input-number v-model="stdForm.standard_quantity" :min="0" :precision="2" style="width:200px;" /></el-form-item>
|
||||
<el-form-item label="单位"><el-input v-model="stdForm.unit" style="width:150px;" /></el-form-item>
|
||||
<el-form-item label="标准单价"><el-input-number v-model="stdForm.standard_price" :min="0" :precision="2" style="width:200px;" /></el-form-item>
|
||||
<el-form-item label="归集类型">
|
||||
<el-select v-model="stdForm.project_type" style="width:200px;">
|
||||
<el-option v-for="(label, val) in projectTypeOptions" :key="val" :label="label" :value="val" />
|
||||
</el-select>
|
||||
<div v-if="stdForm.project_type === 'platform_dev'" style="font-size:12px;color:#e6a23c;line-height:1.4;">平台研发投入 → 自动标记研发(research_flag=1),可作研发费用加计扣除归集</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="showStdForm = false">取消</el-button>
|
||||
@@ -308,6 +326,12 @@
|
||||
<el-form-item label="项目名称"><el-input v-model="actualForm.item_name" /></el-form-item>
|
||||
<el-form-item label="实际用量"><el-input-number v-model="actualForm.actual_quantity" :min="0" :precision="2" style="width:200px;" /></el-form-item>
|
||||
<el-form-item label="实际单价"><el-input-number v-model="actualForm.actual_price" :min="0" :precision="2" style="width:200px;" /></el-form-item>
|
||||
<el-form-item label="归集类型">
|
||||
<el-select v-model="actualForm.project_type" style="width:200px;">
|
||||
<el-option v-for="(label, val) in projectTypeOptions" :key="val" :label="label" :value="val" />
|
||||
</el-select>
|
||||
<div v-if="actualForm.project_type === 'platform_dev'" style="font-size:12px;color:#e6a23c;line-height:1.4;">平台研发投入 → 自动标记研发(research_flag=1),可作研发费用加计扣除归集</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="showActualForm = false">取消</el-button>
|
||||
@@ -377,6 +401,14 @@ const costTypeMap: Record<string, string> = { material: '材料', labor: '人工
|
||||
const costTypeTagMap: Record<string, string> = { material: 'primary', labor: 'success', overhead: 'warning' }
|
||||
function costTypeLabel(t: string) { return costTypeMap[t] || t }
|
||||
function costTypeTag(t: string) { return costTypeTagMap[t] || 'info' }
|
||||
|
||||
// ── 研发归集维度(2026-09-05 研发费用加计扣除归集)──
|
||||
const projectTypeOptions: Record<string, string> = {
|
||||
platform_dev: '平台研发', client_delivery: '客户交付', internal: '内部管理',
|
||||
}
|
||||
const projectTypeTagMap: Record<string, string> = { platform_dev: 'danger', client_delivery: 'primary', internal: 'info' }
|
||||
function projectTypeLabel(t: string) { return projectTypeOptions[t] || '未分类' }
|
||||
function projectTypeTag(t: string) { return projectTypeTagMap[t] || 'info' }
|
||||
function formatMoney(v: any) {
|
||||
if (v === null || v === undefined) return '--'
|
||||
return Number(v).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
||||
@@ -423,12 +455,20 @@ const showStdForm = ref(false)
|
||||
const stdForm = ref<any>({})
|
||||
const stdSaving = ref(false)
|
||||
const stdProductFilter = ref('')
|
||||
const stdTypeFilter = ref('')
|
||||
const stdFormDefaults = { project_type: 'client_delivery' }
|
||||
|
||||
function resetStdForm() {
|
||||
stdForm.value = { ...stdFormDefaults }
|
||||
showStdForm.value = true
|
||||
}
|
||||
|
||||
async function loadStandardCosts() {
|
||||
stdLoading.value = true
|
||||
try {
|
||||
const params: any = { entity_id: getEntityId() }
|
||||
if (stdProductFilter.value) params.product_code = stdProductFilter.value
|
||||
if (stdTypeFilter.value) params.project_type = stdTypeFilter.value
|
||||
const r: any = await costApi.listStandardCosts(params)
|
||||
standardCosts.value = r.data || []
|
||||
} catch (e) { ElMessage.error('加载标准成本失败') }
|
||||
@@ -474,11 +514,14 @@ const entryPeriod = ref(overviewPeriod.value)
|
||||
const showActualForm = ref(false)
|
||||
const actualForm = ref<any>({})
|
||||
const actualSaving = ref(false)
|
||||
const entryTypeFilter = ref('')
|
||||
const actualFormDefaults = { period: '', product_code: '', product_name: '', cost_type: 'material', item_name: '', project_type: 'client_delivery' }
|
||||
|
||||
async function loadActualCosts() {
|
||||
actualLoading.value = true
|
||||
try {
|
||||
const params: any = { period: entryPeriod.value, entity_id: getEntityId() }
|
||||
if (entryTypeFilter.value) params.project_type = entryTypeFilter.value
|
||||
const r: any = await costApi.listActualCosts(params)
|
||||
actualCosts.value = r.data || []
|
||||
} catch (e) { ElMessage.error('加载实际成本失败') }
|
||||
|
||||
Reference in New Issue
Block a user