修改测算逻辑
This commit is contained in:
+14
-3
@@ -15,6 +15,7 @@ import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import com.ruoyi.common.annotation.Log;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
@@ -183,7 +184,17 @@ public class PriceSetController extends BaseController
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:set:edit')")
|
||||
/**
|
||||
* 版本明细搜索下拉选项:按版本去重后的 产品/国家/分区
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('price:set:list')")
|
||||
@GetMapping("/detail/options")
|
||||
public AjaxResult detailOptions(@RequestParam("psvId") Long psvId)
|
||||
{
|
||||
return success(priceSetService.selectDetailOptions(psvId));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:set:edit') or @ss.hasPermi('price:set:forceEdit')")
|
||||
@Log(title = "套餐明细", businessType = BusinessType.INSERT)
|
||||
@PostMapping("/detail")
|
||||
public AjaxResult addDetail(@RequestBody PrcPriceSetDetail detail)
|
||||
@@ -191,7 +202,7 @@ public class PriceSetController extends BaseController
|
||||
return toAjax(priceSetService.insertDetail(detail));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:set:edit')")
|
||||
@PreAuthorize("@ss.hasPermi('price:set:edit') or @ss.hasPermi('price:set:forceEdit')")
|
||||
@Log(title = "套餐明细", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/detail")
|
||||
public AjaxResult editDetail(@RequestBody PrcPriceSetDetail detail)
|
||||
@@ -199,7 +210,7 @@ public class PriceSetController extends BaseController
|
||||
return toAjax(priceSetService.updateDetail(detail));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:set:edit')")
|
||||
@PreAuthorize("@ss.hasPermi('price:set:edit') or @ss.hasPermi('price:set:forceEdit')")
|
||||
@Log(title = "套餐明细", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/detail/{psdIds}")
|
||||
public AjaxResult removeDetail(@PathVariable Long[] psdIds)
|
||||
|
||||
+11
@@ -80,6 +80,17 @@ public class ProductController extends BaseController
|
||||
return toAjax(productService.updateBmsProduct(product));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改产品国家明细行(重量段/运费/挂号费/尺寸/尾程/时效/生效失效日期)
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('price:product:edit')")
|
||||
@Log(title = "产品国家明细", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/country")
|
||||
public AjaxResult editCountry(@RequestBody BmsProductCountry country)
|
||||
{
|
||||
return toAjax(productService.updateBmsProductCountry(country));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:product:remove')")
|
||||
@Log(title = "产品列表", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{productIds}")
|
||||
|
||||
@@ -128,6 +128,7 @@ price.quote.requestBodyRequired=请求体为空
|
||||
price.quote.customerIdRequired=客户ID为空
|
||||
price.quote.customerCodeRequired=客户代码不能为空
|
||||
price.quote.customerCodeNotFound=客户代码不存在
|
||||
price.quote.customerDisabled=客户已停用,不可计费
|
||||
price.quote.productCodeRequired=产品代码为空
|
||||
price.quote.countryRequired=国家代码或国家名称为空
|
||||
price.quote.weightInvalid=重量必须大于0
|
||||
|
||||
+3
@@ -17,6 +17,9 @@ public interface BmsProductCountryMapper
|
||||
|
||||
public int insertBmsProductCountry(BmsProductCountry country);
|
||||
|
||||
/** 按明细行主键更新可编辑字段(重量段/运费/挂号费/尺寸/尾程/时效/生效失效日期) */
|
||||
public int updateBmsProductCountry(BmsProductCountry country);
|
||||
|
||||
public int deleteByProductId(Long productId);
|
||||
|
||||
public int deleteByProductCode(String productCode);
|
||||
|
||||
+3
@@ -16,6 +16,9 @@ public interface PrcPriceSetDetailMapper
|
||||
|
||||
public List<PrcPriceSetDetail> selectPrcPriceSetDetailList(PrcPriceSetDetail detail);
|
||||
|
||||
/** 版本内去重后的 产品/国家/维度组合行(用于明细搜索下拉选项) */
|
||||
public List<PrcPriceSetDetail> selectDetailOptionRows(@Param("psvId") Long psvId);
|
||||
|
||||
public int insertPrcPriceSetDetail(PrcPriceSetDetail detail);
|
||||
|
||||
public int updatePrcPriceSetDetail(PrcPriceSetDetail detail);
|
||||
|
||||
@@ -21,6 +21,11 @@ public interface IBmsProductService
|
||||
|
||||
public int updateBmsProduct(BmsProduct product);
|
||||
|
||||
/**
|
||||
* 修改产品国家明细行(重量段/运费/挂号费/尺寸/尾程/时效/生效失效日期),状态按日期重算
|
||||
*/
|
||||
public int updateBmsProductCountry(BmsProductCountry country);
|
||||
|
||||
public int deleteBmsProductByIds(Long[] productIds);
|
||||
|
||||
public List<BmsProductCountry> selectCountryListByProductCode(String productCode);
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.ruoyi.price.service;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.ruoyi.price.domain.PrcCustomerPriceSet;
|
||||
import com.ruoyi.price.domain.PrcPriceSet;
|
||||
import com.ruoyi.price.domain.PrcPriceSetCheckItem;
|
||||
@@ -55,6 +56,12 @@ public interface IPrcPriceSetService
|
||||
|
||||
public List<PrcPriceSetDetail> selectDetailList(PrcPriceSetDetail detail);
|
||||
|
||||
/**
|
||||
* 版本明细搜索下拉选项:按版本去重后的 产品/国家/分区 列表。
|
||||
* 返回 { products: [{code,name}], countries: [{code,name}], zones: [{code,name}] }
|
||||
*/
|
||||
public Map<String, Object> selectDetailOptions(Long psvId);
|
||||
|
||||
public int insertDetail(PrcPriceSetDetail detail);
|
||||
|
||||
public int updateDetail(PrcPriceSetDetail detail);
|
||||
|
||||
+16
@@ -136,6 +136,22 @@ public class BmsProductServiceImpl implements IBmsProductService
|
||||
return productMapper.updateBmsProduct(product);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int updateBmsProductCountry(BmsProductCountry country)
|
||||
{
|
||||
if (country == null || country.getDetailId() == null)
|
||||
{
|
||||
throw new ServiceException("明细行主键缺失,无法修改");
|
||||
}
|
||||
int rows = countryMapper.updateBmsProductCountry(country);
|
||||
if (rows > 0)
|
||||
{
|
||||
// 生效/失效日期可能变化,状态按日期重算
|
||||
countryMapper.refreshStatusByDates();
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int deleteBmsProductByIds(Long[] productIds)
|
||||
|
||||
+47
@@ -6,6 +6,7 @@ import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -636,6 +637,52 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> selectDetailOptions(Long psvId)
|
||||
{
|
||||
List<PrcPriceSetDetail> rows = detailMapper.selectDetailOptionRows(psvId);
|
||||
Map<String, Map<String, String>> products = new LinkedHashMap<String, Map<String, String>>();
|
||||
Map<String, Map<String, String>> countries = new LinkedHashMap<String, Map<String, String>>();
|
||||
Map<String, Map<String, String>> zones = new LinkedHashMap<String, Map<String, String>>();
|
||||
for (PrcPriceSetDetail d : rows)
|
||||
{
|
||||
if (d == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (StringUtils.isNotBlank(d.getProductCode()))
|
||||
{
|
||||
Map<String, String> item = new LinkedHashMap<String, String>();
|
||||
item.put("code", d.getProductCode());
|
||||
item.put("name", StringUtils.isNotBlank(d.getProductName()) ? d.getProductName() : d.getProductCode());
|
||||
products.putIfAbsent(d.getProductCode(), item);
|
||||
}
|
||||
String countryCode = StringUtils.isNotBlank(d.getCountryCode()) ? d.getCountryCode() : null;
|
||||
String countryName = StringUtils.isNotBlank(d.getCountryName()) ? d.getCountryName() : null;
|
||||
if (countryCode != null || countryName != null)
|
||||
{
|
||||
Map<String, String> item = new LinkedHashMap<String, String>();
|
||||
item.put("code", countryCode);
|
||||
item.put("name", countryName);
|
||||
countries.putIfAbsent(countryCode != null ? countryCode : countryName, item);
|
||||
}
|
||||
String zoneCode = StringUtils.isNotBlank(d.getZoneCode()) ? d.getZoneCode() : null;
|
||||
String zoneName = StringUtils.isNotBlank(d.getZoneName()) ? d.getZoneName() : null;
|
||||
if (zoneCode != null || zoneName != null)
|
||||
{
|
||||
Map<String, String> item = new LinkedHashMap<String, String>();
|
||||
item.put("code", zoneCode);
|
||||
item.put("name", zoneName);
|
||||
zones.putIfAbsent(zoneCode != null ? zoneCode : zoneName, item);
|
||||
}
|
||||
}
|
||||
Map<String, Object> result = new HashMap<String, Object>();
|
||||
result.put("products", new ArrayList<Map<String, String>>(products.values()));
|
||||
result.put("countries", new ArrayList<Map<String, String>>(countries.values()));
|
||||
result.put("zones", new ArrayList<Map<String, String>>(zones.values()));
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Attach version surcharge snapshots to a page of details (for drawer list). */
|
||||
private void fillDetailSurcharges(List<PrcPriceSetDetail> details)
|
||||
{
|
||||
|
||||
+5
@@ -354,6 +354,11 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
||||
throw new ServiceException(MessageUtils.message("price.quote.customerCodeNotFound"));
|
||||
}
|
||||
BmsCustomer hit = list.get(0);
|
||||
// is_enable: 0 normal, 1 disabled — stopped customers cannot quote/bill
|
||||
if ("1".equals(hit.getIsEnable()))
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.quote.customerDisabled"));
|
||||
}
|
||||
request.setCustomerId(hit.getCustomerId());
|
||||
if (StringUtils.isNotEmpty(hit.getCustomerCode()))
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@ import com.ruoyi.price.service.IPrcPriceSetService;
|
||||
|
||||
/**
|
||||
* Refresh prc_price_set_version status (draft/pending/effect/expired).
|
||||
* Startup + every 5 minutes so PENDING becomes EFFECT when effective_time is reached.
|
||||
* Startup + every 3 hours so PENDING becomes EFFECT when effective_time is reached.
|
||||
*/
|
||||
@Component
|
||||
public class BmsStatusRefreshTask implements ApplicationRunner
|
||||
@@ -27,7 +27,7 @@ public class BmsStatusRefreshTask implements ApplicationRunner
|
||||
refresh("startup");
|
||||
}
|
||||
|
||||
@Scheduled(cron = "0 0/5 * * * ?")
|
||||
@Scheduled(cron = "0 0 0/3 * * ?")
|
||||
public void scheduledRefresh()
|
||||
{
|
||||
refresh("scheduled");
|
||||
|
||||
+18
@@ -179,6 +179,9 @@ public class BmsRegionSyncServiceImpl implements IBmsRegionSyncService
|
||||
region.setCountryNameEn((String) name.get("common"));
|
||||
}
|
||||
|
||||
// ISO代码(先取,后续用于港/澳/台中文名归一)
|
||||
String cca2 = (String) country.get("cca2");
|
||||
|
||||
// 中文名 translations.zho.common(mledoze/countries 标准字段名为 zho)
|
||||
Map<String, Object> translations = (Map<String, Object>) country.get("translations");
|
||||
if (translations != null)
|
||||
@@ -192,6 +195,21 @@ public class BmsRegionSyncServiceImpl implements IBmsRegionSyncService
|
||||
// 注:取不到中文名时不再用英文名兜底,避免污染业务匹配。
|
||||
// 若确需兜底,可改为 region.setCountryNameCn(StringUtils.EMPTY);
|
||||
|
||||
// 中国香港/中国澳门/中国台湾:数据源中文名为繁体「台灣」等,强制归一为规范简体名,
|
||||
// 避免同步覆盖后页面显示不规范、且每次同步都被繁体名刷回。
|
||||
if ("HK".equals(cca2))
|
||||
{
|
||||
region.setCountryNameCn("中国香港");
|
||||
}
|
||||
else if ("MO".equals(cca2))
|
||||
{
|
||||
region.setCountryNameCn("中国澳门");
|
||||
}
|
||||
else if ("TW".equals(cca2))
|
||||
{
|
||||
region.setCountryNameCn("中国台湾");
|
||||
}
|
||||
|
||||
// ISO代码
|
||||
region.setCountryCode2((String) country.get("cca2"));
|
||||
region.setCountryCode3((String) country.get("cca3"));
|
||||
|
||||
@@ -93,6 +93,23 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
)
|
||||
</insert>
|
||||
|
||||
<!-- 仅服务于明细编辑弹窗:前端每次提交全量可编辑字段,
|
||||
重量上限/失效日期允许写 null(= 不封顶 / 长期),故不用 <if> 守卫 -->
|
||||
<update id="updateBmsProductCountry" parameterType="BmsProductCountry">
|
||||
update bms_product_country
|
||||
set weight_min = #{weightMin},
|
||||
weight_max = #{weightMax},
|
||||
freight_price = #{freightPrice},
|
||||
registration_fee = #{registrationFee},
|
||||
size_limit = #{sizeLimit},
|
||||
last_mile = #{lastMile},
|
||||
lead_time = #{leadTime},
|
||||
effective_date = #{effectiveDate},
|
||||
expire_date = #{expireDate},
|
||||
update_time = sysdate()
|
||||
where detail_id = #{detailId}
|
||||
</update>
|
||||
|
||||
<delete id="deleteByProductId" parameterType="Long">
|
||||
delete from bms_product_country where product_id = #{productId}
|
||||
</delete>
|
||||
|
||||
@@ -108,6 +108,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
select count(1) from prc_price_set_detail where psv_id = #{psvId}
|
||||
</select>
|
||||
|
||||
<select id="selectDetailOptionRows" resultMap="PrcPriceSetDetailResult">
|
||||
select distinct product_code, product_name, country_code, country_name, zone_code, zone_name
|
||||
from prc_price_set_detail
|
||||
where psv_id = #{psvId}
|
||||
order by product_code, country_name, zone_code
|
||||
</select>
|
||||
|
||||
<!-- FIXED: join active products + country by product_id only (avoid OR-join row explosion) -->
|
||||
<insert id="insertFixedDetailsFromLibrary">
|
||||
insert ignore into prc_price_set_detail (
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
-- price:set:forceEdit: edit any version status (design highest privilege)
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
|
||||
SELECT 2207, N'任意版本编辑', 2201, 6, '', NULL, '', '', 1, 0, 'F', '0', '0', 'price:set:forceEdit', '#', 'admin', sysdate(), N'设计最高权限:任意状态版本可编辑价格明细'
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 2207 OR perms = 'price:set:forceEdit');
|
||||
|
||||
INSERT INTO sys_role_menu (role_id, menu_id)
|
||||
SELECT 1, 2207 FROM DUAL
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_role_menu WHERE role_id = 1 AND menu_id = 2207);
|
||||
@@ -29,6 +29,15 @@ export function listProductCountries(productCode, effectiveDate, productId) {
|
||||
})
|
||||
}
|
||||
|
||||
/** 修改产品国家明细行(重量段/运费/挂号费/尺寸/尾程/时效/生效失效日期) */
|
||||
export function updateProductCountry(data) {
|
||||
return request({
|
||||
url: '/price/product/country',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
/** 新增产品 */
|
||||
export function addProduct(data) {
|
||||
return request({
|
||||
|
||||
@@ -165,6 +165,15 @@ export function listSetDetail(query) {
|
||||
})
|
||||
}
|
||||
|
||||
/** 版本明细搜索下拉选项:按版本去重后的 产品/国家/分区 */
|
||||
export function listSetDetailOptions(psvId) {
|
||||
return request({
|
||||
url: '/price/set/detail/options',
|
||||
method: 'get',
|
||||
params: { psvId }
|
||||
})
|
||||
}
|
||||
|
||||
export function addSetDetail(data) {
|
||||
return request({
|
||||
url: '/price/set/detail',
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
max-height="calc(100vh - 120px)"
|
||||
empty-text="暂无国家明细"
|
||||
:span-method="spanMethod"
|
||||
row-key="detailId"
|
||||
>
|
||||
<template #col-countryName="{ row }">
|
||||
<span class="pre-wrap">{{ row.countryName }} ({{ row.countryCode }})</span>
|
||||
@@ -21,15 +22,78 @@
|
||||
<template #col-lastMile="{ row }">
|
||||
<span class="pre-wrap">{{ row.lastMile }}</span>
|
||||
</template>
|
||||
<template #col-action="{ row }">
|
||||
<el-button link type="primary" icon="Edit" @click="openEdit(row)" v-hasPermi="['price:product:edit']">编辑</el-button>
|
||||
</template>
|
||||
</simple-table>
|
||||
|
||||
<!-- 编辑国家明细弹窗 -->
|
||||
<el-dialog v-model="editVisible" :title="editTitle" width="620px" append-to-body :close-on-click-modal="false">
|
||||
<div class="edit-context">国家 / 分区:<b>{{ editRow.countryName }} ({{ editRow.countryCode }})</b> · {{ editRow.zoneName || '无分区' }}</div>
|
||||
<el-form ref="editFormRef" :model="editForm" :rules="editRules" label-width="110px">
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="重量下限(KG)" prop="weightMin">
|
||||
<el-input-number v-model="editForm.weightMin" :min="0" :precision="3" :step="0.1" controls-position="right" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="重量上限(KG)" prop="weightMax">
|
||||
<el-input-number v-model="editForm.weightMax" :min="0" :precision="3" :step="0.1" controls-position="right" placeholder="留空为不封顶" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="运费(RMB/KG)" prop="freightPrice">
|
||||
<el-input-number v-model="editForm.freightPrice" :min="0" :precision="2" :step="1" controls-position="right" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="挂号费(RMB/票)" prop="registrationFee">
|
||||
<el-input-number v-model="editForm.registrationFee" :min="0" :precision="2" :step="1" controls-position="right" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="尾程" prop="lastMile">
|
||||
<el-input v-model="editForm.lastMile" placeholder="如 DHL Paket" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="时效" prop="leadTime">
|
||||
<el-input v-model="editForm.leadTime" placeholder="如 7-9工作日" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="生效日期" prop="effectiveDate">
|
||||
<el-date-picker v-model="editForm.effectiveDate" type="date" value-format="YYYY-MM-DD" placeholder="选择日期" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="失效日期" prop="expireDate">
|
||||
<el-date-picker v-model="editForm.expireDate" type="date" value-format="YYYY-MM-DD" placeholder="留空为长期" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="尺寸限制" prop="sizeLimit">
|
||||
<el-input v-model="editForm.sizeLimit" type="textarea" :rows="2" placeholder="如 最大尺寸 60x50x40cm" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="editVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="saving" @click="saveEdit">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import SimpleTable from '@/components/SimpleTable'
|
||||
import { listProductCountries } from '@/api/price/product'
|
||||
import { listProductCountries, updateProductCountry } from '@/api/price/product'
|
||||
import { formatWeightLabel } from '@/views/price/set/utils/priceSet'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const columns = [
|
||||
{ prop: 'countryName', label: '国家', width: 120, showOverflowTooltip: true },
|
||||
// { prop: 'countryCode', label: 'ISO2', width: 60, align: 'center' },
|
||||
@@ -50,7 +114,8 @@ const columns = [
|
||||
{ prop: 'leadTime', label: '时效', width: 100, showOverflowTooltip: true },
|
||||
{ prop: 'effectiveDate', label: '生效日期', width: 110, align: 'center' },
|
||||
{ prop: 'expireDate', label: '失效日期', width: 110, align: 'center', formatter: (row) => row.expireDate || '长期' },
|
||||
{ prop: 'status', label: '状态', width: 90, align: 'center', dict: 'bms_product_effect_status' }
|
||||
{ prop: 'status', label: '状态', width: 90, align: 'center', dict: 'bms_product_effect_status' },
|
||||
{ prop: 'action', label: '操作', width: 70, fixed: 'right', slot: 'action' }
|
||||
]
|
||||
|
||||
const visible = ref(false)
|
||||
@@ -59,6 +124,8 @@ const loading = ref(false)
|
||||
const list = ref([])
|
||||
const keyword = ref('')
|
||||
const status = ref('')
|
||||
// 当前产品行(用于编辑保存后重新加载明细)
|
||||
const currentProduct = ref(null)
|
||||
|
||||
const filteredList = computed(() => {
|
||||
const kw = keyword.value.trim().toLowerCase()
|
||||
@@ -78,13 +145,10 @@ function formatEffDate(v) {
|
||||
return String(v).slice(0, 10)
|
||||
}
|
||||
|
||||
function open(row) {
|
||||
title.value = row.productCode + ' · ' + row.productName + ' · 国家服务明细'
|
||||
visible.value = true
|
||||
function load() {
|
||||
if (!currentProduct.value) return
|
||||
const row = currentProduct.value
|
||||
loading.value = true
|
||||
list.value = []
|
||||
keyword.value = ''
|
||||
status.value = ''
|
||||
listProductCountries(row.productCode, formatEffDate(row.effectiveDate), row.productId).then(res => {
|
||||
list.value = (res.data || []).slice().sort((a, b) => {
|
||||
const c = String(a.countryCode || '').localeCompare(String(b.countryCode || ''))
|
||||
@@ -96,6 +160,84 @@ function open(row) {
|
||||
})
|
||||
}
|
||||
|
||||
function open(row) {
|
||||
title.value = row.productCode + ' · ' + row.productName + ' · 国家服务明细'
|
||||
currentProduct.value = row
|
||||
visible.value = true
|
||||
list.value = []
|
||||
keyword.value = ''
|
||||
status.value = ''
|
||||
load()
|
||||
}
|
||||
|
||||
/* ---------- 编辑明细 ---------- */
|
||||
const editVisible = ref(false)
|
||||
const editTitle = ref('编辑国家明细')
|
||||
const editFormRef = ref()
|
||||
const saving = ref(false)
|
||||
const editRow = ref({})
|
||||
const editForm = reactive({
|
||||
detailId: null,
|
||||
weightMin: null,
|
||||
weightMax: null,
|
||||
freightPrice: null,
|
||||
registrationFee: null,
|
||||
sizeLimit: '',
|
||||
lastMile: '',
|
||||
leadTime: '',
|
||||
effectiveDate: '',
|
||||
expireDate: ''
|
||||
})
|
||||
|
||||
const editRules = {
|
||||
weightMin: [{ required: true, message: '重量下限不能为空', trigger: 'blur' }],
|
||||
weightMax: [{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value != null && editForm.weightMin != null && Number(value) <= Number(editForm.weightMin)) {
|
||||
callback(new Error('上限需大于下限'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: 'blur'
|
||||
}],
|
||||
freightPrice: [{ required: true, message: '运费不能为空', trigger: 'blur' }],
|
||||
effectiveDate: [{ required: true, message: '生效日期不能为空', trigger: 'change' }]
|
||||
}
|
||||
|
||||
function openEdit(row) {
|
||||
editRow.value = row
|
||||
editTitle.value = `编辑明细 · ${row.countryName}${row.zoneName ? ' · ' + row.zoneName : ''} · ${formatWeightLabel(row.weightMin, row.weightMax) || ''}`
|
||||
Object.assign(editForm, {
|
||||
detailId: row.detailId,
|
||||
weightMin: row.weightMin ?? null,
|
||||
weightMax: row.weightMax ?? null,
|
||||
freightPrice: row.freightPrice ?? null,
|
||||
registrationFee: row.registrationFee ?? null,
|
||||
sizeLimit: row.sizeLimit || '',
|
||||
lastMile: row.lastMile || '',
|
||||
leadTime: row.leadTime || '',
|
||||
effectiveDate: row.effectiveDate ? String(row.effectiveDate).slice(0, 10) : '',
|
||||
expireDate: row.expireDate ? String(row.expireDate).slice(0, 10) : ''
|
||||
})
|
||||
editVisible.value = true
|
||||
nextTick(() => editFormRef.value?.clearValidate())
|
||||
}
|
||||
|
||||
function saveEdit() {
|
||||
editFormRef.value.validate(valid => {
|
||||
if (!valid) return
|
||||
saving.value = true
|
||||
updateProductCountry({ ...editForm, expireDate: editForm.expireDate || null }).then(() => {
|
||||
proxy.$modal.msgSuccess('修改成功')
|
||||
editVisible.value = false
|
||||
load()
|
||||
}).finally(() => {
|
||||
saving.value = false
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function rowGroupKey(row) {
|
||||
return String(row?.countryCode || '') + '|' + String(row?.zoneCode || '') + '|' + String(row?.countryName || '')
|
||||
}
|
||||
@@ -119,4 +261,12 @@ defineExpose({ open })
|
||||
<style scoped>
|
||||
.mb8 { margin-bottom: 8px; }
|
||||
.pre-wrap { white-space: pre-wrap; font-size: 12px; line-height: 1.4; }
|
||||
.edit-context {
|
||||
margin-bottom: 14px;
|
||||
padding: 8px 12px;
|
||||
background: var(--el-fill-color-light);
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
color: var(--el-text-color-regular);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -36,9 +36,9 @@
|
||||
</template>
|
||||
|
||||
<template #col-action="{ row }">
|
||||
<el-button link type="primary" icon="View" @click="countryRef.open(row)" v-hasPermi="['price:product:query']">产品明细</el-button>
|
||||
<el-button link type="primary" icon="Money" @click="surchargeRef.open(row)" v-hasPermi="['price:surcharge:query']">附加费</el-button>
|
||||
<el-button :disabled="isProductActive(row)" :title="isProductActive(row) ? '已生效不可编辑' : ''" link type="primary" icon="Edit" @click="handleUpdate(row)" v-hasPermi="['price:product:edit']">编辑</el-button>
|
||||
<el-button link type="primary" title="产品明细" icon="View" @click="countryRef.open(row)" v-hasPermi="['price:product:query']"></el-button>
|
||||
<el-button link type="primary" title="附加费" icon="Money" @click="surchargeRef.open(row)" v-hasPermi="['price:surcharge:query']"></el-button>
|
||||
<el-button title="编辑" link type="primary" icon="Edit" @click="handleUpdate(row)" v-hasPermi="['price:product:edit']"></el-button>
|
||||
</template>
|
||||
</basic-table>
|
||||
|
||||
@@ -114,10 +114,6 @@ function handleAdd() {
|
||||
}
|
||||
|
||||
function handleUpdate(row) {
|
||||
if (isProductActive(row)) {
|
||||
proxy.$modal.msgError('已生效的产品不可编辑')
|
||||
return
|
||||
}
|
||||
formRef.value.open(row)
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,6 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-alert type="info" :closable="false" show-icon class="mb12" :title="metaText" />
|
||||
|
||||
<div class="filters">
|
||||
<el-input v-model="query.keyword" clearable :placeholder="phKeyword" style="width: 180px" />
|
||||
<el-select v-model="query.taxMode" clearable :placeholder="phTax" style="width: 140px">
|
||||
@@ -148,11 +146,6 @@ const discCode = ref('')
|
||||
|
||||
const typeHint = computed(() => '· 当前:' + setTypeLabel(props.setType))
|
||||
|
||||
const metaText = computed(() => {
|
||||
const ver = props.versionNo || 'V?'
|
||||
return ver + ' · 数据已落库,编辑价格/附加费会立即写入数据库。'
|
||||
})
|
||||
|
||||
const productGroups = computed(() => {
|
||||
const map = new Map()
|
||||
for (const d of props.details) {
|
||||
|
||||
@@ -8,29 +8,47 @@
|
||||
>
|
||||
<div class="drawer-body" v-loading="loading">
|
||||
<div class="toolbar">
|
||||
<el-input
|
||||
<el-select
|
||||
v-model="query.productKeyword"
|
||||
filterable
|
||||
clearable
|
||||
:placeholder="phProduct"
|
||||
style="width: 160px"
|
||||
@keyup.enter="search"
|
||||
/>
|
||||
<el-input
|
||||
:loading="optionsLoading"
|
||||
style="width: 200px"
|
||||
>
|
||||
<el-option v-for="p in optionProducts" :key="p.code" :label="p.label" :value="p.code">
|
||||
<span class="opt-name">{{ p.name }}</span>
|
||||
<span class="opt-code">{{ p.code }}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
<el-select
|
||||
v-if="isFixed"
|
||||
v-model="query.countryKeyword"
|
||||
filterable
|
||||
clearable
|
||||
:placeholder="phCountry"
|
||||
style="width: 140px"
|
||||
@keyup.enter="search"
|
||||
/>
|
||||
<el-input
|
||||
:loading="optionsLoading"
|
||||
style="width: 160px"
|
||||
>
|
||||
<el-option v-for="c in optionCountries" :key="c.key" :label="c.label" :value="c.value">
|
||||
<span class="opt-name">{{ c.name }}</span>
|
||||
<span class="opt-code">{{ c.code }}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
<el-select
|
||||
v-if="isFixed"
|
||||
v-model="query.zoneKeyword"
|
||||
filterable
|
||||
clearable
|
||||
:placeholder="phZone"
|
||||
style="width: 140px"
|
||||
@keyup.enter="search"
|
||||
/>
|
||||
:loading="optionsLoading"
|
||||
style="width: 160px"
|
||||
>
|
||||
<el-option v-for="z in optionZones" :key="z.key" :label="z.label" :value="z.value">
|
||||
<span class="opt-name">{{ z.name }}</span>
|
||||
<span class="opt-code">{{ z.code }}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
<el-button type="primary" @click="search">查询</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</div>
|
||||
@@ -110,7 +128,7 @@
|
||||
<script setup name="SetVersionDetailDrawer">
|
||||
import SurchargeTags from '../SurchargeTags.vue'
|
||||
import { isFixedType } from '../../utils/priceSet'
|
||||
import { listSetDetail } from '@/api/price/set'
|
||||
import { listSetDetail, listSetDetailOptions } from '@/api/price/set'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: Boolean, default: false },
|
||||
@@ -132,7 +150,48 @@ const query = reactive({
|
||||
zoneKeyword: ''
|
||||
})
|
||||
|
||||
const phProduct = '产品代码/名称'
|
||||
// 下拉选项:当前版本内去重后的 产品/国家/分区
|
||||
const optionsLoading = ref(false)
|
||||
const optionProducts = ref([])
|
||||
const optionCountries = ref([])
|
||||
const optionZones = ref([])
|
||||
|
||||
function loadOptions() {
|
||||
const psvId = props.version && props.version.psvId
|
||||
if (!psvId) {
|
||||
optionProducts.value = []
|
||||
optionCountries.value = []
|
||||
optionZones.value = []
|
||||
return
|
||||
}
|
||||
optionsLoading.value = true
|
||||
listSetDetailOptions(psvId).then(res => {
|
||||
const data = res.data || {}
|
||||
optionProducts.value = (data.products || []).map(p => ({
|
||||
code: p.code,
|
||||
name: p.name || p.code,
|
||||
label: (p.name && p.name !== p.code) ? `${p.code} ${p.name}` : p.code
|
||||
}))
|
||||
optionCountries.value = (data.countries || []).map(c => ({
|
||||
key: c.code || c.name,
|
||||
code: c.code || '-',
|
||||
name: c.name || c.code,
|
||||
value: c.code || c.name, // 有代码按代码搜,否则按名称搜(后端 code/name 双 LIKE)
|
||||
label: (c.name && c.code) ? `${c.name} ${c.code}` : (c.name || c.code)
|
||||
}))
|
||||
optionZones.value = (data.zones || []).map(z => ({
|
||||
key: z.code || z.name,
|
||||
code: z.code || '-',
|
||||
name: z.name || z.code,
|
||||
value: z.code || z.name,
|
||||
label: (z.name && z.code) ? `${z.name} ${z.code}` : (z.name || z.code)
|
||||
}))
|
||||
}).finally(() => {
|
||||
optionsLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
const phProduct = '产品'
|
||||
const phCountry = '国家'
|
||||
const phZone = '分区'
|
||||
const colProduct = '产品'
|
||||
@@ -205,6 +264,7 @@ watch(
|
||||
query.productKeyword = ''
|
||||
query.countryKeyword = ''
|
||||
query.zoneKeyword = ''
|
||||
loadOptions()
|
||||
load()
|
||||
}
|
||||
}
|
||||
@@ -242,6 +302,13 @@ watch(
|
||||
padding: 0 6px;
|
||||
}
|
||||
.p-name { color: #303133; }
|
||||
.opt-name { margin-right: 8px; }
|
||||
.opt-code {
|
||||
float: right;
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 12px;
|
||||
font-family: Consolas, Monaco, monospace;
|
||||
}
|
||||
.hl { color: #1890ff; font-weight: 600; }
|
||||
.reg { color: #d48806; font-weight: 600; }
|
||||
.pct { color: #1890ff; font-weight: 600; }
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<el-table-column :label="colRows" width="100" align="center">
|
||||
<template #default="{ row }">{{ detailCount(row) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="colAction" width="320" align="center" fixed="right">
|
||||
<el-table-column :label="colAction" width="360" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="openDetails(row)">查看详情</el-button>
|
||||
<el-button
|
||||
@@ -38,6 +38,12 @@
|
||||
@click="emit('edit', row)"
|
||||
v-hasPermi="['price:set:edit']"
|
||||
>继续配置</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="warning"
|
||||
@click="emit('edit', row)"
|
||||
v-hasPermi="['price:set:forceEdit']"
|
||||
>编辑</el-button>
|
||||
<el-button
|
||||
v-if="isDraftVersion(row)"
|
||||
link
|
||||
@@ -99,8 +105,8 @@ function tabLabel(key) {
|
||||
all: '全部',
|
||||
'0': '草稿',
|
||||
'3': '待生效',
|
||||
'1': '生效',
|
||||
'2': '失效'
|
||||
'1': '已生效',
|
||||
'2': '已失效'
|
||||
}
|
||||
return labels[key] + ' (' + (counts.value[key] || 0) + ')'
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user