Compare commits

3 Commits

13 changed files with 254 additions and 115 deletions
@@ -24,6 +24,7 @@ import com.bms.common.core.domain.AjaxResult;
import com.bms.common.core.page.TableDataInfo; import com.bms.common.core.page.TableDataInfo;
import com.bms.common.enums.BusinessType; import com.bms.common.enums.BusinessType;
import com.bms.common.exception.ServiceException; import com.bms.common.exception.ServiceException;
import com.bms.price.domain.PayProduct;
import com.bms.price.domain.PaySaveCommand; import com.bms.price.domain.PaySaveCommand;
import com.bms.price.domain.PrcCustomerPriceSet; import com.bms.price.domain.PrcCustomerPriceSet;
import com.bms.price.domain.PrcCustomerPriceSetBatch; import com.bms.price.domain.PrcCustomerPriceSetBatch;
@@ -208,6 +209,15 @@ public class LabelPackageController extends BaseController
return success(payProductService.selectPayProductDetail(productId)); return success(payProductService.selectPayProductDetail(productId));
} }
/** 保存打单产品明细(重量段/附加费等),按当前 term 覆盖子表 */
@PreAuthorize("@ss.hasPermi('price:label:edit')")
@Log(title = "打单产品明细", businessType = BusinessType.UPDATE)
@PutMapping("/product")
public AjaxResult updateProduct(@RequestBody PayProduct product)
{
return toAjax(payProductService.updateDetail(product));
}
/** 查询打单产品已绑定渠道(默认应收 AR) */ /** 查询打单产品已绑定渠道(默认应收 AR) */
@PreAuthorize("@ss.hasPermi('price:label:query')") @PreAuthorize("@ss.hasPermi('price:label:query')")
@GetMapping("/product/{productId}/channels") @GetMapping("/product/{productId}/channels")
@@ -73,10 +73,4 @@ public class PaySurcharge
private BigDecimal weightMax; private BigDecimal weightMax;
private Integer sortNo; private Integer sortNo;
/**
* 是否计费并落库:false 不写入 pay_surcharge;null/缺省视为要计费(兼容应付全量保存)。
* 仅作为前端传参,不落库字段。
*/
private Boolean needBill;
} }
@@ -461,8 +461,6 @@ public class PayProductServiceImpl implements IPayProductService
{ {
product.setExtraLines(new ArrayList<>()); product.setExtraLines(new ArrayList<>());
} }
// 计数前先按「是否计费」过滤,与落库行数一致
retainBillableSurcharges(product);
product.setWeightCount(product.getWeightBands().size()); product.setWeightCount(product.getWeightBands().size());
product.setSurchargeCount(product.getSurcharges().size()); product.setSurchargeCount(product.getSurcharges().size());
product.setExtraCount(product.getExtraLines().size()); product.setExtraCount(product.getExtraLines().size());
@@ -1531,7 +1529,6 @@ public class PayProductServiceImpl implements IPayProductService
private void insertSurcharges(PayProduct product) private void insertSurcharges(PayProduct product)
{ {
retainBillableSurcharges(product);
List<PaySurcharge> rows = product.getSurcharges(); List<PaySurcharge> rows = product.getSurcharges();
if (rows == null || rows.isEmpty()) if (rows == null || rows.isEmpty())
{ {
@@ -1550,27 +1547,6 @@ public class PayProductServiceImpl implements IPayProductService
} }
} }
/**
* 仅保留需要计费的附加费:needBill=false 不入库;null 视为要计费。
*/
private void retainBillableSurcharges(PayProduct product)
{
List<PaySurcharge> rows = product.getSurcharges();
if (rows == null || rows.isEmpty())
{
return;
}
List<PaySurcharge> kept = new ArrayList<PaySurcharge>();
for (PaySurcharge fee : rows)
{
if (fee != null && !Boolean.FALSE.equals(fee.getNeedBill()))
{
kept.add(fee);
}
}
product.setSurcharges(kept);
}
/** /**
* 按附加费名称映射 prc_surcharge.sc_id;币种不能为空:附加费 -> 价卡 -> EUR。 * 按附加费名称映射 prc_surcharge.sc_id;币种不能为空:附加费 -> 价卡 -> EUR。
*/ */
@@ -3127,7 +3127,7 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
} }
/** /**
* 业务规则计费重量(VOL_8000 等)。 * 业务规则计费重量(THREE_SIDE / VOL_8000)。
* 如需禁用:注释掉 resolveWeightByRule 调用,始终返回 baseWeight。 * 如需禁用:注释掉 resolveWeightByRule 调用,始终返回 baseWeight。
*/ */
private BigDecimal applyBizRuleWeight(TmsPriceBillRequest request, BigDecimal baseWeight) private BigDecimal applyBizRuleWeight(TmsPriceBillRequest request, BigDecimal baseWeight)
@@ -21,7 +21,7 @@ public interface IBmsBizRuleService
int deleteByIds(Long[] ruleIds); int deleteByIds(Long[] ruleIds);
/** /**
* 将生效的重量规则(目前为 VOL_8000)应用到计费请求。 * 将生效的重量规则(THREE_SIDE / VOL_8000)应用到计费请求。
* @return 规则命中时返回覆盖后的计费重量;无规则适用时返回 null * @return 规则命中时返回覆盖后的计费重量;无规则适用时返回 null
*/ */
BigDecimal resolveWeightByRule(TmsPriceBillRequest request, BigDecimal systemWeight); BigDecimal resolveWeightByRule(TmsPriceBillRequest request, BigDecimal systemWeight);
@@ -136,8 +136,12 @@ public class BmsBizRuleServiceImpl implements IBmsBizRuleService
{ {
return null; return null;
} }
// VOL_8000 在本地入库 / 计费时应用。THREE_SIDE 仅作为配置 // 先三边之和取重,未命中再走 VOL_8000
// (上游已应用该产品规则)。 BigDecimal threeSide = resolveThreeSideWeight(request, systemWeight);
if (threeSide != null)
{
return threeSide;
}
BigDecimal vol8000 = resolveVol8000(request, systemWeight); BigDecimal vol8000 = resolveVol8000(request, systemWeight);
if (vol8000 != null) if (vol8000 != null)
{ {
@@ -146,6 +150,47 @@ public class BmsBizRuleServiceImpl implements IBmsBizRuleService
return null; return null;
} }
/**
* 三边之和取重:命中 THREE_SIDE 规则时,
* L+W+H < 上限取本地收货重;>= 上限取已有计费重。
*
* @param existingChargeWeight 当前已有计费重(未被三边规则覆盖前)
* @return 命中规则时返回计费重;未命中返回 null
*/
private BigDecimal resolveThreeSideWeight(TmsPriceBillRequest request, BigDecimal existingChargeWeight)
{
List<BmsBizRule> rules = bizRuleMapper.selectActiveByType(RULE_TYPE_THREE_SIDE);
if (rules == null || rules.isEmpty())
{
return null;
}
for (BmsBizRule rule : rules)
{
if (!matchThreeSideDims(rule, request, existingChargeWeight))
{
continue;
}
if (rule.getSideSumMax() == null)
{
continue;
}
BigDecimal sum = sideSum(request);
if (sum == null)
{
continue;
}
// 三边之和 < 上限(如 90):取本地收货重;无收货重则保留已有计费重
if (sum.compareTo(rule.getSideSumMax()) < 0)
{
BigDecimal receiveWeight = firstPositive(request.getReceiveWeight());
return receiveWeight != null ? receiveWeight : existingChargeWeight;
}
// 三边之和 >= 上限:保留已有计费重
return existingChargeWeight;
}
return null;
}
/** /**
* VOL_8000:chargeWeight = max(L*W*H/8000, receive_weight)。 * VOL_8000:chargeWeight = max(L*W*H/8000, receive_weight)。
* 将体积重(/8000)与收货重比较。 * 将体积重(/8000)与收货重比较。
@@ -273,7 +318,10 @@ public class BmsBizRuleServiceImpl implements IBmsBizRuleService
return new ArrayList<String>(set); return new ArrayList<String>(set);
} }
private boolean matchThreeSide(BmsBizRule rule, TmsPriceBillRequest request, BigDecimal systemWeight) /**
* 三边规则维度匹配(不包含三边和阈值判断)。
*/
private boolean matchThreeSideDims(BmsBizRule rule, TmsPriceBillRequest request, BigDecimal systemWeight)
{ {
if (rule == null) if (rule == null)
{ {
@@ -292,19 +340,7 @@ public class BmsBizRuleServiceImpl implements IBmsBizRuleService
return false; return false;
} }
BigDecimal checkWeight = firstPositive(request.getReceiveWeight(), systemWeight, request.getWeight()); BigDecimal checkWeight = firstPositive(request.getReceiveWeight(), systemWeight, request.getWeight());
if (!inWeightRange(checkWeight, rule.getWeightMin(), rule.getWeightMax())) return inWeightRange(checkWeight, rule.getWeightMin(), rule.getWeightMax());
{
return false;
}
if (rule.getSideSumMax() != null)
{
BigDecimal sum = sideSum(request);
if (sum == null || sum.compareTo(rule.getSideSumMax()) >= 0)
{
return false;
}
}
return true;
} }
private boolean dimMatch(String requestCode, String ruleCode, boolean tryProductMap) private boolean dimMatch(String requestCode, String ruleCode, boolean tryProductMap)
+11
View File
@@ -155,6 +155,17 @@ export function getLabelProduct(productId, termId) {
}) })
} }
/** 保存打单产品明细(重量段/附加费) */
export function updateLabelProduct(data) {
return request({
url: '/price/label/product',
method: 'put',
data,
headers: { repeatSubmit: false },
timeout: 120000
})
}
export function removeLabelProducts(priceSetId, productIds) { export function removeLabelProducts(priceSetId, productIds) {
return request({ return request({
url: '/price/label/' + priceSetId + '/products/' + productIds, url: '/price/label/' + priceSetId + '/products/' + productIds,
@@ -79,16 +79,26 @@
<div class="pay-edit-bar"> <div class="pay-edit-bar">
<el-input v-model="pane.feeKeyword" clearable placeholder="筛选附加费用、地区、价格、货币或费用解释" class="pay-filter" @input="pane.feePageNo = 1" /> <el-input v-model="pane.feeKeyword" clearable placeholder="筛选附加费用、地区、价格、货币或费用解释" class="pay-filter" @input="pane.feePageNo = 1" />
<el-button v-if="editable" size="small" type="primary" plain @click="openFeePicker(pane)">添加附加费</el-button> <el-button v-if="editable" size="small" type="primary" plain @click="openFeePicker(pane)">添加附加费</el-button>
<template v-if="billSelect"> <el-button
<el-button size="small" @click="setFeesBill(pane, true)">全选计费</el-button> v-if="editable"
<el-button size="small" @click="setFeesBill(pane, false)">取消全选</el-button> size="small"
<span class="pay-bill-tip">勾选后才会写入 pay_surcharge;默认不勾选</span> type="danger"
</template> plain
:disabled="!feeCheckedCount(pane)"
@click="removeCheckedFees(pane)"
>批量删除</el-button>
</div> </div>
<el-table :data="pageFees(pane)" border size="small" empty-text="没有附加费"> <el-table :data="pageFees(pane)" border size="small" empty-text="没有附加费">
<el-table-column v-if="billSelect" label="是否计费" width="88" align="center" fixed="left"> <el-table-column v-if="editable" width="48" align="center" fixed="left">
<template #header>
<el-checkbox
:model-value="isAllFilteredFeesChecked(pane)"
:indeterminate="isFeeCheckIndeterminate(pane)"
@update:model-value="v => setFilteredFeesChecked(pane, v)"
/>
</template>
<template #default="{ row }"> <template #default="{ row }">
<el-checkbox :model-value="row.needBill === true" @update:model-value="v => { row.needBill = !!v }" /> <el-checkbox :model-value="row._checked === true" @update:model-value="v => { row._checked = !!v }" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="feeName" label="附加费用" min-width="180" show-overflow-tooltip> <el-table-column prop="feeName" label="附加费用" min-width="180" show-overflow-tooltip>
@@ -266,11 +276,6 @@ const props = defineProps({
type: Boolean, type: Boolean,
default: false default: false
}, },
/** 打单套餐:附加费表显示「是否计费」勾选,仅勾选行入库 */
billSelect: {
type: Boolean,
default: false
},
savingRowKey: { savingRowKey: {
type: [String, Number], type: [String, Number],
default: '' default: ''
@@ -541,18 +546,51 @@ function filteredFees(pane) {
function feeTabLabel(pane) { function feeTabLabel(pane) {
const fees = pane.item.surcharges || [] const fees = pane.item.surcharges || []
if (!props.billSelect) return '附加费(' + fees.length + ')' return '附加费(' + fees.length + ')'
const n = fees.filter(f => f && f.needBill === true).length
return '附加费(已选 ' + n + '/' + fees.length + ')'
} }
function setFeesBill(pane, value) { function feeCheckedCount(pane) {
const fees = pane.item.surcharges || [] return (pane?.item?.surcharges || []).filter(fee => fee && fee._checked === true).length
for (const fee of fees) { }
if (fee) fee.needBill = !!value
function isAllFilteredFeesChecked(pane) {
const rows = filteredFees(pane)
return rows.length > 0 && rows.every(fee => fee && fee._checked === true)
}
function isFeeCheckIndeterminate(pane) {
const rows = filteredFees(pane)
if (!rows.length) return false
const n = rows.filter(fee => fee && fee._checked === true).length
return n > 0 && n < rows.length
}
function setFilteredFeesChecked(pane, value) {
for (const fee of filteredFees(pane)) {
if (fee) fee._checked = !!value
} }
} }
async function removeCheckedFees(pane) {
const list = pane?.item?.surcharges
if (!list?.length) return
const checked = list.filter(fee => fee && fee._checked === true)
if (!checked.length) {
proxy.$modal.msgWarning('请先勾选要删除的附加费')
return
}
try {
await proxy.$modal.confirm(`确认删除所选 ${checked.length} 项附加费?`)
} catch {
return
}
const remain = list.filter(fee => !(fee && fee._checked === true))
pane.item.surcharges = remain
const maxPage = Math.max(1, Math.ceil(remain.length / pageSize) || 1)
if (pane.feePageNo > maxPage) pane.feePageNo = maxPage
proxy.$modal.msgSuccess(`已删除 ${checked.length} 项`)
}
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
const feePickerVisible = ref(false) const feePickerVisible = ref(false)
const feePickerPane = ref(null) const feePickerPane = ref(null)
@@ -589,9 +627,7 @@ function configToFeeRow(cfg, product) {
priceUnit: isPercent ? '%' : (currency + '/ITEM'), priceUnit: isPercent ? '%' : (currency + '/ITEM'),
feeDesc: String(cfg?.scNote || '').trim(), feeDesc: String(cfg?.scNote || '').trim(),
calcRule: toCalcRule(cfg?.chargeType), calcRule: toCalcRule(cfg?.chargeType),
calcParam: '', calcParam: ''
// 从配置添加默认要计费
needBill: true
} }
} }
@@ -738,10 +774,6 @@ function statusText(status) {
gap: 8px; gap: 8px;
margin-bottom: 8px; margin-bottom: 8px;
} }
.pay-bill-tip {
color: var(--el-text-color-secondary);
font-size: 12px;
}
.pay-hint { .pay-hint {
margin: 0 0 8px; margin: 0 0 8px;
color: var(--el-text-color-secondary); color: var(--el-text-color-secondary);
@@ -14,6 +14,13 @@
placeholder="搜索附加费名称、计费方式、币种、国家或备注" placeholder="搜索附加费名称、计费方式、币种、国家或备注"
class="picker-search" class="picker-search"
/> />
<el-button
type="primary"
plain
icon="Plus"
v-hasPermi="['price:surcharge:add']"
@click="openCreate"
>新建附加费</el-button>
<span class="picker-meta">共 {{ filteredRows.length }} 项,已选 {{ picked.length }} 项</span> <span class="picker-meta">共 {{ filteredRows.length }} 项,已选 {{ picked.length }} 项</span>
</div> </div>
@@ -43,17 +50,20 @@
<el-table-column prop="scNote" label="备注" min-width="160" show-overflow-tooltip /> <el-table-column prop="scNote" label="备注" min-width="160" show-overflow-tooltip />
</el-table> </el-table>
<div class="picker-hint">勾选后点确定写入当前产品附加费表;已存在相同配置的不会重复添加。</div> <div class="picker-hint">可先「新建附加费」写入配置库;勾选后点确定写入当前产品附加费表;已存在相同配置的不会重复添加。</div>
<template #footer> <template #footer>
<el-button @click="visible = false">取 消</el-button> <el-button @click="visible = false">取 消</el-button>
<el-button type="primary" :disabled="!picked.length" @click="submit">确 定</el-button> <el-button type="primary" :disabled="!picked.length" @click="submit">确 定</el-button>
</template> </template>
</el-dialog> </el-dialog>
<surcharge-form-dialog ref="formDialogRef" @success="onCreated" />
</template> </template>
<script setup> <script setup>
import { listSurchargeOptions } from '@/api/price/surcharge' import { listSurchargeOptions } from '@/api/price/surcharge'
import SurchargeFormDialog from '@/views/price/basic/surcharge/components/SurchargeFormDialog.vue'
const props = defineProps({ const props = defineProps({
modelValue: { type: Boolean, default: false }, modelValue: { type: Boolean, default: false },
@@ -64,6 +74,7 @@ const emit = defineEmits(['update:modelValue', 'confirm'])
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
const tableRef = ref() const tableRef = ref()
const formDialogRef = ref()
const loading = ref(false) const loading = ref(false)
const keyword = ref('') const keyword = ref('')
const rows = ref([]) const rows = ref([])
@@ -187,9 +198,25 @@ function onOpened() {
nextTick(() => { nextTick(() => {
tableRef.value?.clearSelection?.() tableRef.value?.clearSelection?.()
}) })
if (!rows.value.length) {
loadRows() loadRows()
}
function openCreate() {
formDialogRef.value?.open()
}
async function onCreated() {
const before = new Set((rows.value || []).map(row => String(row.scId)))
await loadRows()
// 自动勾选刚新建的配置
const next = new Set(pickedIds.value)
for (const row of rows.value || []) {
if (row?.scId == null) continue
const id = String(row.scId)
if (!before.has(id) && canSelect(row)) next.add(id)
} }
pickedIds.value = next
nextTick(() => restoreSelection())
} }
function submit() { function submit() {
@@ -215,11 +242,12 @@ watch(() => props.modelValue, v => {
.picker-bar { .picker-bar {
display: flex; display: flex;
align-items: center; align-items: center;
flex-wrap: wrap;
gap: 12px; gap: 12px;
margin-bottom: 10px; margin-bottom: 10px;
} }
.picker-search { .picker-search {
width: 360px; width: 320px;
} }
.picker-meta { .picker-meta {
color: var(--el-text-color-secondary); color: var(--el-text-color-secondary);
@@ -87,7 +87,7 @@
</div> </div>
<el-drawer v-model="detailVisible" :title="detailTitle" size="78%" destroy-on-close append-to-body> <el-drawer v-model="detailVisible" :title="detailTitle" size="78%" destroy-on-close append-to-body>
<pay-config-detail :product="detailProduct" :editable="false" :bill-select="true" /> <pay-config-detail :product="detailProduct" :editable="false" />
<template #footer> <template #footer>
<el-button @click="detailVisible = false">关闭</el-button> <el-button @click="detailVisible = false">关闭</el-button>
</template> </template>
@@ -187,9 +187,7 @@ async function onFile(uploadFile) {
const data = res.data || {} const data = res.data || {}
data.products = (data.products || []).map((item, index) => ({ data.products = (data.products || []).map((item, index) => ({
...item, ...item,
rowKey: `${item.sheetName || ''}-${item.productName || ''}-${index}`, rowKey: `${item.sheetName || ''}-${item.productName || ''}-${index}`
// 解析后默认不计费,勾选后才入 pay_surcharge
surcharges: (item.surcharges || []).map(fee => ({ ...fee, needBill: fee?.needBill === true }))
})) }))
preview.value = data preview.value = data
const picked = data.products.filter(row => canSelect(row)) const picked = data.products.filter(row => canSelect(row))
@@ -312,17 +310,16 @@ async function submit(payload = {}) {
const productsPayload = (payload.products && payload.products.length) const productsPayload = (payload.products && payload.products.length)
? payload.products ? payload.products
: selected.value.map(row => ({ ...row, productType: 'AR' })) : selected.value.map(row => ({ ...row, productType: 'AR' }))
const filteredProducts = productsPayload.map(product => ({ const productsForSave = productsPayload.map(product => ({
...product, ...product,
productType: product.productType || 'AR', productType: product.productType || 'AR'
surcharges: (product.surcharges || []).filter(fee => fee && fee.needBill === true)
})) }))
const res = await saveLabelImport({ const res = await saveLabelImport({
priceSetId: payload.priceSetId || null, priceSetId: payload.priceSetId || null,
priceSetName: String(payload.priceSetName || '').trim(), priceSetName: String(payload.priceSetName || '').trim(),
priceSetNote: payload.priceSetNote || '', priceSetNote: payload.priceSetNote || '',
overwrite: payload.overwrite != null ? payload.overwrite : overwrite.value, overwrite: payload.overwrite != null ? payload.overwrite : overwrite.value,
products: filteredProducts products: productsForSave
}) })
const data = res.data || {} const data = res.data || {}
return { return {
@@ -22,11 +22,11 @@
<el-table-column :label="colTerm" min-width="180" align="center"> <el-table-column :label="colTerm" min-width="180" align="center">
<template #default="{ row }">{{ termText(row) }}</template> <template #default="{ row }">{{ termText(row) }}</template>
</el-table-column> </el-table-column>
<el-table-column :label="colStatus" width="90" align="center"> <!-- <el-table-column :label="colStatus" width="90" align="center">
<template #default="{ row }"> <template #default="{ row }">
<el-tag :type="termStatusTag(row.termStatus)" size="small">{{ termStatusText(row.termStatus) }}</el-tag> <el-tag :type="termStatusTag(row.termStatus)" size="small">{{ termStatusText(row.termStatus) }}</el-tag>
</template> </template>
</el-table-column> </el-table-column> -->
<el-table-column prop="weightCount" :label="colWeight" width="80" align="center" /> <el-table-column prop="weightCount" :label="colWeight" width="80" align="center" />
<el-table-column prop="surchargeCount" :label="colSc" width="80" align="center" /> <el-table-column prop="surchargeCount" :label="colSc" width="80" align="center" />
<el-table-column :label="colBoundChannels" min-width="180" show-overflow-tooltip> <el-table-column :label="colBoundChannels" min-width="180" show-overflow-tooltip>
@@ -34,16 +34,29 @@
</el-table-column> </el-table-column>
<el-table-column :label="colAction" width="160" align="center" fixed="right"> <el-table-column :label="colAction" width="160" align="center" fixed="right">
<template #default="{ row }"> <template #default="{ row }">
<el-button link type="primary" @click="openProduct(row)" v-hasPermi="['price:label:query']">查看</el-button> <el-button link type="primary" @click="openProduct(row)" v-hasPermi="['price:label:query', 'price:label:edit']">编辑</el-button>
<el-button link type="primary" @click="openBindList(row)" v-hasPermi="['price:label:edit']">绑定</el-button> <el-button link type="primary" @click="openBindList(row)" v-hasPermi="['price:label:edit']">绑定</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<el-drawer v-model="detailVisible" :title="detailTitle" size="78%" destroy-on-close append-to-body> <el-drawer v-model="detailVisible" :title="detailTitle" size="78%" destroy-on-close append-to-body>
<pay-config-detail v-loading="detailLoading" :product="detail" :editable="false" /> <pay-config-detail
v-loading="detailLoading"
:product="detail"
:editable="canEditDetail"
:saving-row-key="savingRowKey"
@save-row="saveDetailRow"
/>
<template #footer> <template #footer>
<el-button @click="detailVisible = false">关闭</el-button> <el-button @click="detailVisible = false">关闭</el-button>
<el-button
v-if="canEditDetail"
type="primary"
:loading="detailSaving"
v-hasPermi="['price:label:edit']"
@click="saveDetail"
>保存明细</el-button>
</template> </template>
</el-drawer> </el-drawer>
@@ -125,9 +138,11 @@
import { import {
listLabelProducts, listLabelProducts,
getLabelProduct, getLabelProduct,
updateLabelProduct,
listLabelProductChannels, listLabelProductChannels,
replaceLabelProductChannels replaceLabelProductChannels
} from '@/api/price/label' } from '@/api/price/label'
import { checkPermi } from '@/utils/permission'
import { listChannelOptions } from '@/api/masterdata/serviceChannel' import { listChannelOptions } from '@/api/masterdata/serviceChannel'
import PayConfigDetail from '@/views/price/basic/pay/components/PayConfigDetail.vue' import PayConfigDetail from '@/views/price/basic/pay/components/PayConfigDetail.vue'
@@ -144,7 +159,10 @@ const rows = ref([])
const keyword = ref('') const keyword = ref('')
const detailVisible = ref(false) const detailVisible = ref(false)
const detailLoading = ref(false) const detailLoading = ref(false)
const detailSaving = ref(false)
const savingRowKey = ref('')
const detail = ref(null) const detail = ref(null)
const canEditDetail = computed(() => checkPermi(['price:label:edit']))
const bindListVisible = ref(false) const bindListVisible = ref(false)
const bindListLoading = ref(false) const bindListLoading = ref(false)
@@ -258,9 +276,66 @@ async function load() {
} }
} }
function cleanMoney(product) {
if (!product) return
;(product.weightBands || []).forEach(row => {
if (row && row.price === '') row.price = null
})
;(product.surcharges || []).forEach(row => {
if (row && row.amount === '') row.amount = null
})
}
async function persistDetail() {
if (!detail.value?.productId) {
throw new Error('NO_PRODUCT')
}
cleanMoney(detail.value)
await updateLabelProduct(detail.value)
const termId = detail.value.termId
const res = await getLabelProduct(detail.value.productId, termId)
detail.value = res.data || detail.value
await load()
emit('changed')
}
async function saveDetail() {
if (!detail.value || detailSaving.value || savingRowKey.value) return
detailSaving.value = true
try {
await persistDetail()
proxy.$modal.msgSuccess('明细已保存')
} catch (e) {
// request 拦截器已提示
} finally {
detailSaving.value = false
}
}
async function saveDetailRow(payload) {
if (!detail.value || savingRowKey.value || detailSaving.value) {
payload?.done?.(false)
return
}
savingRowKey.value = payload?.rowKey || 'row'
let ok = false
try {
await persistDetail()
ok = true
proxy.$modal.msgSuccess('该行已保存')
} catch (e) {
ok = false
} finally {
savingRowKey.value = ''
payload?.done?.(ok)
}
}
async function openProduct(row) { async function openProduct(row) {
detailVisible.value = true detailVisible.value = true
detailLoading.value = true detailLoading.value = true
detailSaving.value = false
savingRowKey.value = ''
detail.value = null detail.value = null
try { try {
const res = await getLabelProduct(row.productId, row.termId) const res = await getLabelProduct(row.productId, row.termId)
@@ -38,11 +38,11 @@
<el-table-column label="失效时间" min-width="170" align="center"> <el-table-column label="失效时间" min-width="170" align="center">
<template #default="{ row }">{{ formatHour(row.expireTime) || '-' }}</template> <template #default="{ row }">{{ formatHour(row.expireTime) || '-' }}</template>
</el-table-column> </el-table-column>
<el-table-column label="状态" width="100" align="center"> <!-- <el-table-column label="状态" width="100" align="center">
<template #default="{ row }"> <template #default="{ row }">
<el-tag :type="statusTag(row.status)" size="small">{{ statusText(row.status) }}</el-tag> <el-tag :type="statusTag(row.status)" size="small">{{ statusText(row.status) }}</el-tag>
</template> </template>
</el-table-column> </el-table-column> -->
<el-table-column label="产品数" width="90" align="center"> <el-table-column label="产品数" width="90" align="center">
<template #default="{ row }">{{ row.productCount || 0 }}</template> <template #default="{ row }">{{ row.productCount || 0 }}</template>
</el-table-column> </el-table-column>
+3 -23
View File
@@ -100,7 +100,6 @@
<pay-config-detail <pay-config-detail
:product="detailProduct" :product="detailProduct"
:editable="true" :editable="true"
:bill-select="true"
:saving-row-key="savingRowKey" :saving-row-key="savingRowKey"
@save-row="saveDetailRow" @save-row="saveDetailRow"
/> />
@@ -220,8 +219,7 @@ async function loadEditProducts() {
} }
} }
})) }))
// 库中已落库的附加费默认勾选(保留原计费项) productRows.value = details
productRows.value = markSurchargeBillFlags(details, true)
selectedProducts.value = [] selectedProducts.value = []
importMeta.sourceFile = details[0]?.sourceFile || '' importMeta.sourceFile = details[0]?.sourceFile || ''
importMeta.supplierName = details[0]?.supplierName || '' importMeta.supplierName = details[0]?.supplierName || ''
@@ -235,23 +233,6 @@ async function openImport() {
importRef.value?.open() importRef.value?.open()
} }
function markSurchargeBillFlags(products, defaultNeedBill) {
return (products || []).map(product => ({
...product,
surcharges: (product.surcharges || []).map(fee => ({
...fee,
needBill: fee?.needBill === true ? true : (fee?.needBill === false ? false : !!defaultNeedBill)
}))
}))
}
function withBillableSurcharges(products) {
return (products || []).map(product => ({
...product,
surcharges: (product.surcharges || []).filter(fee => fee && fee.needBill === true)
}))
}
function onProductSelect(rows) { function onProductSelect(rows) {
selectedProducts.value = rows || [] selectedProducts.value = rows || []
} }
@@ -297,8 +278,7 @@ async function removeSelectedProducts() {
} }
function onImportConfirm(payload) { function onImportConfirm(payload) {
// 价卡解析行默认不计费,需手动勾选 productRows.value = payload?.products || []
productRows.value = markSurchargeBillFlags(payload?.products || [], false)
selectedProducts.value = [] selectedProducts.value = []
importMeta.sourceFile = payload?.sourceFile || '' importMeta.sourceFile = payload?.sourceFile || ''
importMeta.supplierName = payload?.supplierName || '' importMeta.supplierName = payload?.supplierName || ''
@@ -414,7 +394,7 @@ async function finish() {
try { try {
const payload = { const payload = {
overwrite: editVersionMode.value ? true : importMeta.overwrite, overwrite: editVersionMode.value ? true : importMeta.overwrite,
products: withBillableSurcharges(productRows.value) products: productRows.value
} }
if (editVersionMode.value) { if (editVersionMode.value) {
payload.priceSetId = priceSetId.value payload.priceSetId = priceSetId.value