增加删除功能
This commit is contained in:
@@ -112,6 +112,7 @@ price.zone.countryCode.empty=国家编码不能为空,请检查 region.country
|
|||||||
# 产品导入 BmsProduct
|
# 产品导入 BmsProduct
|
||||||
price.product.codeDate.exists=同一产品代码与生效日期已存在,不能覆盖历史数据
|
price.product.codeDate.exists=同一产品代码与生效日期已存在,不能覆盖历史数据
|
||||||
price.product.effectiveDate.empty=生效日期不能为空
|
price.product.effectiveDate.empty=生效日期不能为空
|
||||||
|
price.product.delete.pendingOnly=仅待生效产品可删除,以下不符合:{0}
|
||||||
price.product.import.effEmpty=缺少生效日期,已跳过
|
price.product.import.effEmpty=缺少生效日期,已跳过
|
||||||
price.product.import.effRequired=请选择生效时间
|
price.product.import.effRequired=请选择生效时间
|
||||||
price.product.import.fileEmpty=上传文件不能为空
|
price.product.import.fileEmpty=上传文件不能为空
|
||||||
|
|||||||
@@ -39,6 +39,11 @@ public interface BmsProductCountryMapper
|
|||||||
@Param("productCode") String productCode,
|
@Param("productCode") String productCode,
|
||||||
@Param("newEffectiveDate") Date newEffectiveDate);
|
@Param("newEffectiveDate") Date newEffectiveDate);
|
||||||
|
|
||||||
|
/** After deleting a pending version: reopen country rows closed at that effective date */
|
||||||
|
public int reopenExpiredByProductCodeAndExpireDate(
|
||||||
|
@Param("productCode") String productCode,
|
||||||
|
@Param("expireDate") Date expireDate);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close open rows for one product + country where effective_date < newEffectiveDate.
|
* Close open rows for one product + country where effective_date < newEffectiveDate.
|
||||||
* Match by country_code when present, otherwise country_name.
|
* Match by country_code when present, otherwise country_name.
|
||||||
|
|||||||
@@ -39,6 +39,11 @@ public interface BmsProductMapper
|
|||||||
@Param("newEffectiveDate") Date newEffectiveDate,
|
@Param("newEffectiveDate") Date newEffectiveDate,
|
||||||
@Param("excludeId") Long excludeId);
|
@Param("excludeId") Long excludeId);
|
||||||
|
|
||||||
|
/** After deleting a pending version: reopen rows closed at that effective date */
|
||||||
|
public int reopenExpiredByProductCodeAndExpireDate(
|
||||||
|
@Param("productCode") String productCode,
|
||||||
|
@Param("expireDate") Date expireDate);
|
||||||
|
|
||||||
/** Recompute status from effective_date / expire_date vs today */
|
/** Recompute status from effective_date / expire_date vs today */
|
||||||
public int refreshStatusByDates();
|
public int refreshStatusByDates();
|
||||||
}
|
}
|
||||||
|
|||||||
+36
-1
@@ -174,6 +174,9 @@ public class BmsProductServiceImpl implements IBmsProductService
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
List<String> codesNoRemain = new ArrayList<>();
|
List<String> codesNoRemain = new ArrayList<>();
|
||||||
|
List<Long> deletableIds = new ArrayList<>();
|
||||||
|
List<String> notPendingTips = new ArrayList<>();
|
||||||
|
List<BmsProduct> toReopen = new ArrayList<>();
|
||||||
for (Long id : productIds)
|
for (Long id : productIds)
|
||||||
{
|
{
|
||||||
BmsProduct p = productMapper.selectBmsProductById(id);
|
BmsProduct p = productMapper.selectBmsProductById(id);
|
||||||
@@ -181,14 +184,46 @@ public class BmsProductServiceImpl implements IBmsProductService
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
String liveStatus = resolveStoredStatus(p.getEffectiveDate(), p.getExpireDate());
|
||||||
|
if (!BmsProduct.STATUS_PENDING.equals(liveStatus))
|
||||||
|
{
|
||||||
|
String tip = p.getProductCode();
|
||||||
|
if (p.getEffectiveDate() != null)
|
||||||
|
{
|
||||||
|
tip = tip + "(" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(p.getEffectiveDate()) + ")";
|
||||||
|
}
|
||||||
|
notPendingTips.add(tip);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (p.getProductId() != null)
|
if (p.getProductId() != null)
|
||||||
{
|
{
|
||||||
countryMapper.deleteByProductId(p.getProductId());
|
countryMapper.deleteByProductId(p.getProductId());
|
||||||
}
|
}
|
||||||
countryMapper.deleteByProductCodeAndEffectiveDate(p.getProductCode(), p.getEffectiveDate());
|
countryMapper.deleteByProductCodeAndEffectiveDate(p.getProductCode(), p.getEffectiveDate());
|
||||||
codesNoRemain.add(p.getProductCode());
|
codesNoRemain.add(p.getProductCode());
|
||||||
|
deletableIds.add(p.getProductId());
|
||||||
|
if (p.getEffectiveDate() != null)
|
||||||
|
{
|
||||||
|
toReopen.add(p);
|
||||||
}
|
}
|
||||||
int rows = productMapper.deleteBmsProductByIds(productIds);
|
}
|
||||||
|
if (!notPendingTips.isEmpty())
|
||||||
|
{
|
||||||
|
throw new ServiceException(MessageUtils.message("price.product.delete.pendingOnly",
|
||||||
|
String.join("\u3001", notPendingTips)));
|
||||||
|
}
|
||||||
|
if (deletableIds.isEmpty())
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int rows = productMapper.deleteBmsProductByIds(deletableIds.toArray(new Long[0]));
|
||||||
|
for (BmsProduct p : toReopen)
|
||||||
|
{
|
||||||
|
productMapper.reopenExpiredByProductCodeAndExpireDate(p.getProductCode(), p.getEffectiveDate());
|
||||||
|
countryMapper.reopenExpiredByProductCodeAndExpireDate(p.getProductCode(), p.getEffectiveDate());
|
||||||
|
}
|
||||||
|
productMapper.refreshStatusByDates();
|
||||||
|
countryMapper.refreshStatusByDates();
|
||||||
List<String> orphanCodes = new ArrayList<>();
|
List<String> orphanCodes = new ArrayList<>();
|
||||||
for (String code : codesNoRemain)
|
for (String code : codesNoRemain)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -171,6 +171,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
and (effective_date is null or effective_date < #{newEffectiveDate})
|
and (effective_date is null or effective_date < #{newEffectiveDate})
|
||||||
</update>
|
</update>
|
||||||
|
|
||||||
|
<update id="reopenExpiredByProductCodeAndExpireDate">
|
||||||
|
update product_country
|
||||||
|
set expire_date = null,
|
||||||
|
update_time = sysdate()
|
||||||
|
where product_code = #{productCode}
|
||||||
|
and expire_date = #{expireDate}
|
||||||
|
</update>
|
||||||
|
|
||||||
<update id="expireOpenByProductCodeAndCountry">
|
<update id="expireOpenByProductCodeAndCountry">
|
||||||
update product_country
|
update product_country
|
||||||
set expire_date = #{newEffectiveDate},
|
set expire_date = #{newEffectiveDate},
|
||||||
|
|||||||
@@ -207,6 +207,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
</if>
|
</if>
|
||||||
</update>
|
</update>
|
||||||
|
|
||||||
|
<update id="reopenExpiredByProductCodeAndExpireDate">
|
||||||
|
update product
|
||||||
|
set expire_date = null,
|
||||||
|
update_time = sysdate()
|
||||||
|
where product_code = #{productCode}
|
||||||
|
and expire_date = #{expireDate}
|
||||||
|
</update>
|
||||||
|
|
||||||
<update id="refreshStatusByDates">
|
<update id="refreshStatusByDates">
|
||||||
update product
|
update product
|
||||||
set status = case
|
set status = case
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
-- Add product batch-delete button permission (idempotent)
|
||||||
|
SET @menuId := (
|
||||||
|
SELECT menu_id FROM sys_menu
|
||||||
|
WHERE perms = 'price:product:list' OR component LIKE '%price/basic/product%'
|
||||||
|
ORDER BY menu_id ASC LIMIT 1
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time)
|
||||||
|
SELECT '产品删除', @menuId, 6, '', '', 1, 0, 'F', '0', '0', 'price:product:remove', '#', 'admin', sysdate()
|
||||||
|
WHERE @menuId IS NOT NULL
|
||||||
|
AND NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'price:product:remove');
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
:search-columns="searchColumns"
|
:search-columns="searchColumns"
|
||||||
:query="queryList"
|
:query="queryList"
|
||||||
row-key="productId"
|
row-key="productId"
|
||||||
|
:selection="true"
|
||||||
>
|
>
|
||||||
<template #toolbar="{ selection }">
|
<template #toolbar="{ selection }">
|
||||||
<el-col :span="1.5">
|
<el-col :span="1.5">
|
||||||
@@ -33,6 +34,16 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-dropdown>
|
</el-dropdown>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
<el-col :span="1.5">
|
||||||
|
<el-button
|
||||||
|
type="danger"
|
||||||
|
plain
|
||||||
|
icon="Delete"
|
||||||
|
:disabled="!selection.length"
|
||||||
|
@click="handleBatchDelete(selection)"
|
||||||
|
v-hasPermi="['price:product:remove']"
|
||||||
|
>批量删除</el-button>
|
||||||
|
</el-col>
|
||||||
<el-col :span="1.5">
|
<el-col :span="1.5">
|
||||||
<el-button plain icon="Clock" @click="expiredRef.open()" v-hasPermi="['price:product:list']">失效价格</el-button>
|
<el-button plain icon="Clock" @click="expiredRef.open()" v-hasPermi="['price:product:list']">失效价格</el-button>
|
||||||
</el-col>
|
</el-col>
|
||||||
@@ -152,9 +163,10 @@ import ProductExcelFileDrawer from './components/ProductExcelFileDrawer.vue'
|
|||||||
import ProductCountryDrawer from './components/ProductCountryDrawer.vue'
|
import ProductCountryDrawer from './components/ProductCountryDrawer.vue'
|
||||||
import ProductSurchargeDrawer from './components/ProductSurchargeDrawer.vue'
|
import ProductSurchargeDrawer from './components/ProductSurchargeDrawer.vue'
|
||||||
import ProductExpiredDrawer from './components/ProductExpiredDrawer.vue'
|
import ProductExpiredDrawer from './components/ProductExpiredDrawer.vue'
|
||||||
import { listProduct, importProductExcelAsync, importProductCountryExcelAsync } from '@/api/price/product'
|
import { listProduct, delProduct, importProductExcelAsync, importProductCountryExcelAsync } from '@/api/price/product'
|
||||||
import { listRegionOptions } from '@/api/data/region'
|
import { listRegionOptions } from '@/api/data/region'
|
||||||
|
|
||||||
|
const { proxy } = getCurrentInstance()
|
||||||
const { bms_product_effect_status } = useDict('bms_product_effect_status')
|
const { bms_product_effect_status } = useDict('bms_product_effect_status')
|
||||||
|
|
||||||
const extraParams = { statusGroup: 'active' }
|
const extraParams = { statusGroup: 'active' }
|
||||||
@@ -225,11 +237,24 @@ const columns = [
|
|||||||
|
|
||||||
const searchColumns = [
|
const searchColumns = [
|
||||||
{ prop: 'productCode', label: '产品代码', component: 'input', placeholder: '产品代码' },
|
{ prop: 'productCode', label: '产品代码', component: 'input', placeholder: '产品代码' },
|
||||||
{ prop: 'productName', label: '产品名称', component: 'input', placeholder: '产品名称' }
|
{ prop: 'productName', label: '产品名称', component: 'input', placeholder: '产品名称' },
|
||||||
|
{
|
||||||
|
prop: 'status',
|
||||||
|
label: '状态',
|
||||||
|
component: 'select',
|
||||||
|
dict: 'bms_product_effect_status',
|
||||||
|
placeholder: '请选择状态',
|
||||||
|
width: '160px'
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
function queryList(params) {
|
function queryList(params) {
|
||||||
return listProduct(params)
|
const p = { ...(params || {}) }
|
||||||
|
// Exact status search must not be limited by default statusGroup=active.
|
||||||
|
if (p.status != null && p.status !== '') {
|
||||||
|
delete p.statusGroup
|
||||||
|
}
|
||||||
|
return listProduct(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleAdd() {
|
function handleAdd() {
|
||||||
@@ -240,6 +265,48 @@ function handleUpdate(row) {
|
|||||||
formRef.value.open(row)
|
formRef.value.open(row)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isPendingProduct(row) {
|
||||||
|
if (!row) return false
|
||||||
|
if (String(row.status) === '0') return true
|
||||||
|
// fallback: effective in future and not expired
|
||||||
|
if (!row.effectiveDate) return false
|
||||||
|
const eff = new Date(String(row.effectiveDate).replace(/-/g, '/')).getTime()
|
||||||
|
if (Number.isNaN(eff) || eff <= Date.now()) return false
|
||||||
|
if (row.expireDate) {
|
||||||
|
const exp = new Date(String(row.expireDate).replace(/-/g, '/')).getTime()
|
||||||
|
if (!Number.isNaN(exp) && exp <= Date.now()) return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleBatchDelete(selection) {
|
||||||
|
const rows = (selection || []).filter(r => r && r.productId != null)
|
||||||
|
if (!rows.length) {
|
||||||
|
proxy.$modal.msgWarning('请先勾选产品')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const pending = rows.filter(isPendingProduct)
|
||||||
|
const blocked = rows.filter(r => !isPendingProduct(r))
|
||||||
|
if (!pending.length) {
|
||||||
|
proxy.$modal.msgWarning('仅待生效产品可删除,当前勾选无待生效记录')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (blocked.length) {
|
||||||
|
const tip = blocked.map(r => r.productCode || r.productId).join('、')
|
||||||
|
proxy.$modal.msgWarning('已跳过非待生效产品:' + tip)
|
||||||
|
}
|
||||||
|
const codes = pending.map(r => r.productCode || r.productId).join('、')
|
||||||
|
const ids = pending.map(r => r.productId)
|
||||||
|
proxy.$modal.confirm(
|
||||||
|
'确认删除已选待生效产品(' + pending.length + '个):'
|
||||||
|
+ codes
|
||||||
|
+ '?删除后产品及国家/重量段明细将一并清除,且不可恢复。'
|
||||||
|
).then(() => delProduct(ids)).then(() => {
|
||||||
|
tableRef.value.refresh()
|
||||||
|
proxy.$modal.msgSuccess('删除成功')
|
||||||
|
}).catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
function handleImportCommand(command) {
|
function handleImportCommand(command) {
|
||||||
if (command === 'country') {
|
if (command === 'country') {
|
||||||
handleCountryImport()
|
handleCountryImport()
|
||||||
|
|||||||
Reference in New Issue
Block a user