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, pickSupplierBlocCode,
pickSupplierCompanyCode, pickSupplierCompanyCode,
pickSupplierDisplayName, pickSupplierDisplayName,
pickSupplierSpecialDeliveryCommissionRate,
formatSupplierSpecialDeliveryCommissionRate,
resolveOrgEffectiveSceneCodes, resolveOrgEffectiveSceneCodes,
} from '@/api/supplySupplier' } 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 */ /** @param {unknown} res */
function unwrapResponseData(res) { function unwrapResponseData(res) {
if (res == null) return res if (res == null) return res
@@ -99,15 +114,22 @@ export function buildCustomerAddPayload(form, { blocCode }) {
return { return {
...buildSupplierAddPayload(form, { blocCode }), ...buildSupplierAddPayload(form, { blocCode }),
company_type: 3, company_type: 3,
...buildCustomerSpecialDeliveryFields(form),
} }
} }
export function buildCustomerModifyPayload(form, companyCode, opts = {}) { export function buildCustomerModifyPayload(form, companyCode, opts = {}) {
return buildSupplierModifyPayload(form, companyCode, opts) return {
...buildSupplierModifyPayload(form, companyCode, opts),
...buildCustomerSpecialDeliveryFields(form),
}
} }
export function buildCustomerModifyPayloadFromRow(row, opts = {}) { export function buildCustomerModifyPayloadFromRow(row, opts = {}) {
return buildSupplierModifyPayloadFromRow(row, opts) return {
...buildSupplierModifyPayloadFromRow(row, opts),
...buildCustomerSpecialDeliveryFieldsFromRow(row),
}
} }
export async function fetchCustomerGroupTreeListed() { export async function fetchCustomerGroupTreeListed() {
@@ -132,5 +154,7 @@ export {
pickSupplierCompanyCode as pickCustomerCompanyCode, pickSupplierCompanyCode as pickCustomerCompanyCode,
pickSupplierDisplayName as pickCustomerDisplayName, pickSupplierDisplayName as pickCustomerDisplayName,
resolveOrgEffectiveSceneCodes, resolveOrgEffectiveSceneCodes,
pickSupplierSpecialDeliveryCommissionRate,
formatSupplierSpecialDeliveryCommissionRate,
buildSupplierFormFields as buildCustomerFormFields, buildSupplierFormFields as buildCustomerFormFields,
} }
+25 -1
View File
@@ -524,6 +524,30 @@ export function serializeBenefitBuckets(buckets) {
return out 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 = {}) { export function buildCustomerPriceWriteBody(form, extras = {}) {
const excess_performance = serializeExcessPerformanceBuckets(form?.excess_performance_buckets) 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 cc = String(company_code ?? '').trim()
const gc = String(goods_code ?? '').trim() const gc = String(goods_code ?? '').trim()
const o = overrides && typeof overrides === 'object' ? overrides : {} const o = overrides && typeof overrides === 'object' ? overrides : {}
return buildCustomerPriceWriteBody( return buildCustomerPriceAddBody(
{ {
excess_performance_buckets: groupsToPerformanceBuckets([]), excess_performance_buckets: groupsToPerformanceBuckets([]),
benefit_buckets: groupsToBenefitBuckets([]), 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_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 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_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 fence_value = pickMoney(raw, 'fence_price', 'fence_value')
const bloc = pickText(raw, 'supplier_bloc_code') 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 { 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_bloc_name,
supplier_company_name, supplier_company_name,
supplier_company_code,
goods_name, goods_name,
bill_type, bill_type,
input_tax_rate, input_tax_rate,
@@ -107,6 +107,7 @@ export function normalizePricingTableRow(raw, idx = 0) {
internal_input_tax_rate, internal_input_tax_rate,
source_rdc: source_rdc_name || source_rdc_code, source_rdc: source_rdc_name || source_rdc_code,
source_rdc_name, source_rdc_name,
source_rdc_code,
fence_value, fence_value,
} }
} }
@@ -150,16 +151,16 @@ function unwrapPricingCalculatePayload(res) {
return envelope return envelope
} }
/** 算价响应 → debug 文本(供弹窗展示) */ /** 算价响应 → 明细文本(供弹窗展示) */
export function formatPricingCalculateDebug(res) { export function formatPricingCalculateDebug(res) {
const payload = unwrapPricingCalculatePayload(res) const payload = unwrapPricingCalculatePayload(res)
const debug = payload?.debug ?? res?.data?.debug ?? res?.debug const debug = payload?.debug ?? res?.data?.debug ?? res?.debug
if (debug == null || debug === '') { if (debug == null || debug === '') {
const fallback = payload && typeof payload === 'object' ? payload : (res?.data ?? res) const fallback = payload && typeof payload === 'object' ? payload : (res?.data ?? res)
try { try {
return `(接口未返回 debug)\n\n${JSON.stringify(fallback, null, 2)}` return `(接口未返回计算明细)\n\n${JSON.stringify(fallback, null, 2)}`
} catch { } catch {
return '(接口未返回 debug)' return '(接口未返回计算明细)'
} }
} }
if (typeof debug === 'string') return debug 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 supply_collection_code = str(body.supply_collection_code)
const customer_price_code = str(body.customer_price_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 (supply_collection_code) payload.supply_collection_code = supply_collection_code
if (customer_price_code) payload.customer_price_code = customer_price_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) 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] * @param {object} [row]
*/ */
export function pickCollectionPaymentMonths(row) { export function pickCollectionPaymentMonths(row) {
const r = row && typeof row === 'object' ? row : {} const r = row && typeof row === 'object' ? row : {}
const v = const v =
r.payment_days r.payment_months
?? r.payment_month
?? r.payment_days
?? r.payment_period_days ?? r.payment_period_days
?? r.paymentDays ?? r.paymentDays
?? r.payment_months
?? r.payment_month
if (v === null || v === undefined || v === '') return '' if (v === null || v === undefined || v === '') return ''
return String(v) return String(v)
} }
@@ -711,6 +711,14 @@ export function resolveOrgContactPerson(raw) {
return { code: '', label: nameLabel || contactRaw } 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 请求体(与三步向导快照对齐) * POST /api/supply/collection/add 请求体(与三步向导快照对齐)
* @param {object} parts * @param {object} parts
@@ -751,7 +759,7 @@ export function buildSupplyCollectionAddBody(parts) {
owner_code: String(p.owner_code || '').trim(), owner_code: String(p.owner_code || '').trim(),
scenes, scenes,
warehouses, warehouses,
payment_days: p.payment_days ?? 60, payment_months: resolveCollectionPaymentMonthsValue(p),
is_support_acceptance: is_support_acceptance:
p.is_support_acceptance === true || acceptanceRaw === '支持', p.is_support_acceptance === true || acceptanceRaw === '支持',
is_support_receipt: p.is_support_receipt === true, 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(), supplier_org_type: String(p.supplier_org_type || '').trim(),
partner_org_type: String(p.partner_org_type || '').trim(), partner_org_type: String(p.partner_org_type || '').trim(),
warehouse_codes: Array.isArray(p.warehouse_codes) ? [...p.warehouse_codes] : [], 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(), acceptance_payment: String(p.acceptance_payment || '').trim(),
rebate_delay_base_months: rebate_delay_base_months:
p.rebate_delay_base_months p.rebate_delay_base_months
+24 -1
View File
@@ -439,6 +439,27 @@ export function formatSupplierOutputVatRateLabel(row) {
return '—' 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 零售) */ /** listed 行解析 sale_type(1 渠道 / 2 零售) */
export function pickSupplierSaleType(row) { export function pickSupplierSaleType(row) {
if (!row || typeof row !== 'object') return ORG_SALE_TYPE_CHANNEL if (!row || typeof row !== 'object') return ORG_SALE_TYPE_CHANNEL
@@ -458,6 +479,7 @@ export function buildSupplierFormStateFromRow(row) {
output_vat_rate_key: '', output_vat_rate_key: '',
is_support_receipt: false, is_support_receipt: false,
sale_type: ORG_SALE_TYPE_CHANNEL, sale_type: ORG_SALE_TYPE_CHANNEL,
special_delivery_commission_rate: null,
credit_code: '', credit_code: '',
legal_person: '', legal_person: '',
contact_person: '', contact_person: '',
@@ -469,7 +491,7 @@ export function buildSupplierFormStateFromRow(row) {
if (!row || typeof row !== 'object') return { ...base } if (!row || typeof row !== 'object') return { ...base }
const next = { ...base } const next = { ...base }
for (const key of Object.keys(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] if (row[key] !== undefined && row[key] !== null) next[key] = row[key]
} }
const tax = hydrateSupplierOrgTaxFields(row) const tax = hydrateSupplierOrgTaxFields(row)
@@ -479,6 +501,7 @@ export function buildSupplierFormStateFromRow(row) {
is_support_receipt: tax.is_support_receipt, is_support_receipt: tax.is_support_receipt,
output_vat_rate_key: tax.output_vat_rate_key, output_vat_rate_key: tax.output_vat_rate_key,
sale_type: pickSupplierSaleType(row), sale_type: pickSupplierSaleType(row),
special_delivery_commission_rate: pickSupplierSpecialDeliveryCommissionRate(row),
} }
} }
@@ -106,7 +106,22 @@
<div class="wops-col wops-col--wh"> <div class="wops-col wops-col--wh">
<span class="wops-name wops-ellipsis">{{ row.source_rdc_name || row.source_rdc || '—' }}</span> <span class="wops-name wops-ellipsis">{{ row.source_rdc_name || row.source_rdc || '—' }}</span>
</div> </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> </div>
</div> </div>
@@ -115,17 +130,49 @@
</div> </div>
</div> </div>
</template> </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> </div>
</template> </template>
<script setup> <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 { mapGoodsRdcsToOptions } from '@/utils/warehouseGlobalTabSchema'
import { import {
PRICING_TAXPAYER_DEFAULT, PRICING_TAXPAYER_DEFAULT,
PRICING_TAXPAYER_TABS, PRICING_TAXPAYER_TABS,
PRICING_TRADE_DOMESTIC, PRICING_TRADE_DOMESTIC,
formatPricingCalculateDebug,
pickPricingCalculateError,
pickPricingTableError, pickPricingTableError,
pricingCalculate,
pricingTableQuery, pricingTableQuery,
} from '@/api/pricingTable' } from '@/api/pricingTable'
@@ -194,6 +241,11 @@ const fenceRows = ref([])
/** 递增序号,避免切换 RDC / 纳税身份时旧请求覆盖新结果 */ /** 递增序号,避免切换 RDC / 纳税身份时旧请求覆盖新结果 */
let fenceFetchSeq = 0 let fenceFetchSeq = 0
const calculateDebugVisible = ref(false)
const calculateDebugTitle = ref('算价明细')
const calculateDebugText = ref('')
const calculateLoading = ref(false)
const rdcOptions = computed(() => mapGoodsRdcsToOptions(props.rdcList)) const rdcOptions = computed(() => mapGoodsRdcsToOptions(props.rdcList))
function rdcDisplayName(rdc) { function rdcDisplayName(rdc) {
@@ -235,6 +287,68 @@ function fmtTaxRate(v) {
return `${n}%` 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() { async function fetchFenceTable() {
const goods_code = String(props.goodsCode || '').trim() const goods_code = String(props.goodsCode || '').trim()
const rdc_code = String(activeRdcCode.value || '').trim() const rdc_code = String(activeRdcCode.value || '').trim()
@@ -314,6 +428,11 @@ watch(activeRdcCode, () => {
watch(activeTaxpayerType, () => { watch(activeTaxpayerType, () => {
void fetchFenceTable() void fetchFenceTable()
}) })
onBeforeUnmount(() => {
calculateDebugVisible.value = false
calculateLoading.value = false
})
</script> </script>
<style scoped> <style scoped>
@@ -544,6 +663,33 @@ watch(activeTaxpayerType, () => {
.gt-cost-table-wrap :deep(.is-cols-cost-fence .gt-cost-col--min-price) { .gt-cost-table-wrap :deep(.is-cols-cost-fence .gt-cost-col--min-price) {
padding-right: 4px; 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) { @media (max-width: 900px) {
.gt-cost-layout { .gt-cost-layout {
flex-direction: column; flex-direction: column;
@@ -557,3 +703,66 @@ watch(activeTaxpayerType, () => {
} }
} }
</style> </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", "buildId": "20260708004",
"generatedAt": "2026-07-07T14:48:25.301Z" "generatedAt": "2026-07-08T08:40:29.095Z"
} }
+52 -2
View File
@@ -42,11 +42,61 @@
* @typedef {{ date: string, items: ChangelogItem[] }} ChangelogDay * @typedef {{ date: string, items: ChangelogItem[] }} ChangelogDay
*/ */
/** 应用版本(如 0.7.032 → 侧栏 v0.7,弹窗 v0.7.032) */ /** 应用版本(如 0.7.035 → 侧栏 v0.7,弹窗 v0.7.035) */
export const APP_VERSION = '0.7.032' export const APP_VERSION = '0.7.39'
/** @type {ChangelogDay[]} */ /** @type {ChangelogDay[]} */
export const APP_CHANGELOG = [ 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', date: '2026-07-07',
items: [ items: [
+3 -3
View File
@@ -278,7 +278,7 @@ export function validateDeployPairings(anchorRows, anchorToDeployMap, deployTree
continue continue
} }
if (!pick.key || !pickToWarehouseCode(pick)) { if (!pick.key || !pickToWarehouseCode(pick)) {
return { ok: false, message: '可调配仓勾选数据不完整', unmappedCount: unmappedRows.length + 1 } return { ok: false, message: '源采购仓勾选数据不完整', unmappedCount: unmappedRows.length + 1 }
} }
mappedCount += 1 mappedCount += 1
rowsToSave.push(row) rowsToSave.push(row)
@@ -290,14 +290,14 @@ export function validateDeployPairings(anchorRows, anchorToDeployMap, deployTree
if (pendingRequired.length) { if (pendingRequired.length) {
return { return {
ok: false, ok: false,
message: `还有 ${pendingRequired.length} 条物料有可调配仓尚未勾选,请先完成关联后再确定`, message: `还有 ${pendingRequired.length} 条物料有源采购仓尚未勾选,请先完成关联后再确定`,
unmappedCount: pendingRequired.length, unmappedCount: pendingRequired.length,
mappedCount, mappedCount,
} }
} }
if (mappedCount === 0) { if (mappedCount === 0) {
return { ok: false, message: '没有可保存的可调配仓关联', unmappedCount: unmappedRows.length } return { ok: false, message: '没有可保存的源采购仓关联', unmappedCount: unmappedRows.length }
} }
return { return {
+14 -14
View File
@@ -9,7 +9,7 @@
class="wh-rdc-deploy-link-dlg wh-fs-scope" class="wh-rdc-deploy-link-dlg wh-fs-scope"
@closed="onClosed" @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> <p v-if="pairHint" class="wh-rdc-deploy-link-hint">{{ pairHint }}</p>
<el-alert <el-alert
v-if="showDeployEmptyTip" v-if="showDeployEmptyTip"
@@ -17,7 +17,7 @@
type="warning" type="warning"
show-icon show-icon
:closable="false" :closable="false"
title="未查到可关联的可调配仓" title="未查到可关联的源采购仓"
description="请确认其它仓库 RDC 中已维护与左侧相同编码的物料;若仍无结果,请联系管理员检查配置。" description="请确认其它仓库 RDC 中已维护与左侧相同编码的物料;若仍无结果,请联系管理员检查配置。"
/> />
<div class="wh-rdc-deploy-link-split"> <div class="wh-rdc-deploy-link-split">
@@ -62,7 +62,7 @@
</section> </section>
<section class="wh-rdc-deploy-link-pane"> <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"> <div class="wh-rdc-deploy-link-tree-wrap">
<el-tree <el-tree
v-if="deployTreeData.length" v-if="deployTreeData.length"
@@ -94,7 +94,7 @@
</div> </div>
</template> </template>
</el-tree> </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> </div>
</section> </section>
</div> </div>
@@ -173,15 +173,15 @@ const dialogTitle = computed(() => {
if (n <= 1) { if (n <= 1) {
const row = anchorRows.value[0] const row = anchorRows.value[0]
const name = String(row?.goods_name || row?.goods_code || '').trim() const name = String(row?.goods_name || row?.goods_code || '').trim()
return name ? `关联可调配仓 · ${name}` : '关联可调配仓' return name ? `关联源采购仓 · ${name}` : '关联源采购仓'
} }
return `批量关联可调配仓 · ${n} 条` return `批量关联源采购仓 · ${n} 条`
}) })
const pairHint = computed(() => { const pairHint = computed(() => {
const n = anchorRows.value.length const n = anchorRows.value.length
const mapped = Object.keys(anchorToDeployMap.value).length const mapped = Object.keys(anchorToDeployMap.value).length
return `在右侧勾选可调配仓,左侧将按编码自动显示配对结果(${mapped}/${n})。同一条物料可改选其它仓下的同名项。` return `在右侧勾选源采购仓,左侧将按编码自动显示配对结果(${mapped}/${n})。同一条物料可改选其它仓下的同名项。`
}) })
const showDeployEmptyTip = computed(() => ( const showDeployEmptyTip = computed(() => (
@@ -278,7 +278,7 @@ function onDeployTreeCheck(data, payload) {
if (ambiguous) { if (ambiguous) {
ElMessage.warning('同编码物料有多条,无法自动配对') ElMessage.warning('同编码物料有多条,无法自动配对')
} else { } else {
ElMessage.warning('该可调配仓物料与左侧待关联物料编码不一致') ElMessage.warning('该源采购仓物料与左侧待关联物料编码不一致')
} }
syncDeployTreeChecks() syncDeployTreeChecks()
return return
@@ -329,7 +329,7 @@ async function loadDialogData() {
const { tree } = await fetchCandidatesTree(rows) const { tree } = await fetchCandidatesTree(rows)
await applyCandidatesTree(tree) await applyCandidatesTree(tree)
} catch (e) { } catch (e) {
ElMessage.error('加载可调配仓候选失败:' + (e?.data?.message || e?.message || '未知错误')) ElMessage.error('加载源采购仓候选失败:' + (e?.data?.message || e?.message || '未知错误'))
} finally { } finally {
loading.value = false loading.value = false
} }
@@ -342,8 +342,8 @@ function normalizeAnchorRows(rows) {
async function persistDeployMappings(rowsToSave, map, { confirmSkipped = 0 } = {}) { async function persistDeployMappings(rowsToSave, map, { confirmSkipped = 0 } = {}) {
const skippedCount = Number(confirmSkipped) || 0 const skippedCount = Number(confirmSkipped) || 0
const confirmText = skippedCount > 0 const confirmText = skippedCount > 0
? `确定为 ${rowsToSave.length} 条物料保存可调配仓关联?另有 ${skippedCount} 条暂无可调配仓,将跳过。` ? `确定为 ${rowsToSave.length} 条物料保存源采购仓关联?另有 ${skippedCount} 条暂无源采购仓,将跳过。`
: `确定为 ${rowsToSave.length} 条物料保存可调配仓关联?` : `确定为 ${rowsToSave.length} 条物料保存源采购仓关联?`
try { try {
await ElMessageBox.confirm( await ElMessageBox.confirm(
@@ -367,7 +367,7 @@ async function persistDeployMappings(rowsToSave, map, { confirmSkipped = 0 } = {
ElMessage.warning('缺少物料或目标仓编码,无法保存') ElMessage.warning('缺少物料或目标仓编码,无法保存')
return false return false
} }
ElMessage.success(`已关联 ${rowsToSave.length} 条物料的可调配仓`) ElMessage.success(`已关联 ${rowsToSave.length} 条物料的源采购仓`)
emit('saved') emit('saved')
visible.value = false visible.value = false
return true return true
@@ -410,7 +410,7 @@ async function prepareLinkDeploy(rawAnchors) {
try { try {
const { tree } = await fetchCandidatesTree(rows) const { tree } = await fetchCandidatesTree(rows)
if (!tree.length) { if (!tree.length) {
ElMessage.warning('未找到可调配的仓库,请确认相关 RDC 仓中已维护该物料') ElMessage.warning('未找到源采购仓,请确认相关 RDC 仓中已维护该物料')
return { handled: true } return { handled: true }
} }
@@ -423,7 +423,7 @@ async function prepareLinkDeploy(rawAnchors) {
} }
return { handled: false } return { handled: false }
} catch (e) { } catch (e) {
ElMessage.error('加载可调配仓候选失败:' + (e?.data?.message || e?.message || '未知错误')) ElMessage.error('加载源采购仓候选失败:' + (e?.data?.message || e?.message || '未知错误'))
return { handled: true } return { handled: true }
} }
} }
+35 -11
View File
@@ -73,11 +73,16 @@
<section class="wh-slg-col wh-slg-col--goods"> <section class="wh-slg-col wh-slg-col--goods">
<div class="wh-slg-col-hd wh-slg-goods-hd"> <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"> <span class="wh-slg-col-meta">
{{ goodsTotal }} 条 {{ goodsTotal }} 条
<template v-if="checkedSelectCount"> · 已选 {{ checkedSelectCount }}</template> <template v-if="checkedSelectCount"> · 已选 {{ checkedSelectCount }}</template>
</span> </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" /> <span class="wh-slg-col-hd-spacer" />
<el-tooltip <el-tooltip
:disabled="advancedSearchBtnEnabled" :disabled="advancedSearchBtnEnabled"
@@ -105,11 +110,6 @@
清除搜索 清除搜索
</el-button> </el-button>
</div> </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 <el-input
v-model="goodsKeyword" v-model="goodsKeyword"
clearable clearable
@@ -1092,13 +1092,40 @@ defineExpose({ clearGoodsSelection })
line-height: 1.55; line-height: 1.55;
padding: 28px 16px; 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 { .wh-slg-goods-tabs {
flex-shrink: 0; flex-shrink: 0;
margin: 0 8px; margin-left: 6px;
display: inline-flex;
align-items: center;
} }
.wh-slg-goods-tabs :deep(.el-radio-button__inner) { .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; 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 { .wh-slg-search {
display: block; display: block;
@@ -1121,9 +1148,6 @@ defineExpose({ clearGoodsSelection })
height: 34px; height: 34px;
line-height: 34px; line-height: 34px;
} }
.wh-slg-goods-hd {
flex-wrap: wrap;
}
.wh-slg-goods-body { .wh-slg-goods-body {
flex: 1; flex: 1;
min-height: 0; min-height: 0;
+8 -8
View File
@@ -2,7 +2,7 @@
<div class="wh-srf-rdc-deploy"> <div class="wh-srf-rdc-deploy">
<div v-if="!goodsRow" class="wh-srf-rdc-deploy-empty"> <div v-if="!goodsRow" class="wh-srf-rdc-deploy-empty">
<p>请在左侧点击物料</p> <p>请在左侧点击物料</p>
<p class="wh-srf-rdc-deploy-empty-sub">选中后可维护该 RDC 的可调配仓</p> <p class="wh-srf-rdc-deploy-empty-sub">选中后可维护该 RDC 的源采购仓</p>
</div> </div>
<template v-else> <template v-else>
@@ -41,7 +41,7 @@
<div <div
v-loading="loading || mutating" v-loading="loading || mutating"
class="wh-srf-rdc-deploy-body" class="wh-srf-rdc-deploy-body"
element-loading-text="加载可调配仓…" element-loading-text="加载源采购仓…"
> >
<el-table <el-table
v-if="filteredRows.length" v-if="filteredRows.length"
@@ -53,7 +53,7 @@
@selection-change="onSelectionChange" @selection-change="onSelectionChange"
> >
<el-table-column v-if="canUpdate" type="selection" width="40" /> <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 }"> <template #default="{ row }">
{{ deployRowLabel(row) }} {{ deployRowLabel(row) }}
</template> </template>
@@ -74,7 +74,7 @@
</el-table> </el-table>
<div v-else-if="!loading" class="wh-srf-rdc-deploy-empty wh-srf-rdc-deploy-empty--inner"> <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> <p v-if="canUpdate" class="wh-srf-rdc-deploy-empty-sub">点击「添加」关联其他仓的 RDC</p>
</div> </div>
</div> </div>
@@ -148,7 +148,7 @@ function onSelectionChange(rows) {
async function onAddDeploy() { async function onAddDeploy() {
if (!props.goodsRow) return if (!props.goodsRow) return
if (!sourceRdcCode.value) { if (!sourceRdcCode.value) {
ElMessage.warning('缺少 RDC 编码,无法查询可调配仓候选') ElMessage.warning('缺少 RDC 编码,无法查询源采购仓候选')
return return
} }
const dlg = deployLinkDlgRef.value const dlg = deployLinkDlgRef.value
@@ -183,7 +183,7 @@ async function loadDeployList() {
} catch (e) { } catch (e) {
if (seq !== loadSeq) return if (seq !== loadSeq) return
deployRows.value = [] deployRows.value = []
ElMessage.error('加载可调配仓失败:' + (e?.data?.message || e?.message || '未知错误')) ElMessage.error('加载源采购仓失败:' + (e?.data?.message || e?.message || '未知错误'))
} finally { } finally {
if (seq === loadSeq) loading.value = false if (seq === loadSeq) loading.value = false
} }
@@ -223,7 +223,7 @@ async function unlinkRows(rows) {
async function unlinkOne(row) { async function unlinkOne(row) {
const label = deployRowLabel(row) const label = deployRowLabel(row)
try { try {
await ElMessageBox.confirm(`确定移除「${label}」?`, '移除可调配仓', { await ElMessageBox.confirm(`确定移除「${label}」?`, '移除源采购仓', {
type: 'warning', type: 'warning',
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
@@ -238,7 +238,7 @@ async function unlinkSelected() {
const rows = selectedRows.value const rows = selectedRows.value
if (!rows.length) return if (!rows.length) return
try { try {
await ElMessageBox.confirm(`确定移除已选的 ${rows.length} 个可调配仓?`, '批量移除', { await ElMessageBox.confirm(`确定移除已选的 ${rows.length} 个源采购仓?`, '批量移除', {
type: 'warning', type: 'warning',
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
+2 -2
View File
@@ -36,7 +36,7 @@
:disabled="!effectiveSelectedRows.length || unlinking" :disabled="!effectiveSelectedRows.length || unlinking"
@click="openBatchDeployDlg" @click="openBatchDeployDlg"
> >
批量关联可调配仓 批量关联源采购仓
</el-button> </el-button>
<el-button <el-button
v-if="canUpdate && goodsRows.length" v-if="canUpdate && goodsRows.length"
@@ -692,7 +692,7 @@ async function linkGoods(items) {
async function openBatchDeployDlg() { async function openBatchDeployDlg() {
const rows = effectiveSelectedRows.value.filter(Boolean) const rows = effectiveSelectedRows.value.filter(Boolean)
if (!rows.length) { if (!rows.length) {
ElMessage.warning('请先勾选要关联可调配仓的 RDC 物料') ElMessage.warning('请先勾选要关联源采购仓的 RDC 物料')
return return
} }
const prep = await batchDeployDlgRef.value?.prepareLinkDeploy?.(rows) const prep = await batchDeployDlgRef.value?.prepareLinkDeploy?.(rows)
+1 -1
View File
@@ -3,7 +3,7 @@
<header class="wh-srf-rdc-right-hd"> <header class="wh-srf-rdc-right-hd">
<el-radio-group v-model="activeTab" size="small" class="wh-srf-rdc-right-tabs"> <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="region">可售卖区域</el-radio-button>
<el-radio-button value="deploy">源采购 RDC</el-radio-button> <el-radio-button value="deploy">源采购仓</el-radio-button>
</el-radio-group> </el-radio-group>
</header> </header>
<div class="wh-srf-rdc-right-body"> <div class="wh-srf-rdc-right-body">
+2 -125
View File
@@ -45,16 +45,6 @@
> >
批量修改 批量修改
</el-button> </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 <el-button
type="warning" type="warning"
size="small" size="small"
@@ -444,20 +434,6 @@
:submitting="addingPriceBook" :submitting="addingPriceBook"
@confirm="onCategoryGoodsPicked" @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> </div>
</template> </template>
@@ -475,6 +451,7 @@ import {
customerPriceAdd, customerPriceAdd,
customerPriceListed, customerPriceListed,
customerPriceRemove, customerPriceRemove,
buildCustomerPriceAddBody,
buildCustomerPriceWriteBody, buildCustomerPriceWriteBody,
createEmptyCustomerPriceForm, createEmptyCustomerPriceForm,
customerPriceFormFromRow, customerPriceFormFromRow,
@@ -497,13 +474,6 @@ import {
normalizeFinanceBillType, normalizeFinanceBillType,
} from '@/utils/financeBillTypes' } from '@/utils/financeBillTypes'
import { pickDisplayCode, pickDisplayName } from '@/utils/warehouseOpsDisplay' import { pickDisplayCode, pickDisplayName } from '@/utils/warehouseOpsDisplay'
import {
PRICING_TAXPAYER_DEFAULT,
PRICING_TRADE_DOMESTIC,
formatPricingCalculateDebug,
pickPricingCalculateError,
pricingCalculate,
} from '@/api/pricingTable'
defineOptions({ name: 'DealerPriceBook' }) defineOptions({ name: 'DealerPriceBook' })
@@ -558,11 +528,6 @@ const batchEditRows = ref([])
const formRef = ref() const formRef = ref()
const selected = ref(new Set()) const selected = ref(new Set())
const calculateDebugVisible = ref(false)
const calculateDebugTitle = ref('算价 debug')
const calculateDebugText = ref('')
const batchCalculating = ref(false)
const formDialogTitle = computed(() => { const formDialogTitle = computed(() => {
if (formMode.value === 'batch') return `批量修改价格本(${batchEditRows.value.length} 条)` if (formMode.value === 'batch') return `批量修改价格本(${batchEditRows.value.length} 条)`
if (formMode.value === 'edit') return '编辑价格本' if (formMode.value === 'edit') return '编辑价格本'
@@ -741,68 +706,6 @@ function fmtValidStatus(v) {
return '—' 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) { function fmtNum(v) {
if (v == null || v === '') return '—' if (v == null || v === '') return '—'
const n = Number(v) const n = Number(v)
@@ -847,7 +750,7 @@ async function loadExpenseBillNames() {
} }
function buildFormPayload() { function buildFormPayload() {
return buildCustomerPriceWriteBody(form, { return buildCustomerPriceAddBody(form, {
company_code: String(activeCompanyCode.value || '').trim(), company_code: String(activeCompanyCode.value || '').trim(),
sales_policy_type: 0, sales_policy_type: 0,
}) })
@@ -1255,17 +1158,6 @@ onMounted(() => {
gap: 8px; gap: 8px;
min-height: 24px; 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 { .dpb-selected-meta {
font-size: 13px; font-size: 13px;
color: #409eff; color: #409eff;
@@ -1632,21 +1524,6 @@ onMounted(() => {
.dpb-op-link--muted { color: #909399; } .dpb-op-link--muted { color: #909399; }
.dpb-op-link--muted:hover { color: #606266; } .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 { .dpb-footer {
flex-shrink: 0; flex-shrink: 0;
padding: 10px 16px 14px; padding: 10px 16px 14px;
+40 -1
View File
@@ -179,6 +179,27 @@
</div> </div>
</div> </div>
</section> </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>
</div> </div>
</section> </section>
@@ -724,6 +745,7 @@ import {
pickSupplierSaleType, pickSupplierSaleType,
pickPartnerOrgDisplayName, pickPartnerOrgDisplayName,
pickSupplierDisplayName, pickSupplierDisplayName,
formatSupplierSpecialDeliveryCommissionRate,
supplySupplierBlocRemove, supplySupplierBlocRemove,
supplySupplierBlocSceneListed, supplySupplierBlocSceneListed,
supplySupplierBlocSceneSet, supplySupplierBlocSceneSet,
@@ -1035,7 +1057,7 @@ const mainInfoCards = computed(() => {
const businessInfoCards = computed(() => { const businessInfoCards = computed(() => {
const row = activeRow.value const row = activeRow.value
return [ const cards = [
{ {
key: 'credit_code', key: 'credit_code',
icon: 'mdi:card-account-details-outline', icon: 'mdi:card-account-details-outline',
@@ -1057,6 +1079,13 @@ const businessInfoCards = computed(() => {
value: displayText(pickSupplierOrgType(row)), value: displayText(pickSupplierOrgType(row)),
tone: 'indigo', tone: 'indigo',
}, },
]
return cards
})
const otherInfoCards = computed(() => {
const row = activeRow.value
const cards = [
{ {
key: 'sales_type', key: 'sales_type',
icon: 'mdi:tag-outline', icon: 'mdi:tag-outline',
@@ -1065,6 +1094,16 @@ const businessInfoCards = computed(() => {
tone: 'violet', 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) { function infoCardGridStyle(card) {
@@ -2604,7 +2604,7 @@ function buildStep3Payload() {
const baseDelayed = parseWizardInt(form.baseDelayedRebateMonths, { allowZero: true }) const baseDelayed = parseWizardInt(form.baseDelayedRebateMonths, { allowZero: true })
const targetDelayed = parseWizardInt(form.targetDelayedRebateMonths, { allowZero: true }) const targetDelayed = parseWizardInt(form.targetDelayedRebateMonths, { allowZero: true })
return { return {
payment_days: parseWizardInt(form.paymentMonths), payment_months: parseWizardInt(form.paymentMonths),
acceptance_payment: form.acceptancePayment, acceptance_payment: form.acceptancePayment,
rebate_delay_base_months: baseDelayed, rebate_delay_base_months: baseDelayed,
rebate_delay_target_months: targetDelayed, rebate_delay_target_months: targetDelayed,
+58 -14
View File
@@ -41,23 +41,10 @@
/> />
</el-select> </el-select>
</el-form-item> </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-form-item v-if="showReceiptSwitch" label="是否支持收据">
<el-switch v-model="form.is_support_receipt" /> <el-switch v-model="form.is_support_receipt" />
</el-form-item> </el-form-item>
<div class="supply-supplier-form-section-title">工商信息</div>
<div class="supply-supplier-form-pair-grid"> <div class="supply-supplier-form-pair-grid">
<el-form-item label="信用代码"> <el-form-item label="信用代码">
<el-input v-model="form.credit_code" clearable /> <el-input v-model="form.credit_code" clearable />
@@ -75,6 +62,37 @@
<el-form-item label="联系地址"> <el-form-item label="联系地址">
<el-input v-model="form.contact_address" clearable /> <el-input v-model="form.contact_address" clearable />
</el-form-item> </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-form-item label="启用">
<el-switch v-model="form.is_valid" /> <el-switch v-model="form.is_valid" />
</el-form-item> </el-form-item>
@@ -312,6 +330,32 @@ async function submit() {
.supply-supplier-form--org .supply-supplier-form-pair-grid .el-form-item { .supply-supplier-form--org .supply-supplier-form-pair-grid .el-form-item {
margin-bottom: 18px; 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-dialog__body,
.el-dialog.supply-supplier-form-dlg .el-form-item__label, .el-dialog.supply-supplier-form-dlg .el-form-item__label,
.el-dialog.supply-supplier-form-dlg .el-input__inner, .el-dialog.supply-supplier-form-dlg .el-input__inner,