20260708001

This commit is contained in:
xiaojibeier
2026-07-08 16:47:32 +08:00
parent bc37cb526a
commit 31ed31da92
18 changed files with 530 additions and 203 deletions
+26 -2
View File
@@ -17,9 +17,24 @@ import {
pickSupplierBlocCode,
pickSupplierCompanyCode,
pickSupplierDisplayName,
pickSupplierSpecialDeliveryCommissionRate,
formatSupplierSpecialDeliveryCommissionRate,
resolveOrgEffectiveSceneCodes,
} from '@/api/supplySupplier'
function buildCustomerSpecialDeliveryFields(formOrRow) {
const rate = pickSupplierSpecialDeliveryCommissionRate(formOrRow)
return {
special_delivery_commission_rate: rate ?? 0,
}
}
function buildCustomerSpecialDeliveryFieldsFromRow(row) {
return {
special_delivery_commission_rate: pickSupplierSpecialDeliveryCommissionRate(row) ?? 0,
}
}
/** @param {unknown} res */
function unwrapResponseData(res) {
if (res == null) return res
@@ -99,15 +114,22 @@ export function buildCustomerAddPayload(form, { blocCode }) {
return {
...buildSupplierAddPayload(form, { blocCode }),
company_type: 3,
...buildCustomerSpecialDeliveryFields(form),
}
}
export function buildCustomerModifyPayload(form, companyCode, opts = {}) {
return buildSupplierModifyPayload(form, companyCode, opts)
return {
...buildSupplierModifyPayload(form, companyCode, opts),
...buildCustomerSpecialDeliveryFields(form),
}
}
export function buildCustomerModifyPayloadFromRow(row, opts = {}) {
return buildSupplierModifyPayloadFromRow(row, opts)
return {
...buildSupplierModifyPayloadFromRow(row, opts),
...buildCustomerSpecialDeliveryFieldsFromRow(row),
}
}
export async function fetchCustomerGroupTreeListed() {
@@ -132,5 +154,7 @@ export {
pickSupplierCompanyCode as pickCustomerCompanyCode,
pickSupplierDisplayName as pickCustomerDisplayName,
resolveOrgEffectiveSceneCodes,
pickSupplierSpecialDeliveryCommissionRate,
formatSupplierSpecialDeliveryCommissionRate,
buildSupplierFormFields as buildCustomerFormFields,
}
+25 -1
View File
@@ -524,6 +524,30 @@ export function serializeBenefitBuckets(buckets) {
return out
}
/** add 接口:excess_performance / benefit 须为 JSONArray;无票种分组时传 [] */
export function serializeExcessPerformanceForAdd(buckets) {
const src = buckets && typeof buckets === 'object' ? buckets : {}
const namedKeys = Object.keys(src).filter((k) => String(k).trim() !== '')
if (!namedKeys.length) return []
return serializeExcessPerformanceBucketsAsCustomArray(src)
}
/** add 接口:benefit 同 excess_performance,无分组时传 [] */
export function serializeBenefitForAdd(buckets) {
const src = buckets && typeof buckets === 'object' ? buckets : {}
const namedKeys = Object.keys(src).filter((k) => String(k).trim() !== '')
if (!namedKeys.length) return []
return serializeBenefitBucketsAsCustomArray(src)
}
/** 表单 → 新增请求体(超额绩效/好处费为数组) */
export function buildCustomerPriceAddBody(form, extras = {}) {
const body = buildCustomerPriceWriteBody(form, extras)
body.excess_performance = serializeExcessPerformanceForAdd(form?.excess_performance_buckets)
body.benefit = serializeBenefitForAdd(form?.benefit_buckets)
return body
}
/** 表单 → 新增/修改请求体 */
export function buildCustomerPriceWriteBody(form, extras = {}) {
const excess_performance = serializeExcessPerformanceBuckets(form?.excess_performance_buckets)
@@ -586,7 +610,7 @@ export function buildCustomerPriceAddItem(company_code, goods_code, overrides =
const cc = String(company_code ?? '').trim()
const gc = String(goods_code ?? '').trim()
const o = overrides && typeof overrides === 'object' ? overrides : {}
return buildCustomerPriceWriteBody(
return buildCustomerPriceAddBody(
{
excess_performance_buckets: groupsToPerformanceBuckets([]),
benefit_buckets: groupsToBenefitBuckets([]),
+12 -7
View File
@@ -88,16 +88,16 @@ export function normalizePricingTableRow(raw, idx = 0) {
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 source_rdc_code = pickText(raw, 'rdc_warehouse_code', 'source_rdc_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')
const supplier_company_code = pickText(raw, 'supplier_company_code', 'company_code')
return {
key: pickText(raw, 'key', 'id') ?? `${bloc || 'bloc'}-${company || idx}`,
key: pickText(raw, 'key', 'id') ?? `${bloc || 'bloc'}-${supplier_company_code || idx}`,
supplier_bloc_name,
supplier_company_name,
supplier_company_code,
goods_name,
bill_type,
input_tax_rate,
@@ -107,6 +107,7 @@ export function normalizePricingTableRow(raw, idx = 0) {
internal_input_tax_rate,
source_rdc: source_rdc_name || source_rdc_code,
source_rdc_name,
source_rdc_code,
fence_value,
}
}
@@ -150,16 +151,16 @@ function unwrapPricingCalculatePayload(res) {
return envelope
}
/** 算价响应 → debug 文本(供弹窗展示) */
/** 算价响应 → 明细文本(供弹窗展示) */
export function formatPricingCalculateDebug(res) {
const payload = unwrapPricingCalculatePayload(res)
const debug = payload?.debug ?? res?.data?.debug ?? res?.debug
if (debug == null || debug === '') {
const fallback = payload && typeof payload === 'object' ? payload : (res?.data ?? res)
try {
return `(接口未返回 debug)\n\n${JSON.stringify(fallback, null, 2)}`
return `(接口未返回计算明细)\n\n${JSON.stringify(fallback, null, 2)}`
} catch {
return '(接口未返回 debug)'
return '(接口未返回计算明细)'
}
}
if (typeof debug === 'string') return debug
@@ -193,8 +194,12 @@ export async function pricingCalculate(body = {}) {
}
const supply_collection_code = str(body.supply_collection_code)
const customer_price_code = str(body.customer_price_code)
const supplier_company_code = str(body.supplier_company_code)
const warehouse_code = str(body.warehouse_code)
if (supply_collection_code) payload.supply_collection_code = supply_collection_code
if (customer_price_code) payload.customer_price_code = customer_price_code
if (supplier_company_code) payload.supplier_company_code = supplier_company_code
if (warehouse_code) payload.warehouse_code = warehouse_code
return request.post('/api/pricing/calculate', payload)
}
+14 -6
View File
@@ -677,17 +677,17 @@ export function pickCollectionTargetDelayedRebateMonths(row) {
}
/**
* 列表/编辑回填:帐期月数(接口仍用 payment_days,后端新增 payment_months 时再兼容读取)
* 列表/编辑回填:帐期月数(接口字段 payment_months;兼容旧 payment_days)
* @param {object} [row]
*/
export function pickCollectionPaymentMonths(row) {
const r = row && typeof row === 'object' ? row : {}
const v =
r.payment_days
r.payment_months
?? r.payment_month
?? r.payment_days
?? r.payment_period_days
?? r.paymentDays
?? r.payment_months
?? r.payment_month
if (v === null || v === undefined || v === '') return ''
return String(v)
}
@@ -711,6 +711,14 @@ export function resolveOrgContactPerson(raw) {
return { code: '', label: nameLabel || contactRaw }
}
function resolveCollectionPaymentMonthsValue(p) {
const src = p && typeof p === 'object' ? p : {}
const v = src.payment_months ?? src.payment_days
if (v === null || v === undefined || v === '') return 0
const n = Number(v)
return Number.isFinite(n) ? n : 0
}
/**
* POST /api/supply/collection/add 请求体(与三步向导快照对齐)
* @param {object} parts
@@ -751,7 +759,7 @@ export function buildSupplyCollectionAddBody(parts) {
owner_code: String(p.owner_code || '').trim(),
scenes,
warehouses,
payment_days: p.payment_days ?? 60,
payment_months: resolveCollectionPaymentMonthsValue(p),
is_support_acceptance:
p.is_support_acceptance === true || acceptanceRaw === '支持',
is_support_receipt: p.is_support_receipt === true,
@@ -805,7 +813,7 @@ export function buildCoordCollectionSaveBody(parts) {
supplier_org_type: String(p.supplier_org_type || '').trim(),
partner_org_type: String(p.partner_org_type || '').trim(),
warehouse_codes: Array.isArray(p.warehouse_codes) ? [...p.warehouse_codes] : [],
payment_days: p.payment_days,
payment_months: resolveCollectionPaymentMonthsValue(p),
acceptance_payment: String(p.acceptance_payment || '').trim(),
rebate_delay_base_months:
p.rebate_delay_base_months
+24 -1
View File
@@ -439,6 +439,27 @@ export function formatSupplierOutputVatRateLabel(row) {
return '—'
}
/** listed 行解析专项送货计提率 */
export function pickSupplierSpecialDeliveryCommissionRate(row) {
if (!row || typeof row !== 'object') return null
const r = /** @type {Record<string, unknown>} */ (row)
const field = r.field && typeof r.field === 'object' ? r.field : null
const fields = r.fields && typeof r.fields === 'object' ? r.fields : null
const raw = r.special_delivery_commission_rate
?? field?.special_delivery_commission_rate
?? fields?.special_delivery_commission_rate
if (raw === null || raw === undefined || raw === '') return null
const n = Number(raw)
return Number.isFinite(n) ? n : null
}
/** 详情展示:专项送货计提率 */
export function formatSupplierSpecialDeliveryCommissionRate(row) {
const n = pickSupplierSpecialDeliveryCommissionRate(row)
if (n == null) return '—'
return `${n}%`
}
/** listed 行解析 sale_type(1 渠道 / 2 零售) */
export function pickSupplierSaleType(row) {
if (!row || typeof row !== 'object') return ORG_SALE_TYPE_CHANNEL
@@ -458,6 +479,7 @@ export function buildSupplierFormStateFromRow(row) {
output_vat_rate_key: '',
is_support_receipt: false,
sale_type: ORG_SALE_TYPE_CHANNEL,
special_delivery_commission_rate: null,
credit_code: '',
legal_person: '',
contact_person: '',
@@ -469,7 +491,7 @@ export function buildSupplierFormStateFromRow(row) {
if (!row || typeof row !== 'object') return { ...base }
const next = { ...base }
for (const key of Object.keys(base)) {
if (key === 'output_vat_rate_key' || key === 'is_support_receipt' || key === 'org_type' || key === 'sale_type') continue
if (key === 'output_vat_rate_key' || key === 'is_support_receipt' || key === 'org_type' || key === 'sale_type' || key === 'special_delivery_commission_rate') continue
if (row[key] !== undefined && row[key] !== null) next[key] = row[key]
}
const tax = hydrateSupplierOrgTaxFields(row)
@@ -479,6 +501,7 @@ export function buildSupplierFormStateFromRow(row) {
is_support_receipt: tax.is_support_receipt,
output_vat_rate_key: tax.output_vat_rate_key,
sale_type: pickSupplierSaleType(row),
special_delivery_commission_rate: pickSupplierSpecialDeliveryCommissionRate(row),
}
}
@@ -106,7 +106,22 @@
<div class="wops-col wops-col--wh">
<span class="wops-name wops-ellipsis">{{ row.source_rdc_name || row.source_rdc || '—' }}</span>
</div>
<div class="wops-col wops-col--num">{{ fmtMoney(row.fence_value) }}</div>
<div class="wops-col wops-col--num gt-cost-col--fence">
<span
v-if="hasFenceNumericValue(row.fence_value)"
class="gt-cost-fence-value"
>
<el-icon
class="gt-cost-fence-help-icon"
aria-label="围栏值说明"
@click.stop="onFenceValueHelpClick(row)"
>
<QuestionFilled />
</el-icon>
<span class="gt-cost-fence-num">{{ fmtMoney(row.fence_value) }}</span>
</span>
<span v-else class="gt-cost-fence-num">{{ fmtMoney(row.fence_value) }}</span>
</div>
</div>
</div>
</div>
@@ -115,17 +130,49 @@
</div>
</div>
</template>
<Teleport to="body">
<el-dialog
v-if="calculateDebugVisible"
v-model="calculateDebugVisible"
:title="calculateDebugTitle"
width="1200px"
class="gt-cost-calc-debug-dlg"
modal-class="gt-cost-calc-debug-overlay"
align-center
append-to-body
lock-scroll
:close-on-click-modal="false"
destroy-on-close
>
<div
class="gt-cost-calc-debug-body"
v-loading="calculateLoading"
element-loading-text="算价中…"
>
<pre class="gt-cost-calc-debug-pre">{{ calculateDebugText }}</pre>
</div>
<template #footer>
<el-button type="primary" @click="calculateDebugVisible = false">关闭</el-button>
</template>
</el-dialog>
</Teleport>
</div>
</template>
<script setup>
import { computed, ref, watch } from 'vue'
import { computed, onBeforeUnmount, ref, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { QuestionFilled } from '@element-plus/icons-vue'
import { mapGoodsRdcsToOptions } from '@/utils/warehouseGlobalTabSchema'
import {
PRICING_TAXPAYER_DEFAULT,
PRICING_TAXPAYER_TABS,
PRICING_TRADE_DOMESTIC,
formatPricingCalculateDebug,
pickPricingCalculateError,
pickPricingTableError,
pricingCalculate,
pricingTableQuery,
} from '@/api/pricingTable'
@@ -194,6 +241,11 @@ const fenceRows = ref([])
/** 递增序号,避免切换 RDC / 纳税身份时旧请求覆盖新结果 */
let fenceFetchSeq = 0
const calculateDebugVisible = ref(false)
const calculateDebugTitle = ref('算价明细')
const calculateDebugText = ref('')
const calculateLoading = ref(false)
const rdcOptions = computed(() => mapGoodsRdcsToOptions(props.rdcList))
function rdcDisplayName(rdc) {
@@ -235,6 +287,68 @@ function fmtTaxRate(v) {
return `${n}%`
}
function hasFenceNumericValue(v) {
if (v == null || v === '') return false
return Number.isFinite(Number(v))
}
function fenceRowCalculateLabel(row) {
const supplier = String(row?.supplier_company_name || '').trim()
const warehouse = String(row?.source_rdc_name || row?.source_rdc || '').trim()
const parts = [supplier, warehouse].filter(Boolean)
return parts.length ? parts.join(' · ') : '围栏值'
}
function buildPricingCalculateBody(row) {
return {
taxpayer_type: activeTaxpayerType.value,
trade_type: PRICING_TRADE_DOMESTIC,
goods_code: String(props.goodsCode || '').trim(),
supplier_company_code: String(row?.supplier_company_code || '').trim(),
warehouse_code: String(row?.source_rdc_code || '').trim(),
}
}
function openCalculateDebug(title, debugText) {
calculateDebugTitle.value = title
calculateDebugText.value = String(debugText ?? '').trim()
calculateDebugVisible.value = true
}
async function onFenceValueHelpClick(row) {
const body = buildPricingCalculateBody(row)
if (!body.goods_code) {
ElMessage.warning('缺少商品编码,无法算价')
return
}
if (!body.supplier_company_code) {
ElMessage.warning('缺少供应商组织编码,无法算价')
return
}
if (!body.warehouse_code) {
ElMessage.warning('缺少源采购 RDC 仓库编码,无法算价')
return
}
if (calculateLoading.value) return
calculateLoading.value = true
openCalculateDebug(`算价明细 · ${fenceRowCalculateLabel(row)}`, '算价中…')
try {
const res = await pricingCalculate(body)
openCalculateDebug(
`算价明细 · ${fenceRowCalculateLabel(row)}`,
formatPricingCalculateDebug(res),
)
} catch (e) {
openCalculateDebug(
`算价明细 · ${fenceRowCalculateLabel(row)}`,
`【失败】${pickPricingCalculateError(e)}`,
)
} finally {
calculateLoading.value = false
}
}
async function fetchFenceTable() {
const goods_code = String(props.goodsCode || '').trim()
const rdc_code = String(activeRdcCode.value || '').trim()
@@ -314,6 +428,11 @@ watch(activeRdcCode, () => {
watch(activeTaxpayerType, () => {
void fetchFenceTable()
})
onBeforeUnmount(() => {
calculateDebugVisible.value = false
calculateLoading.value = false
})
</script>
<style scoped>
@@ -544,6 +663,33 @@ watch(activeTaxpayerType, () => {
.gt-cost-table-wrap :deep(.is-cols-cost-fence .gt-cost-col--min-price) {
padding-right: 4px;
}
.gt-cost-table-wrap :deep(.gt-cost-col--fence) {
display: flex;
align-items: center;
justify-content: flex-end;
}
.gt-cost-fence-value {
display: inline-flex;
align-items: center;
justify-content: flex-end;
gap: 4px;
min-width: 0;
line-height: 1;
}
.gt-cost-fence-help-icon {
flex-shrink: 0;
font-size: calc(13px + var(--wh-fs-delta, 0px));
color: #909399;
cursor: pointer;
line-height: 1;
}
.gt-cost-fence-help-icon:hover {
color: #409eff;
}
.gt-cost-fence-num {
line-height: 1;
font-variant-numeric: tabular-nums;
}
@media (max-width: 900px) {
.gt-cost-layout {
flex-direction: column;
@@ -557,3 +703,66 @@ watch(activeTaxpayerType, () => {
}
}
</style>
<style>
.gt-cost-calc-debug-overlay .el-overlay-dialog {
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
padding: 24px;
box-sizing: border-box;
}
.gt-cost-calc-debug-dlg.el-dialog {
width: min(1200px, 94vw);
max-width: 94vw;
max-height: calc(100vh - 48px);
margin: 0;
display: flex;
flex-direction: column;
overflow: hidden;
}
.gt-cost-calc-debug-dlg.el-dialog .el-dialog__header {
flex-shrink: 0;
}
.gt-cost-calc-debug-dlg.el-dialog .el-dialog__body {
flex: 1 1 auto;
min-height: 0;
overflow: hidden;
padding: 12px 20px;
display: flex;
flex-direction: column;
}
.gt-cost-calc-debug-dlg.el-dialog .el-dialog__footer {
flex-shrink: 0;
}
.gt-cost-calc-debug-dlg .gt-cost-calc-debug-body {
flex: 1 1 auto;
min-height: 0;
display: flex;
flex-direction: column;
overflow: hidden;
}
.gt-cost-calc-debug-dlg .gt-cost-calc-debug-pre {
flex: 1 1 auto;
min-height: 0;
margin: 0;
overflow: auto;
overscroll-behavior: contain;
padding: 12px 14px;
background: #1e1e1e;
color: #d4d4d4;
border-radius: 6px;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
font-size: 12px;
line-height: 1.55;
white-space: pre-wrap;
word-break: break-word;
}
</style>
+2 -2
View File
@@ -1,4 +1,4 @@
{
"buildId": "20260707009",
"generatedAt": "2026-07-07T14:48:25.301Z"
"buildId": "20260708004",
"generatedAt": "2026-07-08T08:40:29.095Z"
}
+52 -2
View File
@@ -42,11 +42,61 @@
* @typedef {{ date: string, items: ChangelogItem[] }} ChangelogDay
*/
/** 应用版本(如 0.7.032 → 侧栏 v0.7,弹窗 v0.7.032) */
export const APP_VERSION = '0.7.032'
/** 应用版本(如 0.7.035 → 侧栏 v0.7,弹窗 v0.7.035) */
export const APP_VERSION = '0.7.39'
/** @type {ChangelogDay[]} */
export const APP_CHANGELOG = [
{
date: '2026-07-08',
items: [
{
icon: 'mdi:format-list-bulleted',
kind: 'add',
text: '「物料列表 → 成本围栏」围栏值前增加可点击「?」,对接 `POST /api/pricing/calculate` 查看算价明细(传 `supplier_company_code`、`warehouse_code` 及当前纳税身份/商品);弹窗宽 1200px、仅内容区滚动,`Teleport` 挂 body 避免嵌套物料弹窗生命周期警告。',
},
{
icon: 'mdi:notebook-outline',
kind: 'change',
text: '「客户管理 → 客户价格本」移除顶栏「批量计算」按钮及相关算价弹窗逻辑。',
},
{
icon: 'mdi:storefront-outline',
kind: 'add',
text: '「客户管理 → 客户列表」组织新建/编辑对接 `special_delivery_commission_rate`(客户专项送货计提率),仅走 `/api/customer/add`、`/api/customer/modify`;供货目录供应商接口不带该字段。',
},
{
icon: 'mdi:truck-delivery-outline',
kind: 'change',
text: '「供应商列表」「客户列表」组织详情与新建/编辑弹窗:基本信息拆为「工商信息」「其他信息」;销售类型单独置于「其他信息」;客户另增专项送货计提率,输入框与详情展示带 % 单位。',
},
{
icon: 'mdi:storefront-outline',
kind: 'fix',
text: '「客户列表」组织详情「工商信息」卡片(信用代码、法人、组织类型)因 computed 漏写 return 未渲染,已修复。',
},
{
icon: 'mdi:warehouse',
kind: 'change',
text: '「仓库管理 → RDC 物料」可调配仓相关文案统一为「源采购仓」:Tab、批量关联按钮、关联弹窗标题/说明/候选区、确认保存提示及列表空态等用户可见文案同步更名。',
},
{
icon: 'mdi:warehouse',
kind: 'change',
text: '「仓库管理 → 关联物料」弹窗:全部/已关联/未关联 Tab 移入物料顶栏,缩小字号并固定高度,与「物料 · N 条」垂直对齐,避免单行过扁。',
},
{
icon: 'mdi:notebook-outline',
kind: 'fix',
text: '「客户管理 → 客户价格本」通过场景/分类添加或手动新增时,`/api/customer/price/add` / `batch_add` 的 `excess_performance`、`benefit` 无票种分组时改传空数组 `[]`,不再提交 `{ rate, amount }` 对象,修复后端 JSONArray 类型转换报错。',
},
{
icon: 'mdi:folder-cog-outline',
kind: 'change',
text: '「统筹集合管理」新建/编辑第三步「财务成本损益」:接口字段 `payment_days` 更名为 `payment_months`(帐期月数);add/modify 提交与列表回填优先读新字段,兼容旧 `payment_days`。',
},
],
},
{
date: '2026-07-07',
items: [
+3 -3
View File
@@ -278,7 +278,7 @@ export function validateDeployPairings(anchorRows, anchorToDeployMap, deployTree
continue
}
if (!pick.key || !pickToWarehouseCode(pick)) {
return { ok: false, message: '可调配仓勾选数据不完整', unmappedCount: unmappedRows.length + 1 }
return { ok: false, message: '源采购仓勾选数据不完整', unmappedCount: unmappedRows.length + 1 }
}
mappedCount += 1
rowsToSave.push(row)
@@ -290,14 +290,14 @@ export function validateDeployPairings(anchorRows, anchorToDeployMap, deployTree
if (pendingRequired.length) {
return {
ok: false,
message: `还有 ${pendingRequired.length} 条物料有可调配仓尚未勾选,请先完成关联后再确定`,
message: `还有 ${pendingRequired.length} 条物料有源采购仓尚未勾选,请先完成关联后再确定`,
unmappedCount: pendingRequired.length,
mappedCount,
}
}
if (mappedCount === 0) {
return { ok: false, message: '没有可保存的可调配仓关联', unmappedCount: unmappedRows.length }
return { ok: false, message: '没有可保存的源采购仓关联', unmappedCount: unmappedRows.length }
}
return {
+14 -14
View File
@@ -9,7 +9,7 @@
class="wh-rdc-deploy-link-dlg wh-fs-scope"
@closed="onClosed"
>
<div v-loading="loading" class="wh-rdc-deploy-link-body" element-loading-text="加载可调配仓候选…">
<div v-loading="loading" class="wh-rdc-deploy-link-body" element-loading-text="加载源采购仓候选…">
<p v-if="pairHint" class="wh-rdc-deploy-link-hint">{{ pairHint }}</p>
<el-alert
v-if="showDeployEmptyTip"
@@ -17,7 +17,7 @@
type="warning"
show-icon
:closable="false"
title="未查到可关联的可调配仓"
title="未查到可关联的源采购仓"
description="请确认其它仓库 RDC 中已维护与左侧相同编码的物料;若仍无结果,请联系管理员检查配置。"
/>
<div class="wh-rdc-deploy-link-split">
@@ -62,7 +62,7 @@
</section>
<section class="wh-rdc-deploy-link-pane">
<header class="wh-rdc-deploy-link-pane-hd">可调配仓候选</header>
<header class="wh-rdc-deploy-link-pane-hd">源采购仓候选</header>
<div class="wh-rdc-deploy-link-tree-wrap">
<el-tree
v-if="deployTreeData.length"
@@ -94,7 +94,7 @@
</div>
</template>
</el-tree>
<div v-else-if="!loading" class="wh-rdc-deploy-link-empty">未查到可调配仓候选</div>
<div v-else-if="!loading" class="wh-rdc-deploy-link-empty">未查到源采购仓候选</div>
</div>
</section>
</div>
@@ -173,15 +173,15 @@ const dialogTitle = computed(() => {
if (n <= 1) {
const row = anchorRows.value[0]
const name = String(row?.goods_name || row?.goods_code || '').trim()
return name ? `关联可调配仓 · ${name}` : '关联可调配仓'
return name ? `关联源采购仓 · ${name}` : '关联源采购仓'
}
return `批量关联可调配仓 · ${n} 条`
return `批量关联源采购仓 · ${n} 条`
})
const pairHint = computed(() => {
const n = anchorRows.value.length
const mapped = Object.keys(anchorToDeployMap.value).length
return `在右侧勾选可调配仓,左侧将按编码自动显示配对结果(${mapped}/${n})。同一条物料可改选其它仓下的同名项。`
return `在右侧勾选源采购仓,左侧将按编码自动显示配对结果(${mapped}/${n})。同一条物料可改选其它仓下的同名项。`
})
const showDeployEmptyTip = computed(() => (
@@ -278,7 +278,7 @@ function onDeployTreeCheck(data, payload) {
if (ambiguous) {
ElMessage.warning('同编码物料有多条,无法自动配对')
} else {
ElMessage.warning('该可调配仓物料与左侧待关联物料编码不一致')
ElMessage.warning('该源采购仓物料与左侧待关联物料编码不一致')
}
syncDeployTreeChecks()
return
@@ -329,7 +329,7 @@ async function loadDialogData() {
const { tree } = await fetchCandidatesTree(rows)
await applyCandidatesTree(tree)
} catch (e) {
ElMessage.error('加载可调配仓候选失败:' + (e?.data?.message || e?.message || '未知错误'))
ElMessage.error('加载源采购仓候选失败:' + (e?.data?.message || e?.message || '未知错误'))
} finally {
loading.value = false
}
@@ -342,8 +342,8 @@ function normalizeAnchorRows(rows) {
async function persistDeployMappings(rowsToSave, map, { confirmSkipped = 0 } = {}) {
const skippedCount = Number(confirmSkipped) || 0
const confirmText = skippedCount > 0
? `确定为 ${rowsToSave.length} 条物料保存可调配仓关联?另有 ${skippedCount} 条暂无可调配仓,将跳过。`
: `确定为 ${rowsToSave.length} 条物料保存可调配仓关联?`
? `确定为 ${rowsToSave.length} 条物料保存源采购仓关联?另有 ${skippedCount} 条暂无源采购仓,将跳过。`
: `确定为 ${rowsToSave.length} 条物料保存源采购仓关联?`
try {
await ElMessageBox.confirm(
@@ -367,7 +367,7 @@ async function persistDeployMappings(rowsToSave, map, { confirmSkipped = 0 } = {
ElMessage.warning('缺少物料或目标仓编码,无法保存')
return false
}
ElMessage.success(`已关联 ${rowsToSave.length} 条物料的可调配仓`)
ElMessage.success(`已关联 ${rowsToSave.length} 条物料的源采购仓`)
emit('saved')
visible.value = false
return true
@@ -410,7 +410,7 @@ async function prepareLinkDeploy(rawAnchors) {
try {
const { tree } = await fetchCandidatesTree(rows)
if (!tree.length) {
ElMessage.warning('未找到可调配的仓库,请确认相关 RDC 仓中已维护该物料')
ElMessage.warning('未找到源采购仓,请确认相关 RDC 仓中已维护该物料')
return { handled: true }
}
@@ -423,7 +423,7 @@ async function prepareLinkDeploy(rawAnchors) {
}
return { handled: false }
} catch (e) {
ElMessage.error('加载可调配仓候选失败:' + (e?.data?.message || e?.message || '未知错误'))
ElMessage.error('加载源采购仓候选失败:' + (e?.data?.message || e?.message || '未知错误'))
return { handled: true }
}
}
+35 -11
View File
@@ -73,11 +73,16 @@
<section class="wh-slg-col wh-slg-col--goods">
<div class="wh-slg-col-hd wh-slg-goods-hd">
<span>物料</span>
<span class="wh-slg-goods-hd-title">物料</span>
<span class="wh-slg-col-meta">
{{ goodsTotal }} 条
<template v-if="checkedSelectCount"> · 已选 {{ checkedSelectCount }}</template>
</span>
<el-radio-group v-model="goodsTab" size="small" class="wh-slg-goods-tabs">
<el-radio-button :value="GOODS_TAB_ALL">全部</el-radio-button>
<el-radio-button :value="GOODS_TAB_LINKED">已关联</el-radio-button>
<el-radio-button :value="GOODS_TAB_UNLINKED">未关联</el-radio-button>
</el-radio-group>
<span class="wh-slg-col-hd-spacer" />
<el-tooltip
:disabled="advancedSearchBtnEnabled"
@@ -105,11 +110,6 @@
清除搜索
</el-button>
</div>
<el-radio-group v-model="goodsTab" size="small" class="wh-slg-goods-tabs">
<el-radio-button :value="GOODS_TAB_ALL">全部</el-radio-button>
<el-radio-button :value="GOODS_TAB_LINKED">已关联</el-radio-button>
<el-radio-button :value="GOODS_TAB_UNLINKED">未关联</el-radio-button>
</el-radio-group>
<el-input
v-model="goodsKeyword"
clearable
@@ -1092,13 +1092,40 @@ defineExpose({ clearGoodsSelection })
line-height: 1.55;
padding: 28px 16px;
}
.wh-slg-goods-hd {
flex-wrap: wrap;
align-items: center;
}
.wh-slg-goods-hd-title,
.wh-slg-goods-hd .wh-slg-col-meta {
display: inline-flex;
align-items: center;
height: 26px;
line-height: 1;
}
.wh-slg-goods-hd-title {
font-size: 13px;
font-weight: 600;
}
.wh-slg-goods-tabs {
flex-shrink: 0;
margin: 0 8px;
margin-left: 6px;
display: inline-flex;
align-items: center;
}
.wh-slg-goods-tabs :deep(.el-radio-button__inner) {
font-size: 14px;
font-size: 12px;
font-weight: 500;
height: 26px;
line-height: 24px;
padding: 0 10px;
border-radius: 4px;
}
.wh-slg-goods-tabs :deep(.el-radio-button:first-child .el-radio-button__inner) {
border-radius: 4px 0 0 4px;
}
.wh-slg-goods-tabs :deep(.el-radio-button:last-child .el-radio-button__inner) {
border-radius: 0 4px 4px 0;
}
.wh-slg-search {
display: block;
@@ -1121,9 +1148,6 @@ defineExpose({ clearGoodsSelection })
height: 34px;
line-height: 34px;
}
.wh-slg-goods-hd {
flex-wrap: wrap;
}
.wh-slg-goods-body {
flex: 1;
min-height: 0;
+8 -8
View File
@@ -2,7 +2,7 @@
<div class="wh-srf-rdc-deploy">
<div v-if="!goodsRow" class="wh-srf-rdc-deploy-empty">
<p>请在左侧点击物料</p>
<p class="wh-srf-rdc-deploy-empty-sub">选中后可维护该 RDC 的可调配仓</p>
<p class="wh-srf-rdc-deploy-empty-sub">选中后可维护该 RDC 的源采购仓</p>
</div>
<template v-else>
@@ -41,7 +41,7 @@
<div
v-loading="loading || mutating"
class="wh-srf-rdc-deploy-body"
element-loading-text="加载可调配仓…"
element-loading-text="加载源采购仓…"
>
<el-table
v-if="filteredRows.length"
@@ -53,7 +53,7 @@
@selection-change="onSelectionChange"
>
<el-table-column v-if="canUpdate" type="selection" width="40" />
<el-table-column label="调配仓库" min-width="160" show-overflow-tooltip>
<el-table-column label="源采购仓" min-width="160" show-overflow-tooltip>
<template #default="{ row }">
{{ deployRowLabel(row) }}
</template>
@@ -74,7 +74,7 @@
</el-table>
<div v-else-if="!loading" class="wh-srf-rdc-deploy-empty wh-srf-rdc-deploy-empty--inner">
<p>暂无可调配仓</p>
<p>暂无源采购仓</p>
<p v-if="canUpdate" class="wh-srf-rdc-deploy-empty-sub">点击「添加」关联其他仓的 RDC</p>
</div>
</div>
@@ -148,7 +148,7 @@ function onSelectionChange(rows) {
async function onAddDeploy() {
if (!props.goodsRow) return
if (!sourceRdcCode.value) {
ElMessage.warning('缺少 RDC 编码,无法查询可调配仓候选')
ElMessage.warning('缺少 RDC 编码,无法查询源采购仓候选')
return
}
const dlg = deployLinkDlgRef.value
@@ -183,7 +183,7 @@ async function loadDeployList() {
} catch (e) {
if (seq !== loadSeq) return
deployRows.value = []
ElMessage.error('加载可调配仓失败:' + (e?.data?.message || e?.message || '未知错误'))
ElMessage.error('加载源采购仓失败:' + (e?.data?.message || e?.message || '未知错误'))
} finally {
if (seq === loadSeq) loading.value = false
}
@@ -223,7 +223,7 @@ async function unlinkRows(rows) {
async function unlinkOne(row) {
const label = deployRowLabel(row)
try {
await ElMessageBox.confirm(`确定移除「${label}」?`, '移除可调配仓', {
await ElMessageBox.confirm(`确定移除「${label}」?`, '移除源采购仓', {
type: 'warning',
confirmButtonText: '确定',
cancelButtonText: '取消',
@@ -238,7 +238,7 @@ async function unlinkSelected() {
const rows = selectedRows.value
if (!rows.length) return
try {
await ElMessageBox.confirm(`确定移除已选的 ${rows.length} 个可调配仓?`, '批量移除', {
await ElMessageBox.confirm(`确定移除已选的 ${rows.length} 个源采购仓?`, '批量移除', {
type: 'warning',
confirmButtonText: '确定',
cancelButtonText: '取消',
+2 -2
View File
@@ -36,7 +36,7 @@
:disabled="!effectiveSelectedRows.length || unlinking"
@click="openBatchDeployDlg"
>
批量关联可调配仓
批量关联源采购仓
</el-button>
<el-button
v-if="canUpdate && goodsRows.length"
@@ -692,7 +692,7 @@ async function linkGoods(items) {
async function openBatchDeployDlg() {
const rows = effectiveSelectedRows.value.filter(Boolean)
if (!rows.length) {
ElMessage.warning('请先勾选要关联可调配仓的 RDC 物料')
ElMessage.warning('请先勾选要关联源采购仓的 RDC 物料')
return
}
const prep = await batchDeployDlgRef.value?.prepareLinkDeploy?.(rows)
+1 -1
View File
@@ -3,7 +3,7 @@
<header class="wh-srf-rdc-right-hd">
<el-radio-group v-model="activeTab" size="small" class="wh-srf-rdc-right-tabs">
<el-radio-button value="region">可售卖区域</el-radio-button>
<el-radio-button value="deploy">源采购 RDC</el-radio-button>
<el-radio-button value="deploy">源采购仓</el-radio-button>
</el-radio-group>
</header>
<div class="wh-srf-rdc-right-body">
+2 -125
View File
@@ -45,16 +45,6 @@
>
批量修改
</el-button>
<el-button
class="dpb-batch-calc-btn"
type="success"
size="small"
:disabled="!selectedEditableRows.length || batchCalculating"
:loading="batchCalculating"
@click="runBatchCalculate"
>
批量计算
</el-button>
<el-button
type="warning"
size="small"
@@ -444,20 +434,6 @@
:submitting="addingPriceBook"
@confirm="onCategoryGoodsPicked"
/>
<el-dialog
v-model="calculateDebugVisible"
:title="calculateDebugTitle"
width="760px"
class="dpb-calc-debug-dlg"
:close-on-click-modal="false"
destroy-on-close
>
<pre class="dpb-calc-debug-pre">{{ calculateDebugText }}</pre>
<template #footer>
<el-button type="primary" @click="calculateDebugVisible = false">关闭</el-button>
</template>
</el-dialog>
</div>
</template>
@@ -475,6 +451,7 @@ import {
customerPriceAdd,
customerPriceListed,
customerPriceRemove,
buildCustomerPriceAddBody,
buildCustomerPriceWriteBody,
createEmptyCustomerPriceForm,
customerPriceFormFromRow,
@@ -497,13 +474,6 @@ import {
normalizeFinanceBillType,
} from '@/utils/financeBillTypes'
import { pickDisplayCode, pickDisplayName } from '@/utils/warehouseOpsDisplay'
import {
PRICING_TAXPAYER_DEFAULT,
PRICING_TRADE_DOMESTIC,
formatPricingCalculateDebug,
pickPricingCalculateError,
pricingCalculate,
} from '@/api/pricingTable'
defineOptions({ name: 'DealerPriceBook' })
@@ -558,11 +528,6 @@ const batchEditRows = ref([])
const formRef = ref()
const selected = ref(new Set())
const calculateDebugVisible = ref(false)
const calculateDebugTitle = ref('算价 debug')
const calculateDebugText = ref('')
const batchCalculating = ref(false)
const formDialogTitle = computed(() => {
if (formMode.value === 'batch') return `批量修改价格本(${batchEditRows.value.length} 条)`
if (formMode.value === 'edit') return '编辑价格本'
@@ -741,68 +706,6 @@ function fmtValidStatus(v) {
return '—'
}
function buildPricingCalculateBody(row) {
const goods_code = String(row?.goods_code ?? '').trim()
const customer_price_code = pickCustomerPriceCode(row)
return {
taxpayer_type: PRICING_TAXPAYER_DEFAULT,
trade_type: PRICING_TRADE_DOMESTIC,
goods_code,
customer_price_code,
}
}
function pricingCalculateRowLabel(row) {
const name = pickGoodsDisplayName(row)
const code = String(row?.goods_code ?? '').trim()
if (name && code && name !== code) return `${name}(${code})`
return name || code || '—'
}
function openCalculateDebug(title, debugText) {
calculateDebugTitle.value = title
calculateDebugText.value = String(debugText ?? '').trim()
calculateDebugVisible.value = true
}
async function runBatchCalculate() {
const targets = selectedEditableRows.value.filter((row) => {
const goods_code = String(row?.goods_code ?? '').trim()
const customer_price_code = pickCustomerPriceCode(row)
return goods_code && customer_price_code
})
if (!targets.length) {
ElMessage.warning('请先勾选可计算的物料')
return
}
if (batchCalculating.value) return
batchCalculating.value = true
const parts = []
let failCount = 0
for (let i = 0; i < targets.length; i += 1) {
const row = targets[i]
const label = pricingCalculateRowLabel(row)
parts.push(`========== ${i + 1}/${targets.length} · ${label} ==========`)
try {
const res = await pricingCalculate(buildPricingCalculateBody(row))
const debug = formatPricingCalculateDebug(res)
parts.push(debug || '(无 debug 内容)')
} catch (e) {
failCount += 1
parts.push(`【失败】${pickPricingCalculateError(e)}`)
}
if (i < targets.length - 1) parts.push('')
}
batchCalculating.value = false
openCalculateDebug(
`批量算价 debug · ${targets.length} 条${failCount ? `(${failCount} 条失败)` : ''}`,
parts.join('\n'),
)
}
function fmtNum(v) {
if (v == null || v === '') return '—'
const n = Number(v)
@@ -847,7 +750,7 @@ async function loadExpenseBillNames() {
}
function buildFormPayload() {
return buildCustomerPriceWriteBody(form, {
return buildCustomerPriceAddBody(form, {
company_code: String(activeCompanyCode.value || '').trim(),
sales_policy_type: 0,
})
@@ -1255,17 +1158,6 @@ onMounted(() => {
gap: 8px;
min-height: 24px;
}
.dpb-batch-calc-btn.el-button--success:not(.is-disabled) {
--el-button-bg-color: #16a34a;
--el-button-border-color: #15803d;
--el-button-hover-bg-color: #22c55e;
--el-button-hover-border-color: #16a34a;
--el-button-active-bg-color: #15803d;
--el-button-active-border-color: #166534;
font-weight: 700;
letter-spacing: 0.02em;
box-shadow: 0 1px 4px rgba(22, 163, 74, 0.35);
}
.dpb-selected-meta {
font-size: 13px;
color: #409eff;
@@ -1632,21 +1524,6 @@ onMounted(() => {
.dpb-op-link--muted { color: #909399; }
.dpb-op-link--muted:hover { color: #606266; }
.dpb-calc-debug-pre {
margin: 0;
max-height: min(68vh, 640px);
overflow: auto;
padding: 12px 14px;
background: #1e1e1e;
color: #d4d4d4;
border-radius: 6px;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
font-size: 12px;
line-height: 1.55;
white-space: pre-wrap;
word-break: break-word;
}
.dpb-footer {
flex-shrink: 0;
padding: 10px 16px 14px;
+40 -1
View File
@@ -179,6 +179,27 @@
</div>
</div>
</section>
<section class="supply-supplier-info-group">
<div class="supply-supplier-info-group-title">其他信息</div>
<div class="supply-supplier-info-cards supply-supplier-info-cards--align">
<div
v-for="card in otherInfoCards"
:key="card.key"
class="supply-info-card"
:style="infoCardGridStyle(card)"
>
<div class="supply-info-card-icon" :class="`is-${card.tone}`">
<Icon :icon="card.icon" />
</div>
<div class="supply-info-card-body">
<div class="supply-info-card-label">{{ card.label }}</div>
<div class="supply-info-card-value">{{ card.value }}</div>
<div v-if="card.sub" class="supply-info-card-sub">{{ card.sub }}</div>
</div>
</div>
</div>
</section>
</div>
</div>
</section>
@@ -724,6 +745,7 @@ import {
pickSupplierSaleType,
pickPartnerOrgDisplayName,
pickSupplierDisplayName,
formatSupplierSpecialDeliveryCommissionRate,
supplySupplierBlocRemove,
supplySupplierBlocSceneListed,
supplySupplierBlocSceneSet,
@@ -1035,7 +1057,7 @@ const mainInfoCards = computed(() => {
const businessInfoCards = computed(() => {
const row = activeRow.value
return [
const cards = [
{
key: 'credit_code',
icon: 'mdi:card-account-details-outline',
@@ -1057,6 +1079,13 @@ const businessInfoCards = computed(() => {
value: displayText(pickSupplierOrgType(row)),
tone: 'indigo',
},
]
return cards
})
const otherInfoCards = computed(() => {
const row = activeRow.value
const cards = [
{
key: 'sales_type',
icon: 'mdi:tag-outline',
@@ -1065,6 +1094,16 @@ const businessInfoCards = computed(() => {
tone: 'violet',
},
]
if (isCustomer.value) {
cards.push({
key: 'special_delivery_commission_rate',
icon: 'mdi:percent-outline',
label: '客户专项送货计提率',
value: formatSupplierSpecialDeliveryCommissionRate(row),
tone: 'amber',
})
}
return cards
})
function infoCardGridStyle(card) {
@@ -2604,7 +2604,7 @@ function buildStep3Payload() {
const baseDelayed = parseWizardInt(form.baseDelayedRebateMonths, { allowZero: true })
const targetDelayed = parseWizardInt(form.targetDelayedRebateMonths, { allowZero: true })
return {
payment_days: parseWizardInt(form.paymentMonths),
payment_months: parseWizardInt(form.paymentMonths),
acceptance_payment: form.acceptancePayment,
rebate_delay_base_months: baseDelayed,
rebate_delay_target_months: targetDelayed,
+58 -14
View File
@@ -41,23 +41,10 @@
/>
</el-select>
</el-form-item>
<el-form-item label="销售类型">
<el-select
v-model="form.sale_type"
placeholder="请选择"
style="width: 100%"
>
<el-option
v-for="opt in salesTypeOptions"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
</el-select>
</el-form-item>
<el-form-item v-if="showReceiptSwitch" label="是否支持收据">
<el-switch v-model="form.is_support_receipt" />
</el-form-item>
<div class="supply-supplier-form-section-title">工商信息</div>
<div class="supply-supplier-form-pair-grid">
<el-form-item label="信用代码">
<el-input v-model="form.credit_code" clearable />
@@ -75,6 +62,37 @@
<el-form-item label="联系地址">
<el-input v-model="form.contact_address" clearable />
</el-form-item>
<div class="supply-supplier-form-section-title">其他信息</div>
<div class="supply-supplier-form-pair-grid">
<el-form-item label="销售类型">
<el-select
v-model="form.sale_type"
placeholder="请选择"
style="width: 100%"
>
<el-option
v-for="opt in salesTypeOptions"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
</el-select>
</el-form-item>
<el-form-item v-if="isCustomer" label="客户专项送货计提率">
<div class="supply-supplier-rate-field">
<el-input-number
v-model="form.special_delivery_commission_rate"
:min="0"
:max="100"
:precision="2"
:controls="false"
class="supply-supplier-rate-input"
placeholder="如:1.5"
/>
<span class="supply-supplier-rate-unit">%</span>
</div>
</el-form-item>
</div>
<el-form-item label="启用">
<el-switch v-model="form.is_valid" />
</el-form-item>
@@ -312,6 +330,32 @@ async function submit() {
.supply-supplier-form--org .supply-supplier-form-pair-grid .el-form-item {
margin-bottom: 18px;
}
.supply-supplier-form-section-title {
margin: 4px 0 2px;
padding-top: 2px;
font-size: calc(13px + var(--wh-fs-delta, 0px));
font-weight: 600;
color: #606266;
line-height: 1.4;
}
.supply-supplier-rate-field {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
}
.supply-supplier-rate-field .supply-supplier-rate-input {
flex: 1;
min-width: 0;
}
.supply-supplier-rate-field .supply-supplier-rate-input .el-input {
width: 100%;
}
.supply-supplier-rate-unit {
flex-shrink: 0;
color: #909399;
font-size: calc(14px + var(--wh-fs-delta, 0px));
}
.el-dialog.supply-supplier-form-dlg .el-dialog__body,
.el-dialog.supply-supplier-form-dlg .el-form-item__label,
.el-dialog.supply-supplier-form-dlg .el-input__inner,