2020706005
This commit is contained in:
@@ -160,25 +160,6 @@ export async function prCostRemove(expense_code) {
|
||||
return res?.data ?? res
|
||||
}
|
||||
|
||||
/** POST /api/pr/cost/amortize — 月度批量分摊(聚合时间段内已确认采购,逐笔未分摊费用) */
|
||||
export async function prCostAmortize(body) {
|
||||
const payload = {
|
||||
start_time: str(body?.start_time),
|
||||
end_time: str(body?.end_time),
|
||||
}
|
||||
if (!payload.start_time || !payload.end_time) throw new Error('请选择分摊时间段')
|
||||
const expense_code = pickPrExpenseCode(body)
|
||||
if (expense_code) payload.expense_code = expense_code
|
||||
const res = await request.post('/api/pr/cost/amortize', payload, { skipErrorNotify: true })
|
||||
return res?.data ?? res
|
||||
}
|
||||
|
||||
/** 已分摊 / 无匹配采购 / 时段内无可分摊等业务提示,非致命错误 */
|
||||
export function isBenignAmortizeError(err) {
|
||||
const msg = pickApiErrorMessage(err, '')
|
||||
return !msg || /没有未分摊|已分摊|无需分摊|无可分摊|无匹配|没有.*采购|暂无/.test(msg)
|
||||
}
|
||||
|
||||
/** 分页拉全量公关费用 */
|
||||
export async function prCostListedAll(body = {}) {
|
||||
const limit = 50
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
import request from '@/utils/request'
|
||||
import { pickGoodsNameFromListedRow } from '@/utils/goodsCategorys'
|
||||
|
||||
function str(v) {
|
||||
return String(v ?? '').trim()
|
||||
}
|
||||
|
||||
/** 默认内销(暂无出口场景) */
|
||||
export const PRICING_TRADE_DOMESTIC = 'DOMESTIC'
|
||||
|
||||
/** 成本围栏 · 纳税身份 Tab(key 即接口 taxpayer_type 枚举) */
|
||||
export const PRICING_TAXPAYER_TABS = [
|
||||
{ key: 'GENERAL', label: '一般纳税人' },
|
||||
{ key: 'SMALL', label: '小规模纳税人' },
|
||||
{ key: 'INDIVIDUAL', label: '收据组织' },
|
||||
]
|
||||
|
||||
/** @deprecated 请直接用 PRICING_TAXPAYER_TABS[].key */
|
||||
export const PRICING_TAXPAYER_BY_ORG = {
|
||||
general: 'GENERAL',
|
||||
small: 'SMALL',
|
||||
personal: 'INDIVIDUAL',
|
||||
}
|
||||
|
||||
export const PRICING_TAXPAYER_DEFAULT = 'GENERAL'
|
||||
|
||||
const PRICING_TAXPAYER_KEYS = new Set(PRICING_TAXPAYER_TABS.map((t) => t.key))
|
||||
|
||||
/** 接口 data.list */
|
||||
function unwrapPricingTableList(res) {
|
||||
const envelope = res?.data ?? res
|
||||
if (Array.isArray(envelope)) return envelope
|
||||
const payload = envelope?.data ?? envelope
|
||||
if (Array.isArray(payload)) return payload
|
||||
if (payload && typeof payload === 'object') {
|
||||
const list = payload.list ?? payload.rows ?? payload.table
|
||||
if (Array.isArray(list)) return list
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
function pickText(raw, ...keys) {
|
||||
for (const k of keys) {
|
||||
const v = raw[k]
|
||||
if (v == null) continue
|
||||
const s = String(v).trim()
|
||||
if (s) return s
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/** 税率等:0 为有效值 */
|
||||
function pickRate(raw, ...keys) {
|
||||
for (const k of keys) {
|
||||
const v = raw[k]
|
||||
if (v == null || v === '') continue
|
||||
const n = Number(v)
|
||||
if (Number.isFinite(n)) return n
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/** 金额:过滤 null / 非有限数 / 明显异常值 */
|
||||
function pickMoney(raw, ...keys) {
|
||||
for (const k of keys) {
|
||||
const v = raw[k]
|
||||
if (v == null || v === '') continue
|
||||
const n = Number(v)
|
||||
if (!Number.isFinite(n)) continue
|
||||
if (Math.abs(n) >= 1e12) continue
|
||||
return n
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/** 定价决策表行 → 成本围栏表格(字段对齐 /api/pricing/table) */
|
||||
export function normalizePricingTableRow(raw, idx = 0) {
|
||||
if (!raw || typeof raw !== 'object') return null
|
||||
|
||||
const supplier_bloc_name = pickText(raw, 'supplier_bloc_name')
|
||||
const supplier_company_name = pickText(raw, 'supplier_company_name')
|
||||
const goods_name = pickGoodsNameFromListedRow(raw) || pickText(raw, 'goods_name')
|
||||
const bill_type = pickText(raw, 'bill_name', 'bill_type')
|
||||
const input_tax_rate = pickRate(raw, 'bill_tax', 'input_tax_rate')
|
||||
const source_company_name = pickText(raw, 'receive_company_name', 'source_company_name')
|
||||
const internal_min_price = pickMoney(raw, 'internal_price', 'internal_min_price')
|
||||
const internal_bill_type = pickText(raw, 'internal_bill_name', 'internal_bill_type')
|
||||
const internal_input_tax_rate = pickRate(raw, 'internal_bill_tax', 'internal_input_tax_rate')
|
||||
const source_rdc_name = pickText(raw, 'rdc_warehouse_name', 'source_rdc_name')
|
||||
const source_rdc_code = pickText(raw, 'rdc_warehouse_code', 'source_rdc')
|
||||
const fence_value = pickMoney(raw, 'fence_price', 'fence_value')
|
||||
|
||||
const bloc = pickText(raw, 'supplier_bloc_code')
|
||||
const company = pickText(raw, 'supplier_company_code')
|
||||
|
||||
return {
|
||||
key: pickText(raw, 'key', 'id') ?? `${bloc || 'bloc'}-${company || idx}`,
|
||||
supplier_bloc_name,
|
||||
supplier_company_name,
|
||||
goods_name,
|
||||
bill_type,
|
||||
input_tax_rate,
|
||||
source_company_name,
|
||||
internal_min_price,
|
||||
internal_bill_type,
|
||||
internal_input_tax_rate,
|
||||
source_rdc: source_rdc_name || source_rdc_code,
|
||||
source_rdc_name,
|
||||
fence_value,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/pricing/table
|
||||
* 定价决策表:按 RDC、商品、纳税人身份、交易类型生成围栏支持数据
|
||||
*/
|
||||
export async function pricingTableQuery(body = {}) {
|
||||
const taxpayer_type = str(body.taxpayer_type).toUpperCase()
|
||||
const trade_type = str(body.trade_type) || PRICING_TRADE_DOMESTIC
|
||||
const goods_code = str(body.goods_code)
|
||||
const rdc_code = str(body.rdc_code)
|
||||
if (!taxpayer_type) throw new Error('缺少纳税人身份')
|
||||
if (!PRICING_TAXPAYER_KEYS.has(taxpayer_type)) {
|
||||
throw new Error(`纳税人身份无效:${taxpayer_type}`)
|
||||
}
|
||||
if (!goods_code) throw new Error('缺少商品编码')
|
||||
if (!rdc_code) throw new Error('缺少 RDC 仓库编码')
|
||||
|
||||
const res = await request.post('/api/pricing/table', {
|
||||
taxpayer_type,
|
||||
trade_type,
|
||||
goods_code,
|
||||
rdc_code,
|
||||
})
|
||||
return unwrapPricingTableList(res)
|
||||
.map((row, idx) => normalizePricingTableRow(row, idx))
|
||||
.filter(Boolean)
|
||||
}
|
||||
|
||||
export function pickPricingTableError(err, fallback = '加载定价决策表失败') {
|
||||
return String(err?.data?.message || err?.message || fallback).trim() || fallback
|
||||
}
|
||||
@@ -287,19 +287,3 @@ export async function allocShareListed(body = {}) {
|
||||
total,
|
||||
}
|
||||
}
|
||||
|
||||
/** POST /api/warehouse/alloc/amortize */
|
||||
export async function allocAmortize(body) {
|
||||
const payload = {
|
||||
start_time: str(body?.start_time),
|
||||
end_time: str(body?.end_time),
|
||||
}
|
||||
if (!payload.start_time || !payload.end_time) throw new Error('请选择分摊时间段')
|
||||
const res = await request.post('/api/warehouse/alloc/amortize', payload, { skipErrorNotify: true })
|
||||
return res?.data ?? res
|
||||
}
|
||||
|
||||
export function isBenignAllocAmortizeError(err) {
|
||||
const msg = pickApiErrorMessage(err, '')
|
||||
return !msg || /没有|已分摊|无需分摊|无可分摊|暂无|为空/.test(msg)
|
||||
}
|
||||
|
||||
@@ -270,19 +270,3 @@ export async function deployShareListed(body = {}) {
|
||||
total,
|
||||
}
|
||||
}
|
||||
|
||||
/** POST /api/warehouse/deploy/amortize */
|
||||
export async function deployAmortize(body) {
|
||||
const payload = {
|
||||
start_time: str(body?.start_time),
|
||||
end_time: str(body?.end_time),
|
||||
}
|
||||
if (!payload.start_time || !payload.end_time) throw new Error('请选择分摊时间段')
|
||||
const res = await request.post('/api/warehouse/deploy/amortize', payload, { skipErrorNotify: true })
|
||||
return res?.data ?? res
|
||||
}
|
||||
|
||||
export function isBenignDeployAmortizeError(err) {
|
||||
const msg = pickApiErrorMessage(err, '')
|
||||
return !msg || /没有|已分摊|无需分摊|无可分摊|暂无|为空/.test(msg)
|
||||
}
|
||||
|
||||
@@ -291,40 +291,6 @@ export async function freightShareListed(body = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
/** POST /api/warehouse/freight/amortize — 月度批量分摊(按已到库订单 + 运费日志维度) */
|
||||
export async function freightAmortize(body) {
|
||||
const payload = {
|
||||
start_time: str(body?.start_time),
|
||||
end_time: str(body?.end_time),
|
||||
}
|
||||
if (!payload.start_time || !payload.end_time) throw new Error('请选择分摊时间段')
|
||||
const res = await request.post('/api/warehouse/freight/amortize', payload, { skipErrorNotify: true })
|
||||
return res?.data ?? res
|
||||
}
|
||||
|
||||
/** 时段内无运费日志 / 已分摊等业务提示,非致命错误 */
|
||||
export function isBenignFreightAmortizeError(err) {
|
||||
const msg = pickApiErrorMessage(err, '')
|
||||
return !msg || /没有|已分摊|无需分摊|无可分摊|无运费|暂无|为空/.test(msg)
|
||||
}
|
||||
|
||||
/** 分页拉全量物料运费日志(跑批前校验时段内是否有日志) */
|
||||
export async function freightLogsListedAll(body = {}) {
|
||||
const limit = 50
|
||||
let page = 1
|
||||
const all = []
|
||||
let total = 0
|
||||
for (;;) {
|
||||
const res = await freightLogsListed({ ...body, page, limit })
|
||||
const batch = res.list || []
|
||||
all.push(...batch)
|
||||
total = res.total
|
||||
if (!batch.length || all.length >= total) break
|
||||
page += 1
|
||||
}
|
||||
return { list: all, total }
|
||||
}
|
||||
|
||||
export function pickApiErrorMessage(err, fallback = '操作失败') {
|
||||
return String(err?.data?.message || err?.message || fallback).trim() || fallback
|
||||
}
|
||||
|
||||
@@ -254,22 +254,6 @@ export async function wasteShareListed(body = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
/** POST /api/warehouse/waste/amortize */
|
||||
export async function wasteAmortize(body) {
|
||||
const payload = {
|
||||
start_time: str(body?.start_time),
|
||||
end_time: str(body?.end_time),
|
||||
}
|
||||
if (!payload.start_time || !payload.end_time) throw new Error('请选择分摊时间段')
|
||||
const res = await request.post('/api/warehouse/waste/amortize', payload, { skipErrorNotify: true })
|
||||
return res?.data ?? res
|
||||
}
|
||||
|
||||
export function isBenignWasteAmortizeError(err) {
|
||||
const msg = pickApiErrorMessage(err, '')
|
||||
return !msg || /没有|已分摊|无需分摊|无可分摊|暂无|为空/.test(msg)
|
||||
}
|
||||
|
||||
export function pickApiErrorMessage(err, fallback = '操作失败') {
|
||||
return String(
|
||||
err?.data?.message
|
||||
|
||||
@@ -1,50 +1,56 @@
|
||||
<template>
|
||||
<div class="gt-cost-panel" v-loading="loading" element-loading-text="加载中…">
|
||||
<div class="gt-cost-panel">
|
||||
<template v-if="!goodsCode">
|
||||
<el-empty :image-size="56" description="请先保存物料后再查看成本围栏" class="gt-cost-empty" />
|
||||
</template>
|
||||
<template v-else-if="loadError">
|
||||
<el-alert type="error" :title="loadError" show-icon :closable="false" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="gt-cost-layout">
|
||||
<aside class="gt-cost-rdc-nav" aria-label="售出 RDC 列表">
|
||||
<div class="gt-cost-rdc-hd">售出 RDC 列表</div>
|
||||
<template v-if="rdcList.length">
|
||||
<template v-if="rdcOptions.length">
|
||||
<button
|
||||
v-for="rdc in rdcList"
|
||||
:key="rdc.warehouse_code"
|
||||
v-for="rdc in rdcOptions"
|
||||
:key="rdc.code"
|
||||
type="button"
|
||||
class="gt-cost-rdc-item"
|
||||
:class="{ 'is-active': activeRdcCode === rdc.warehouse_code }"
|
||||
@click="activeRdcCode = rdc.warehouse_code"
|
||||
:class="{ 'is-active': activeRdcCode === rdc.code }"
|
||||
@click="activeRdcCode = rdc.code"
|
||||
>
|
||||
<span class="gt-cost-rdc-name">{{ rdc.name || rdc.warehouse_code }}</span>
|
||||
<span v-if="rdc.warehouse_code" class="gt-cost-rdc-code">{{ rdc.warehouse_code }}</span>
|
||||
<span class="gt-cost-rdc-name">{{ rdcDisplayName(rdc) }}</span>
|
||||
<span v-if="rdcDisplaySub(rdc)" class="gt-cost-rdc-code">{{ rdcDisplaySub(rdc) }}</span>
|
||||
</button>
|
||||
</template>
|
||||
<p v-else class="gt-cost-rdc-empty">RDC 列表接口待接入</p>
|
||||
<p v-else class="gt-cost-rdc-empty">暂无关联 RDC</p>
|
||||
</aside>
|
||||
|
||||
<div class="gt-cost-main">
|
||||
<div class="gt-cost-org-tabs" role="tablist" aria-label="组织类型">
|
||||
<button
|
||||
v-for="tab in ORG_TYPE_TABS"
|
||||
v-for="tab in PRICING_TAXPAYER_TABS"
|
||||
:key="tab.key"
|
||||
type="button"
|
||||
role="tab"
|
||||
class="gt-cost-org-tab"
|
||||
:class="[
|
||||
{ 'is-active': activeOrgType === tab.key },
|
||||
{ 'is-active': activeTaxpayerType === tab.key },
|
||||
orgTypeTabClass(tab.key),
|
||||
]"
|
||||
:aria-selected="activeOrgType === tab.key"
|
||||
@click="activeOrgType = tab.key"
|
||||
:aria-selected="activeTaxpayerType === tab.key"
|
||||
@click="activeTaxpayerType = tab.key"
|
||||
>
|
||||
{{ tab.label }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<el-alert
|
||||
v-if="loadError"
|
||||
type="error"
|
||||
:title="loadError"
|
||||
show-icon
|
||||
:closable="false"
|
||||
class="gt-cost-table-error"
|
||||
/>
|
||||
|
||||
<div class="gt-cost-table-wrap">
|
||||
<div class="wops-flex-table is-cols-cost-fence">
|
||||
<div class="wops-flex-scroll">
|
||||
@@ -60,7 +66,13 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!fenceRows.length" class="wops-empty-wrap">
|
||||
<div
|
||||
class="gt-cost-table-body"
|
||||
v-loading="loading"
|
||||
element-loading-text="加载中…"
|
||||
element-loading-background="rgba(255, 255, 255, 0.72)"
|
||||
>
|
||||
<div v-if="!loading && !fenceRows.length" class="wops-empty-wrap">
|
||||
<p class="wops-empty">暂无围栏数据</p>
|
||||
</div>
|
||||
|
||||
@@ -82,15 +94,15 @@
|
||||
<div class="wops-col wops-col--plain">
|
||||
<span class="wops-ellipsis">{{ row.bill_type || '—' }}</span>
|
||||
</div>
|
||||
<div class="wops-col wops-col--num">{{ fmtTaxRate(row.input_tax_rate) }}</div>
|
||||
<div class="wops-col wops-col--num gt-cost-col--tax-rate">{{ fmtTaxRate(row.input_tax_rate) }}</div>
|
||||
<div class="wops-col wops-col--wh">
|
||||
<span class="wops-name wops-ellipsis">{{ row.source_company_name || '—' }}</span>
|
||||
</div>
|
||||
<div class="wops-col wops-col--num">{{ fmtMoney(row.internal_min_price) }}</div>
|
||||
<div class="wops-col wops-col--num gt-cost-col--min-price">{{ fmtMoney(row.internal_min_price) }}</div>
|
||||
<div class="wops-col wops-col--plain">
|
||||
<span class="wops-ellipsis">{{ row.internal_bill_type || '—' }}</span>
|
||||
</div>
|
||||
<div class="wops-col wops-col--num">{{ fmtTaxRate(row.internal_input_tax_rate) }}</div>
|
||||
<div class="wops-col wops-col--num gt-cost-col--tax-rate">{{ fmtTaxRate(row.internal_input_tax_rate) }}</div>
|
||||
<div class="wops-col wops-col--wh">
|
||||
<span class="wops-name wops-ellipsis">{{ row.source_rdc_name || row.source_rdc || '—' }}</span>
|
||||
</div>
|
||||
@@ -101,23 +113,28 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { mapGoodsRdcsToOptions } from '@/utils/warehouseGlobalTabSchema'
|
||||
import {
|
||||
PRICING_TAXPAYER_DEFAULT,
|
||||
PRICING_TAXPAYER_TABS,
|
||||
PRICING_TRADE_DOMESTIC,
|
||||
pickPricingTableError,
|
||||
pricingTableQuery,
|
||||
} from '@/api/pricingTable'
|
||||
|
||||
const props = defineProps({
|
||||
goodsCode: { type: String, default: '' },
|
||||
/** 物料 /goods/view 返回的 rdcs,与仓库管理页签同源 */
|
||||
rdcList: { type: Array, default: () => [] },
|
||||
})
|
||||
|
||||
const ORG_TYPE_TABS = [
|
||||
{ key: 'general', label: '一般纳税人' },
|
||||
{ key: 'small', label: '小规模纳税人' },
|
||||
{ key: 'personal', label: '收据组织' },
|
||||
]
|
||||
|
||||
const FENCE_COLUMNS = [
|
||||
{ key: 'supplier_bloc_name', label: '供应商集团名称' },
|
||||
{ key: 'supplier_company_name', label: '供应商组织名称' },
|
||||
@@ -161,20 +178,48 @@ function colHeadColorClass(key) {
|
||||
}
|
||||
|
||||
function orgTypeTabClass(key) {
|
||||
if (key === 'general') return 'is-general'
|
||||
if (key === 'small') return 'is-small'
|
||||
if (key === 'personal') return 'is-personal'
|
||||
if (key === 'GENERAL') return 'is-general'
|
||||
if (key === 'SMALL') return 'is-small'
|
||||
if (key === 'INDIVIDUAL') return 'is-personal'
|
||||
return ''
|
||||
}
|
||||
|
||||
const loading = ref(false)
|
||||
const loadError = ref('')
|
||||
const activeOrgType = ref('general')
|
||||
/** 当前 Tab 对应的接口 taxpayer_type 枚举 */
|
||||
const activeTaxpayerType = ref(PRICING_TAXPAYER_DEFAULT)
|
||||
const activeRdcCode = ref('')
|
||||
/** @type {import('vue').Ref<Array<{ warehouse_code: string, name: string }>>} */
|
||||
const rdcList = ref([])
|
||||
/** @type {import('vue').Ref<object[]>} */
|
||||
const fenceRows = ref([])
|
||||
/** 递增序号,避免切换 RDC / 纳税身份时旧请求覆盖新结果 */
|
||||
let fenceFetchSeq = 0
|
||||
|
||||
const rdcOptions = computed(() => mapGoodsRdcsToOptions(props.rdcList))
|
||||
|
||||
function rdcDisplayName(rdc) {
|
||||
const name = String(rdc?.name || '').trim()
|
||||
const code = String(rdc?.code || '').trim()
|
||||
if (name && name !== code) return name
|
||||
return code || name || '—'
|
||||
}
|
||||
|
||||
function rdcDisplaySub(rdc) {
|
||||
const name = String(rdc?.name || '').trim()
|
||||
const code = String(rdc?.code || '').trim()
|
||||
if (name && name !== code) return code
|
||||
return ''
|
||||
}
|
||||
|
||||
function pickInitialRdcCode() {
|
||||
const opts = rdcOptions.value
|
||||
if (!opts.length) {
|
||||
activeRdcCode.value = ''
|
||||
return
|
||||
}
|
||||
const cur = String(activeRdcCode.value || '').trim()
|
||||
if (cur && opts.some((r) => r.code === cur)) return
|
||||
activeRdcCode.value = opts[0].code
|
||||
}
|
||||
|
||||
function fmtMoney(v) {
|
||||
if (v == null || v === '') return '—'
|
||||
@@ -190,23 +235,61 @@ function fmtTaxRate(v) {
|
||||
return `${n}%`
|
||||
}
|
||||
|
||||
async function fetchFenceTable() {
|
||||
const goods_code = String(props.goodsCode || '').trim()
|
||||
const rdc_code = String(activeRdcCode.value || '').trim()
|
||||
const taxpayer_type = String(activeTaxpayerType.value || '').trim()
|
||||
|
||||
if (!goods_code) {
|
||||
fenceRows.value = []
|
||||
return
|
||||
}
|
||||
if (!rdc_code) {
|
||||
fenceRows.value = []
|
||||
return
|
||||
}
|
||||
if (!taxpayer_type) {
|
||||
fenceRows.value = []
|
||||
return
|
||||
}
|
||||
|
||||
const seq = ++fenceFetchSeq
|
||||
loading.value = true
|
||||
loadError.value = ''
|
||||
try {
|
||||
const rows = await pricingTableQuery({
|
||||
goods_code,
|
||||
rdc_code,
|
||||
taxpayer_type,
|
||||
trade_type: PRICING_TRADE_DOMESTIC,
|
||||
})
|
||||
if (seq !== fenceFetchSeq) return
|
||||
if (activeTaxpayerType.value !== taxpayer_type) return
|
||||
fenceRows.value = rows
|
||||
} catch (e) {
|
||||
if (seq !== fenceFetchSeq) return
|
||||
if (activeTaxpayerType.value !== taxpayer_type) return
|
||||
loadError.value = pickPricingTableError(e)
|
||||
// 切换 Tab/RDC 失败时保留上一份数据,避免表格闪空
|
||||
} finally {
|
||||
if (seq === fenceFetchSeq) loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function loadCost() {
|
||||
const code = String(props.goodsCode || '').trim()
|
||||
loadError.value = ''
|
||||
rdcList.value = []
|
||||
fenceRows.value = []
|
||||
if (!code) {
|
||||
activeTaxpayerType.value = PRICING_TAXPAYER_DEFAULT
|
||||
activeRdcCode.value = ''
|
||||
activeOrgType.value = 'general'
|
||||
if (!code) return
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
// RDC 列表与围栏表格数据接口待接入;先保留布局与组织类型切换
|
||||
} catch (e) {
|
||||
loadError.value = String(e?.data?.message || e?.message || '加载失败').trim() || '加载失败'
|
||||
} finally {
|
||||
loading.value = false
|
||||
return
|
||||
}
|
||||
if (!PRICING_TAXPAYER_TABS.some((t) => t.key === activeTaxpayerType.value)) {
|
||||
activeTaxpayerType.value = PRICING_TAXPAYER_DEFAULT
|
||||
}
|
||||
pickInitialRdcCode()
|
||||
await fetchFenceTable()
|
||||
}
|
||||
|
||||
watch(
|
||||
@@ -215,9 +298,21 @@ watch(
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
watch(activeOrgType, () => {
|
||||
// 组织类型切换后重新拉取列表(接口接入后实现)
|
||||
fenceRows.value = []
|
||||
watch(
|
||||
() => props.rdcList,
|
||||
() => {
|
||||
pickInitialRdcCode()
|
||||
void fetchFenceTable()
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
|
||||
watch(activeRdcCode, () => {
|
||||
void fetchFenceTable()
|
||||
})
|
||||
|
||||
watch(activeTaxpayerType, () => {
|
||||
void fetchFenceTable()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -359,6 +454,10 @@ watch(activeOrgType, () => {
|
||||
border-color: #67c23a;
|
||||
background: #f0f9eb;
|
||||
}
|
||||
.gt-cost-table-error {
|
||||
flex-shrink: 0;
|
||||
margin: 0 12px;
|
||||
}
|
||||
.gt-cost-table-wrap {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
@@ -380,6 +479,13 @@ watch(activeOrgType, () => {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
.gt-cost-table-body {
|
||||
flex: 1;
|
||||
min-height: 120px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.gt-cost-table-wrap :deep(.wops-empty-wrap) {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
@@ -417,9 +523,27 @@ watch(activeOrgType, () => {
|
||||
.gt-cost-table-wrap :deep(.wops-col--goods) {
|
||||
padding-left: 12px;
|
||||
}
|
||||
.gt-cost-table-wrap :deep(.gt-cost-head-col--tax),
|
||||
.gt-cost-table-wrap :deep(.gt-cost-head-col--int-tax),
|
||||
.gt-cost-table-wrap :deep(.gt-cost-head-col--min-price) {
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
text-align: center;
|
||||
overflow: visible;
|
||||
text-overflow: clip;
|
||||
}
|
||||
.gt-cost-table-wrap :deep(.gt-cost-col--tax-rate),
|
||||
.gt-cost-table-wrap :deep(.gt-cost-col--min-price) {
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
}
|
||||
.gt-cost-table-wrap :deep(.is-cols-cost-fence .wops-col--num) {
|
||||
padding-right: 6px;
|
||||
}
|
||||
.gt-cost-table-wrap :deep(.is-cols-cost-fence .gt-cost-col--tax-rate),
|
||||
.gt-cost-table-wrap :deep(.is-cols-cost-fence .gt-cost-col--min-price) {
|
||||
padding-right: 4px;
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
.gt-cost-layout {
|
||||
flex-direction: column;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"buildId": "20260706001",
|
||||
"generatedAt": "2026-07-06T04:43:39.914Z"
|
||||
"buildId": "20260706004",
|
||||
"generatedAt": "2026-07-06T11:25:37.842Z"
|
||||
}
|
||||
@@ -50,6 +50,31 @@ export const APP_CHANGELOG = [
|
||||
{
|
||||
date: '2026-07-06',
|
||||
items: [
|
||||
{
|
||||
icon: 'mdi:fence',
|
||||
kind: 'change',
|
||||
text: '「物料弹窗 → 成本围栏」切换一般纳税人 / 小规模 / 收据组织 Tab 时,仅表格数据区 loading,Tab 按钮与表头保持不动;请求期间保留上一份数据,避免闪空;`taxpayer_type` 直传 `GENERAL` / `SMALL` / `INDIVIDUAL` 枚举。',
|
||||
},
|
||||
{
|
||||
icon: 'mdi:fence',
|
||||
kind: 'fix',
|
||||
text: '「物料弹窗 → 成本围栏」定价决策表字段对齐接口:`bill_name`、`bill_tax`、`receive_company_name`、`internal_price`、`internal_bill_name`、`internal_bill_tax`、`rdc_warehouse_name`、`fence_price` 等;商品名读 `goods_categorys`;税率为 0 显示 `0%`;明显异常金额过滤为「—」。',
|
||||
},
|
||||
{
|
||||
icon: 'mdi:fence',
|
||||
kind: 'add',
|
||||
text: '「物料弹窗 → 成本围栏」接入 `POST /api/pricing/table` 定价决策表:左侧售出 RDC(与仓库管理同源 `rdcs`);顶栏纳税身份 Tab;表格展示供应商集团/组织、票据与进项税率、源采购组织、内部交易价与票据、源 RDC、围栏值;`trade_type` 固定 `DOMESTIC`,切换 RDC / 纳税身份 / 物料自动请求。',
|
||||
},
|
||||
{
|
||||
icon: 'mdi:warehouse',
|
||||
kind: 'change',
|
||||
text: '「物料弹窗 → 仓库管理」布局调整:去掉中间费用票据竖 Tab;各 type=4 费用票据纵向分组展示,组内「提成 / 利润」并排,各含内部/零售/渠道率与额(额后加「元」);左侧 RDC 列表宽 168px。',
|
||||
},
|
||||
{
|
||||
icon: 'mdi:format-list-bulleted',
|
||||
kind: 'change',
|
||||
text: '「物料列表」新增/编辑弹窗宽度改为 88vw;基础属性字段网格按右侧可用宽度自适应列数(1920 下约 6 列),大屏随宽度递增。',
|
||||
},
|
||||
{
|
||||
icon: 'mdi:cart-outline',
|
||||
kind: 'fix',
|
||||
|
||||
@@ -89,9 +89,9 @@
|
||||
.wops-flex-table.is-cols-pr-cost {
|
||||
--wops-cols: 7fr 10.5fr 10.5fr 7fr 8fr 8fr 6fr 9fr 6fr 8fr;
|
||||
}
|
||||
/* 物料成本围栏(11 列):内部交易相关表头加宽,配合 88vw 弹窗单行显示 */
|
||||
/* 物料成本围栏(11 列):税率/内部最低价收窄,源采购组织加宽 */
|
||||
.wops-flex-table.is-cols-cost-fence {
|
||||
--wops-cols: 8fr 9fr 10fr 7fr 6fr 9fr 10fr 8fr 11fr 7fr 5fr;
|
||||
--wops-cols: 10fr 10fr 10fr 7fr minmax(4.2em, 4fr) 10fr minmax(7.2em, 8fr) 8fr minmax(8.5em, 9fr) 9fr 5fr;
|
||||
}
|
||||
|
||||
.wops-flex-scroll {
|
||||
|
||||
@@ -76,7 +76,6 @@
|
||||
/>
|
||||
<el-button type="primary" size="small" @click="doSearch">查询</el-button>
|
||||
<el-button size="small" @click="resetFilters">重置</el-button>
|
||||
<el-button type="warning" size="small" @click="runBatchAndLoad">重新跑批</el-button>
|
||||
</div>
|
||||
|
||||
<div v-loading="loading" :element-loading-text="loadingText" class="asl-body">
|
||||
@@ -155,9 +154,7 @@ import {
|
||||
normalizeFinanceBillType,
|
||||
} from '@/utils/financeBillTypes'
|
||||
import {
|
||||
allocAmortize,
|
||||
allocShareListed,
|
||||
isBenignAllocAmortizeError,
|
||||
pickApiErrorMessage,
|
||||
} from '@/api/warehouseAlloc'
|
||||
|
||||
@@ -365,46 +362,6 @@ function onSizeChange() {
|
||||
search()
|
||||
}
|
||||
|
||||
async function runBatchAndLoad() {
|
||||
const start_time = String(batchRange.start_time || '').trim()
|
||||
const end_time = String(batchRange.end_time || '').trim()
|
||||
if (!start_time || !end_time) {
|
||||
ElMessage.warning('请选择分摊时间段(开始日期、结束日期)')
|
||||
return
|
||||
}
|
||||
if (start_time > end_time) {
|
||||
ElMessage.warning('开始日期不能晚于结束日期')
|
||||
return
|
||||
}
|
||||
|
||||
page.value = 1
|
||||
loading.value = true
|
||||
loadingText.value = '正在跑批中…'
|
||||
try {
|
||||
let amortizeHint = ''
|
||||
try {
|
||||
await allocAmortize({ start_time, end_time })
|
||||
} catch (e) {
|
||||
if (!isBenignAllocAmortizeError(e)) {
|
||||
throw e
|
||||
}
|
||||
amortizeHint = pickApiErrorMessage(e, '')
|
||||
}
|
||||
|
||||
activeBillCode.value = ''
|
||||
await fetchShareList()
|
||||
if (amortizeHint) {
|
||||
ElMessage.warning(amortizeHint)
|
||||
} else if (rows.value.length) {
|
||||
ElMessage.success(`跑批完成,分摊结果 ${total.value} 条`)
|
||||
}
|
||||
} catch (e) {
|
||||
ElMessage.error(pickApiErrorMessage(e, '跑批失败'))
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function search() {
|
||||
loading.value = true
|
||||
loadingText.value = '加载中…'
|
||||
@@ -433,7 +390,7 @@ function resetFilters() {
|
||||
|
||||
async function initPage() {
|
||||
await loadExpenseBills()
|
||||
await runBatchAndLoad()
|
||||
await search()
|
||||
}
|
||||
|
||||
onMounted(initPage)
|
||||
|
||||
@@ -76,7 +76,6 @@
|
||||
/>
|
||||
<el-button type="primary" size="small" @click="doSearch">查询</el-button>
|
||||
<el-button size="small" @click="resetFilters">重置</el-button>
|
||||
<el-button type="warning" size="small" @click="runBatchAndLoad">重新跑批</el-button>
|
||||
</div>
|
||||
|
||||
<div v-loading="loading" :element-loading-text="loadingText" class="dsl-body">
|
||||
@@ -155,9 +154,7 @@ import {
|
||||
normalizeFinanceBillType,
|
||||
} from '@/utils/financeBillTypes'
|
||||
import {
|
||||
deployAmortize,
|
||||
deployShareListed,
|
||||
isBenignDeployAmortizeError,
|
||||
pickApiErrorMessage,
|
||||
} from '@/api/warehouseDeploy'
|
||||
|
||||
@@ -365,46 +362,6 @@ function onSizeChange() {
|
||||
search()
|
||||
}
|
||||
|
||||
async function runBatchAndLoad() {
|
||||
const start_time = String(batchRange.start_time || '').trim()
|
||||
const end_time = String(batchRange.end_time || '').trim()
|
||||
if (!start_time || !end_time) {
|
||||
ElMessage.warning('请选择分摊时间段(开始日期、结束日期)')
|
||||
return
|
||||
}
|
||||
if (start_time > end_time) {
|
||||
ElMessage.warning('开始日期不能晚于结束日期')
|
||||
return
|
||||
}
|
||||
|
||||
page.value = 1
|
||||
loading.value = true
|
||||
loadingText.value = '正在跑批中…'
|
||||
try {
|
||||
let amortizeHint = ''
|
||||
try {
|
||||
await deployAmortize({ start_time, end_time })
|
||||
} catch (e) {
|
||||
if (!isBenignDeployAmortizeError(e)) {
|
||||
throw e
|
||||
}
|
||||
amortizeHint = pickApiErrorMessage(e, '')
|
||||
}
|
||||
|
||||
activeBillCode.value = ''
|
||||
await fetchShareList()
|
||||
if (amortizeHint) {
|
||||
ElMessage.warning(amortizeHint)
|
||||
} else if (rows.value.length) {
|
||||
ElMessage.success(`跑批完成,分摊结果 ${total.value} 条`)
|
||||
}
|
||||
} catch (e) {
|
||||
ElMessage.error(pickApiErrorMessage(e, '跑批失败'))
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function search() {
|
||||
loading.value = true
|
||||
loadingText.value = '加载中…'
|
||||
@@ -433,7 +390,7 @@ function resetFilters() {
|
||||
|
||||
async function initPage() {
|
||||
await loadExpenseBills()
|
||||
await runBatchAndLoad()
|
||||
await search()
|
||||
}
|
||||
|
||||
onMounted(initPage)
|
||||
|
||||
@@ -76,7 +76,6 @@
|
||||
/>
|
||||
<el-button type="primary" size="small" @click="doSearch">查询</el-button>
|
||||
<el-button size="small" @click="resetFilters">重置</el-button>
|
||||
<el-button type="warning" size="small" @click="runBatchAndLoad">重新跑批</el-button>
|
||||
</div>
|
||||
|
||||
<div v-loading="loading" :element-loading-text="loadingText" class="fsl-body">
|
||||
@@ -158,9 +157,7 @@ import {
|
||||
normalizeFinanceBillType,
|
||||
} from '@/utils/financeBillTypes'
|
||||
import {
|
||||
freightAmortize,
|
||||
freightShareListed,
|
||||
isBenignFreightAmortizeError,
|
||||
pickApiErrorMessage,
|
||||
} from '@/api/warehouseFreight'
|
||||
|
||||
@@ -369,41 +366,6 @@ function onSizeChange() {
|
||||
search()
|
||||
}
|
||||
|
||||
async function runBatchAndLoad() {
|
||||
const start_time = String(batchRange.start_time || '').trim()
|
||||
const end_time = String(batchRange.end_time || '').trim()
|
||||
if (!start_time || !end_time) {
|
||||
ElMessage.warning('请选择分摊时间段(开始日期、结束日期)')
|
||||
return
|
||||
}
|
||||
if (start_time > end_time) {
|
||||
ElMessage.warning('开始日期不能晚于结束日期')
|
||||
return
|
||||
}
|
||||
|
||||
page.value = 1
|
||||
loading.value = true
|
||||
loadingText.value = '正在跑批中…'
|
||||
try {
|
||||
try {
|
||||
await freightAmortize({ start_time, end_time })
|
||||
} catch (e) {
|
||||
if (!isBenignFreightAmortizeError(e)) {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
await fetchShareList()
|
||||
if (rows.value.length) {
|
||||
ElMessage.success(`跑批完成,分摊结果 ${total.value} 条`)
|
||||
}
|
||||
} catch (e) {
|
||||
ElMessage.error(pickApiErrorMessage(e, '跑批失败'))
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function search() {
|
||||
loading.value = true
|
||||
loadingText.value = '加载中…'
|
||||
@@ -432,7 +394,7 @@ function resetFilters() {
|
||||
|
||||
async function initPage() {
|
||||
await loadExpenseBills()
|
||||
await runBatchAndLoad()
|
||||
await search()
|
||||
}
|
||||
|
||||
onMounted(initPage)
|
||||
|
||||
@@ -68,7 +68,6 @@
|
||||
/>
|
||||
<el-button type="primary" size="small" @click="doSearch">查询</el-button>
|
||||
<el-button size="small" @click="resetFilters">重置</el-button>
|
||||
<el-button type="warning" size="small" @click="runBatchAndLoad">重新跑批</el-button>
|
||||
</div>
|
||||
|
||||
<div v-loading="loading" :element-loading-text="loadingText" class="pcsl-body">
|
||||
@@ -150,11 +149,9 @@ import {
|
||||
normalizeFinanceBillType,
|
||||
} from '@/utils/financeBillTypes'
|
||||
import {
|
||||
prCostAmortize,
|
||||
prCostShareListed,
|
||||
pickPrCostShareGoodsName,
|
||||
pickApiErrorMessage,
|
||||
isBenignAmortizeError,
|
||||
} from '@/api/prCost'
|
||||
|
||||
defineOptions({ name: 'PrCostShareListed' })
|
||||
@@ -375,51 +372,6 @@ function onSizeChange() {
|
||||
search()
|
||||
}
|
||||
|
||||
/** cost/amortize(月度批量)→ cost/share/listed */
|
||||
async function runBatchAndLoad() {
|
||||
const start_time = String(batchRange.start_time || '').trim()
|
||||
const end_time = String(batchRange.end_time || '').trim()
|
||||
if (!start_time || !end_time) {
|
||||
ElMessage.warning('请选择分摊时间段(开始日期、结束日期)')
|
||||
return
|
||||
}
|
||||
if (start_time > end_time) {
|
||||
ElMessage.warning('开始日期不能晚于结束日期')
|
||||
return
|
||||
}
|
||||
|
||||
page.value = 1
|
||||
loading.value = true
|
||||
loadingText.value = '正在跑批中…'
|
||||
try {
|
||||
let amortizeHint = ''
|
||||
try {
|
||||
await prCostAmortize({ start_time, end_time })
|
||||
} catch (e) {
|
||||
if (!isBenignAmortizeError(e)) {
|
||||
throw e
|
||||
}
|
||||
amortizeHint = pickApiErrorMessage(e, '')
|
||||
}
|
||||
|
||||
activeBillCode.value = ''
|
||||
await fetchShareList()
|
||||
if (amortizeHint) {
|
||||
ElMessage.warning(amortizeHint)
|
||||
} else if (rows.value.length) {
|
||||
ElMessage.success(`跑批完成,分摊结果 ${total.value} 条`)
|
||||
} else {
|
||||
ElMessage.warning(
|
||||
`跑批完成,暂无分摊结果(${start_time} ~ ${end_time})。请确认:① 该时段内有已确认/已到库采购(按订单确认时间,非采购日期);② 公关费用与采购在同一供应组织且商品能匹配;③ 票据 Tab 选「全部」`,
|
||||
)
|
||||
}
|
||||
} catch (e) {
|
||||
ElMessage.error(pickApiErrorMessage(e, '跑批失败'))
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function search() {
|
||||
loading.value = true
|
||||
loadingText.value = '加载中…'
|
||||
@@ -447,7 +399,7 @@ function resetFilters() {
|
||||
|
||||
async function initPage() {
|
||||
await loadExpenseBills()
|
||||
await runBatchAndLoad()
|
||||
await search()
|
||||
}
|
||||
|
||||
onMounted(initPage)
|
||||
|
||||
@@ -44,7 +44,6 @@
|
||||
/>
|
||||
<el-button type="primary" size="small" @click="doSearch">查询</el-button>
|
||||
<el-button size="small" @click="resetFilters">重置</el-button>
|
||||
<el-button type="warning" size="small" @click="runBatchAndLoad">重新跑批</el-button>
|
||||
</div>
|
||||
|
||||
<div v-loading="loading" :element-loading-text="loadingText" class="wsl-body">
|
||||
@@ -115,9 +114,7 @@ import WarehouseOpsNameCode from '@/components/warehouse/WarehouseOpsNameCode.vu
|
||||
import { formatOpsLogTime } from '@/utils/warehouseOpsDisplay'
|
||||
import { pickGoodsNameFromListedRow } from '@/utils/goodsCategorys'
|
||||
import {
|
||||
isBenignWasteAmortizeError,
|
||||
pickApiErrorMessage,
|
||||
wasteAmortize,
|
||||
wasteShareListed,
|
||||
} from '@/api/warehouseWaste'
|
||||
|
||||
@@ -245,45 +242,6 @@ function onSizeChange() {
|
||||
search()
|
||||
}
|
||||
|
||||
async function runBatchAndLoad() {
|
||||
const start_time = String(batchRange.start_time || '').trim()
|
||||
const end_time = String(batchRange.end_time || '').trim()
|
||||
if (!start_time || !end_time) {
|
||||
ElMessage.warning('请选择分摊时间段(开始日期、结束日期)')
|
||||
return
|
||||
}
|
||||
if (start_time > end_time) {
|
||||
ElMessage.warning('开始日期不能晚于结束日期')
|
||||
return
|
||||
}
|
||||
|
||||
page.value = 1
|
||||
loading.value = true
|
||||
loadingText.value = '正在跑批中…'
|
||||
try {
|
||||
let amortizeHint = ''
|
||||
try {
|
||||
await wasteAmortize({ start_time, end_time })
|
||||
} catch (e) {
|
||||
if (!isBenignWasteAmortizeError(e)) {
|
||||
throw e
|
||||
}
|
||||
amortizeHint = pickApiErrorMessage(e, '')
|
||||
}
|
||||
|
||||
await fetchShareList()
|
||||
if (amortizeHint) {
|
||||
ElMessage.warning(amortizeHint)
|
||||
} else if (rows.value.length) {
|
||||
ElMessage.success(`跑批完成,分摊结果 ${total.value} 条`)
|
||||
}
|
||||
} catch (e) {
|
||||
ElMessage.error(pickApiErrorMessage(e, '跑批失败'))
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function search() {
|
||||
loading.value = true
|
||||
loadingText.value = '加载中…'
|
||||
@@ -310,7 +268,7 @@ function resetFilters() {
|
||||
}
|
||||
|
||||
async function initPage() {
|
||||
await runBatchAndLoad()
|
||||
await search()
|
||||
}
|
||||
|
||||
onMounted(initPage)
|
||||
|
||||
@@ -500,7 +500,10 @@
|
||||
/>
|
||||
</div>
|
||||
<div v-if="mlActiveNavKey === ML_NAV_COST" class="mf-nav-pane mf-nav-pane--cost-api">
|
||||
<GlobalTabCostPanel :goods-code="materialEditingId || ''" />
|
||||
<GlobalTabCostPanel
|
||||
:goods-code="materialEditingId || ''"
|
||||
:rdc-list="materialWarehouseRdcs"
|
||||
/>
|
||||
</div>
|
||||
<template v-for="(cat, catIdx) in materialForm.categories" :key="`${cat.category_code}-${catIdx}`">
|
||||
<div
|
||||
|
||||
Reference in New Issue
Block a user