修改产品功能
This commit is contained in:
@@ -39,4 +39,6 @@ public class BmsProduct extends BaseEntity
|
||||
private List<BmsProductCountry> countries;
|
||||
/** query alias of status (0/1/2) */
|
||||
private String effectStatus;
|
||||
/** query: active = status in (0,1); expired = status 2 */
|
||||
private String statusGroup;
|
||||
}
|
||||
|
||||
@@ -24,11 +24,6 @@ public class PrcPriceSet extends BaseEntity
|
||||
private BigDecimal defaultDiscount;
|
||||
private String defaultType;
|
||||
private String calculateSign;
|
||||
/**
|
||||
* Pull-only: product base price version_no (V1/V2 from bms_product_price_version).
|
||||
* Not a DB column — used when pullFromLibrary=Y to choose library rows.
|
||||
*/
|
||||
private String baseVersionNo;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
private Date effectiveDate;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
|
||||
+8
-7
@@ -1830,21 +1830,22 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
|
||||
detailMapper.deletePrcPriceSetDetailByPsvId(version.getPsvId());
|
||||
// clean legacy rows without psv_id under this set (would look like duplicates in UI)
|
||||
detailMapper.deleteOrphanDetailsByPriceSetId(set.getPriceSetId());
|
||||
String baseVersionNo = set.getBaseVersionNo();
|
||||
if (StringUtils.isEmpty(baseVersionNo))
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.set.baseVersionNo.empty"));
|
||||
}
|
||||
int inserted;
|
||||
if (TYPE_DISCOUNT.equals(set.getSetType()))
|
||||
{
|
||||
inserted = detailMapper.insertDiscountDetailsFromLibrary(
|
||||
set.getPriceSetId(), version.getPsvId(), baseVersionNo);
|
||||
set.getPriceSetId(), version.getPsvId(), null);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Prefer currently effective open country rows; fallback asOf window if needed
|
||||
inserted = detailMapper.insertFixedDetailsFromLibrary(
|
||||
set.getPriceSetId(), version.getPsvId(), null, false, baseVersionNo);
|
||||
set.getPriceSetId(), version.getPsvId(), null, true, null);
|
||||
if (inserted <= 0)
|
||||
{
|
||||
inserted = detailMapper.insertFixedDetailsFromLibrary(
|
||||
set.getPriceSetId(), version.getPsvId(), new Date(), false, null);
|
||||
}
|
||||
}
|
||||
if (inserted <= 0)
|
||||
{
|
||||
|
||||
@@ -96,6 +96,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="effectStatus != null and effectStatus != ''">
|
||||
AND p.status = #{effectStatus}
|
||||
</if>
|
||||
<if test="statusGroup != null and statusGroup == 'active'">
|
||||
AND p.status in ('0', '1')
|
||||
</if>
|
||||
<if test="statusGroup != null and statusGroup == 'expired'">
|
||||
AND p.status = '2'
|
||||
<if test="params != null and params.beginTime != null and params.beginTime != ''">
|
||||
AND p.expire_date >= #{params.beginTime}
|
||||
</if>
|
||||
<if test="params != null and params.endTime != null and params.endTime != ''">
|
||||
AND p.expire_date <= #{params.endTime}
|
||||
</if>
|
||||
</if>
|
||||
<if test="cargoAttr != null and cargoAttr != ''">
|
||||
AND p.cargo_attr like concat('%', #{cargoAttr}, '%')
|
||||
</if>
|
||||
@@ -104,7 +116,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
OR p.product_name like concat('%', #{params.keyword}, '%'))
|
||||
</if>
|
||||
</where>
|
||||
order by p.product_code asc, p.effective_date desc, p.product_id desc
|
||||
<choose>
|
||||
<when test="statusGroup != null and statusGroup == 'expired'">
|
||||
order by p.expire_date desc, p.product_code asc, p.product_id desc
|
||||
</when>
|
||||
<otherwise>
|
||||
order by p.product_code asc, p.effective_date desc, p.product_id desc
|
||||
</otherwise>
|
||||
</choose>
|
||||
</select>
|
||||
|
||||
<insert id="insertBmsProduct" parameterType="BmsProduct" useGeneratedKeys="true" keyProperty="productId">
|
||||
|
||||
@@ -171,6 +171,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
and ifnull(c.status, '1') <> '2'
|
||||
<choose>
|
||||
<when test="openOnly">
|
||||
and (c.effective_date is null or date(c.effective_date) <= curdate())
|
||||
and (c.expire_date is null or date(c.expire_date) > curdate())
|
||||
</when>
|
||||
<otherwise>
|
||||
|
||||
@@ -63,11 +63,3 @@ export function delProduct(productIds) {
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
/** List base-price version options (by effective_date desc) for package create */
|
||||
export function listProductPriceVersions() {
|
||||
return request({
|
||||
url: '/price/product/priceVersions',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
@@ -95,7 +95,6 @@ import { formatWeightLabel } from '@/views/price/set/utils/priceSet'
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const columns = [
|
||||
{ prop: 'versionNo', label: '版本', width: 70, align: 'center', formatter: (row) => row.versionNo || '-' },
|
||||
{ prop: 'countryName', label: '国家', width: 120, showOverflowTooltip: true },
|
||||
// { prop: 'countryCode', label: 'ISO2', width: 60, align: 'center' },
|
||||
{ prop: 'zoneName', label: '分区名称', width: 140, showOverflowTooltip: true, formatter: (row) => row.zoneName || '-' },
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<el-drawer v-model="visible" size="960px" :title="title" append-to-body>
|
||||
<el-form :inline="true" @submit.prevent>
|
||||
<el-form-item label="产品代码">
|
||||
<el-input
|
||||
v-model="queryParams.productCode"
|
||||
placeholder="产品代码"
|
||||
clearable
|
||||
style="width: 160px"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="产品名称">
|
||||
<el-input
|
||||
v-model="queryParams.productName"
|
||||
placeholder="产品名称"
|
||||
clearable
|
||||
style="width: 160px"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="失效时间段">
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
type="daterange"
|
||||
value-format="YYYY-MM-DD"
|
||||
range-separator="-"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
style="width: 260px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-table v-loading="loading" :data="list" border max-height="calc(100vh - 260px)" empty-text="暂无失效产品">
|
||||
<el-table-column label="产品代码" prop="productCode" min-width="110" show-overflow-tooltip />
|
||||
<el-table-column label="产品名称" prop="productName" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="时效等级" prop="serviceLevel" width="90" align="center" />
|
||||
<el-table-column label="走货属性" prop="cargoAttr" min-width="110" show-overflow-tooltip />
|
||||
<el-table-column label="目的区域" prop="destRegion" width="90" align="center" />
|
||||
<el-table-column label="生效日期" prop="effectiveDate" width="120" align="center" />
|
||||
<el-table-column label="失效日期" prop="expireDate" width="120" align="center" />
|
||||
<el-table-column label="国家数" prop="countryCount" width="80" align="center" />
|
||||
<el-table-column label="附加费数" prop="surchargeCount" width="90" align="center" />
|
||||
<el-table-column label="状态" width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :options="bms_product_effect_status" :value="row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="更新内容" prop="updateNote" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="100" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
title="产品明细"
|
||||
icon="View"
|
||||
@click="emit('view-country', row)"
|
||||
v-hasPermi="['price:product:query']"
|
||||
></el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNum"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="loadList"
|
||||
/>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { listProduct } from '@/api/price/product'
|
||||
|
||||
const emit = defineEmits(['view-country'])
|
||||
const { proxy } = getCurrentInstance()
|
||||
const { bms_product_effect_status } = useDict('bms_product_effect_status')
|
||||
|
||||
const title = '失效价格'
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const list = ref([])
|
||||
const total = ref(0)
|
||||
const dateRange = ref([])
|
||||
const queryParams = ref({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
productCode: undefined,
|
||||
productName: undefined,
|
||||
statusGroup: 'expired'
|
||||
})
|
||||
|
||||
function open() {
|
||||
visible.value = true
|
||||
resetQuery()
|
||||
}
|
||||
|
||||
function loadList() {
|
||||
loading.value = true
|
||||
const p = { ...queryParams.value }
|
||||
if (dateRange.value && dateRange.value.length === 2) {
|
||||
proxy.addDateRange(p, dateRange.value)
|
||||
}
|
||||
listProduct(p).then(res => {
|
||||
list.value = res.rows || []
|
||||
total.value = res.total || 0
|
||||
}).finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function handleQuery() {
|
||||
queryParams.value.pageNum = 1
|
||||
loadList()
|
||||
}
|
||||
|
||||
function resetQuery() {
|
||||
dateRange.value = []
|
||||
queryParams.value = {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
productCode: undefined,
|
||||
productName: undefined,
|
||||
statusGroup: 'expired'
|
||||
}
|
||||
loadList()
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
<basic-table
|
||||
ref="tableRef"
|
||||
:extra-params="extraParams"
|
||||
:columns="columns"
|
||||
:search-columns="searchColumns"
|
||||
:query="queryList"
|
||||
@@ -32,6 +33,9 @@
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button plain icon="Clock" @click="expiredRef.open()" v-hasPermi="['price:product:list']">失效价格</el-button>
|
||||
</el-col>
|
||||
</template>
|
||||
|
||||
<template #col-cargoAttr="{ row }">
|
||||
@@ -52,6 +56,7 @@
|
||||
<product-form-dialog ref="formRef" @success="() => tableRef.refresh()" />
|
||||
<product-country-drawer ref="countryRef" />
|
||||
<product-surcharge-drawer ref="surchargeRef" @success="() => tableRef.refresh()" />
|
||||
<product-expired-drawer ref="expiredRef" @view-country="(row) => countryRef.open(row)" />
|
||||
<excel-import-dialog
|
||||
ref="importRef"
|
||||
title="产品 Excel 导入"
|
||||
@@ -99,16 +104,18 @@ import ExcelImportDialog from '@/components/ExcelImportDialog'
|
||||
import ProductFormDialog from './components/ProductFormDialog.vue'
|
||||
import ProductCountryDrawer from './components/ProductCountryDrawer.vue'
|
||||
import ProductSurchargeDrawer from './components/ProductSurchargeDrawer.vue'
|
||||
import ProductExpiredDrawer from './components/ProductExpiredDrawer.vue'
|
||||
import { listProduct } from '@/api/price/product'
|
||||
import { listRegionOptions } from '@/api/data/region'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
const { bms_product_effect_status } = useDict('bms_product_effect_status')
|
||||
|
||||
const extraParams = { statusGroup: 'active' }
|
||||
const tableRef = ref()
|
||||
const formRef = ref()
|
||||
const countryRef = ref()
|
||||
const surchargeRef = ref()
|
||||
const expiredRef = ref()
|
||||
const importRef = ref()
|
||||
const countryImportRef = ref()
|
||||
const importCountryCode = ref('')
|
||||
@@ -144,24 +151,12 @@ const columns = [
|
||||
const searchColumns = [
|
||||
{ prop: 'productCode', label: '产品代码', component: 'input', placeholder: '产品代码' },
|
||||
{ prop: 'productName', label: '产品名称', component: 'input', placeholder: '产品名称' }
|
||||
// {
|
||||
// prop: 'taxMode',
|
||||
// label: '税模式',
|
||||
// component: 'select',
|
||||
// dict: 'bms_tax_mode',
|
||||
// placeholder: '税模式',
|
||||
// clearable: true
|
||||
// }
|
||||
]
|
||||
|
||||
function queryList(params) {
|
||||
return listProduct(params)
|
||||
}
|
||||
|
||||
function isProductActive(row) {
|
||||
return row && String(row.status) === '1'
|
||||
}
|
||||
|
||||
function handleAdd() {
|
||||
formRef.value.open()
|
||||
}
|
||||
|
||||
@@ -7,25 +7,6 @@
|
||||
<div class="form-hint">显示给客户的套餐对外名称</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="基础价格版本" prop="baseVersionNo">
|
||||
<el-select
|
||||
v-model="basic.baseVersionNo"
|
||||
filterable
|
||||
clearable
|
||||
placeholder="请选择基础价格版本"
|
||||
style="width: 100%"
|
||||
:loading="versionLoading"
|
||||
>
|
||||
<el-option
|
||||
v-for="(item, idx) in versionOptions"
|
||||
:key="item.versionNo"
|
||||
:label="versionLabel(item, idx === 0)"
|
||||
:value="item.versionNo"
|
||||
/>
|
||||
</el-select>
|
||||
<div class="form-hint">创建套餐价格时按此版本从产品库拉取明细;默认最新版本</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="计价类型" prop="setType">
|
||||
<div class="type-row">
|
||||
<div
|
||||
@@ -69,41 +50,19 @@
|
||||
|
||||
<script setup name="StepBasicInfo">
|
||||
/**
|
||||
* Step1 basic info. baseVersionNo is pull-only (not persisted).
|
||||
* 第 1 步 · 基本信息
|
||||
* 直接读写父级传入的 basic 对象(同一引用),校验规则由本组件维护。
|
||||
*/
|
||||
import { listProductPriceVersions } from '@/api/price/product'
|
||||
|
||||
const basic = defineModel({ type: Object, required: true })
|
||||
|
||||
const formRef = ref()
|
||||
const versionLoading = ref(false)
|
||||
const versionOptions = ref([])
|
||||
|
||||
const rules = {
|
||||
priceSetName: [{ required: true, message: '套餐名称不能为空', trigger: 'blur' }],
|
||||
baseVersionNo: [{ required: true, message: '请选择基础价格版本', trigger: 'change' }],
|
||||
setType: [{ required: true, message: '请选择计价类型', trigger: 'change' }],
|
||||
}
|
||||
|
||||
function versionLabel(item, isLatest) {
|
||||
const no = item.versionNo || '-'
|
||||
return no
|
||||
}
|
||||
|
||||
async function loadVersions() {
|
||||
versionLoading.value = true
|
||||
try {
|
||||
const res = await listProductPriceVersions()
|
||||
const list = res.data || []
|
||||
versionOptions.value = list
|
||||
if (!basic.value.baseVersionNo && list.length) {
|
||||
basic.value.baseVersionNo = list[0].versionNo
|
||||
}
|
||||
} finally {
|
||||
versionLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 供父级在下一步 / 保存前调用 */
|
||||
function validate() {
|
||||
return new Promise(resolve => {
|
||||
if (!formRef.value) return resolve(false)
|
||||
@@ -111,20 +70,46 @@ function validate() {
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadVersions()
|
||||
})
|
||||
|
||||
defineExpose({ validate })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.max-form { max-width: 760px; }
|
||||
.form-hint { font-size: 12px; color: #909399; line-height: 1.4; margin-top: 4px; }
|
||||
.unit { margin-left: 8px; color: #909399; }
|
||||
.type-row { display: flex; gap: 12px; width: 100%; }
|
||||
.type-card { flex: 1; border: 1px solid #d9d9d9; border-radius: 6px; padding: 14px 16px; cursor: pointer; }
|
||||
.type-card.selected { border-color: #1890ff; background: #e6f7ff; }
|
||||
.t-title { font-weight: 600; margin-bottom: 4px; }
|
||||
.t-desc { font-size: 12px; color: #909399; line-height: 1.5; }
|
||||
.max-form {
|
||||
max-width: 760px;
|
||||
}
|
||||
.form-hint {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
line-height: 1.4;
|
||||
margin-top: 4px;
|
||||
}
|
||||
.unit {
|
||||
margin-left: 8px;
|
||||
color: #909399;
|
||||
}
|
||||
.type-row {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
}
|
||||
.type-card {
|
||||
flex: 1;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 6px;
|
||||
padding: 14px 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.type-card.selected {
|
||||
border-color: #1890ff;
|
||||
background: #e6f7ff;
|
||||
}
|
||||
.t-title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.t-desc {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
line-height: 1.5;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -220,7 +220,6 @@ function buildPayload() {
|
||||
setType: basic.setType,
|
||||
defaultDiscount: basic.setType === 'DISCOUNT' ? basic.defaultDiscount : undefined,
|
||||
priceSetNote: basic.priceSetNote,
|
||||
baseVersionNo: basic.baseVersionNo || undefined,
|
||||
version: {
|
||||
psvId: copyMode.value ? undefined : version.psvId,
|
||||
versionNo: version.versionNo
|
||||
|
||||
@@ -361,7 +361,6 @@ export function createEmptyBasicInfo() {
|
||||
defaultDiscount: 88,
|
||||
effectiveDate: undefined,
|
||||
expireDate: undefined,
|
||||
baseVersionNo: undefined,
|
||||
priceSetNote: ''
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user