Enhance surcharge management by adding support for price type filtering in SurchargeController and related services. Introduce new methods for handling redline product codes in mappers and services. Update front-end API to accommodate price type parameters in surcharge queries. Modify database schema to include redline product and country tables for improved surcharge calculations.
This commit is contained in:
+173
@@ -0,0 +1,173 @@
|
||||
package com.bms.web.controller.price;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
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 org.springframework.web.multipart.MultipartFile;
|
||||
import com.bms.common.annotation.Log;
|
||||
import com.bms.common.core.controller.BaseController;
|
||||
import com.bms.common.core.domain.AjaxResult;
|
||||
import com.bms.common.core.page.TableDataInfo;
|
||||
import com.bms.common.enums.BusinessType;
|
||||
import com.bms.common.utils.MessageUtils;
|
||||
import com.bms.price.domain.BmsRedlineProduct;
|
||||
import com.bms.price.domain.BmsRedlineProductCountry;
|
||||
import com.bms.price.domain.BmsRedlineProductImportTask;
|
||||
import com.bms.price.domain.PrcPriceExcelFile;
|
||||
import com.bms.price.service.IBmsRedlineProductService;
|
||||
import com.bms.price.service.IPrcPriceExcelFileService;
|
||||
|
||||
/**
|
||||
* redline product list (async Excel import)
|
||||
*
|
||||
* @author bms
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/price/redline")
|
||||
public class RedlineProductController extends BaseController
|
||||
{
|
||||
@Autowired
|
||||
private IBmsRedlineProductService productService;
|
||||
|
||||
@Autowired
|
||||
private IPrcPriceExcelFileService priceExcelFileService;
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:redline:list')")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo list(BmsRedlineProduct product)
|
||||
{
|
||||
startPage();
|
||||
List<BmsRedlineProduct> list = productService.selectBmsRedlineProductList(product);
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
/** Imported quote Excel attachments for this product_code (via product.pef_id). */
|
||||
@PreAuthorize("@ss.hasPermi('price:redline:query')")
|
||||
@GetMapping("/{productCode}/excelFiles")
|
||||
public AjaxResult excelFiles(@PathVariable String productCode)
|
||||
{
|
||||
List<PrcPriceExcelFile> list = priceExcelFileService.selectByRedlineProductCode(productCode);
|
||||
return success(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 产品下拉选项(非分页)。
|
||||
* 供计费列表、客户绑定等场景选择产品;不做权限控制(仍需登录)。
|
||||
*/
|
||||
@GetMapping("/optionselect")
|
||||
public AjaxResult optionselect(BmsRedlineProduct product)
|
||||
{
|
||||
List<BmsRedlineProduct> list = productService.selectBmsRedlineProductList(product);
|
||||
return success(list);
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:redline:import')")
|
||||
@GetMapping("/import/task/{taskId}")
|
||||
public AjaxResult getImportTask(@PathVariable String taskId)
|
||||
{
|
||||
BmsRedlineProductImportTask task = productService.getImportTask(taskId);
|
||||
if (task == null)
|
||||
{
|
||||
return error(MessageUtils.message("price.product.import.task.notExists"));
|
||||
}
|
||||
return success(task);
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:redline:query')")
|
||||
@GetMapping("/{productCode}/countries")
|
||||
public AjaxResult countries(@PathVariable String productCode,
|
||||
@RequestParam(required = false) Date effectiveDate,
|
||||
@RequestParam(required = false) Long productId)
|
||||
{
|
||||
List<BmsRedlineProductCountry> list = productService.selectCountryListByProductCode(productCode, effectiveDate, productId);
|
||||
return success(list);
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:redline:query')")
|
||||
@GetMapping("/{productId}")
|
||||
public AjaxResult getInfo(@PathVariable Long productId)
|
||||
{
|
||||
return success(productService.selectBmsRedlineProductById(productId));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:redline:add')")
|
||||
@Log(title = "\u4ea7\u54c1\u5217\u8868", businessType = BusinessType.INSERT)
|
||||
@PostMapping
|
||||
public AjaxResult add(@Validated @RequestBody BmsRedlineProduct product)
|
||||
{
|
||||
product.setCreateBy(getUsername());
|
||||
return toAjax(productService.insertBmsRedlineProduct(product));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:redline:edit')")
|
||||
@Log(title = "\u4ea7\u54c1\u5217\u8868", businessType = BusinessType.UPDATE)
|
||||
@PutMapping
|
||||
public AjaxResult edit(@Validated @RequestBody BmsRedlineProduct product)
|
||||
{
|
||||
product.setUpdateBy(getUsername());
|
||||
return toAjax(productService.updateBmsRedlineProduct(product));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:redline:edit')")
|
||||
@Log(title = "\u4ea7\u54c1\u56fd\u5bb6\u660e\u7ec6", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/country")
|
||||
public AjaxResult editCountry(@RequestBody BmsRedlineProductCountry country)
|
||||
{
|
||||
return toAjax(productService.updateBmsRedlineProductCountry(country));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:redline:remove')")
|
||||
@Log(title = "\u4ea7\u54c1\u5217\u8868", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{productIds}")
|
||||
public AjaxResult remove(@PathVariable Long[] productIds)
|
||||
{
|
||||
return toAjax(productService.deleteBmsRedlineProductByIds(productIds));
|
||||
}
|
||||
|
||||
/**
|
||||
* Async import Weishi B2C workbook. Returns taskId; poll /import/task/{taskId}.
|
||||
* effectiveDate is required: written to product / product_country; previous open rows expire at this date.
|
||||
*/
|
||||
@Log(title = "\u4ea7\u54c1\u5217\u8868", businessType = BusinessType.IMPORT)
|
||||
@PreAuthorize("@ss.hasPermi('price:redline:import')")
|
||||
@PostMapping("/importData")
|
||||
public AjaxResult importData(MultipartFile file, boolean updateSupport,
|
||||
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date effectiveDate) throws Exception
|
||||
{
|
||||
String taskId = productService.submitImportProductExcelAsync(file, updateSupport, getUsername(), effectiveDate);
|
||||
Map<String, Object> payload = new HashMap<String, Object>();
|
||||
payload.put("taskId", taskId);
|
||||
return success(payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Async single-country cost-sheet import. Returns taskId; poll /import/task/{taskId}.
|
||||
* effectiveDate is required: written to product / product_country; previous open rows expire at this date.
|
||||
*/
|
||||
@Log(title = "\u4ea7\u54c1\u5217\u8868", businessType = BusinessType.IMPORT)
|
||||
@PreAuthorize("@ss.hasPermi('price:redline:import')")
|
||||
@PostMapping("/importCountryData")
|
||||
public AjaxResult importCountryData(MultipartFile file, String countryCode,
|
||||
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date effectiveDate) throws Exception
|
||||
{
|
||||
String taskId = productService.submitImportCountryProductExcelAsync(file, countryCode, getUsername(),
|
||||
effectiveDate);
|
||||
Map<String, Object> payload = new HashMap<String, Object>();
|
||||
payload.put("taskId", taskId);
|
||||
return success(payload);
|
||||
}
|
||||
}
|
||||
+4
-2
@@ -11,6 +11,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.bms.common.annotation.Log;
|
||||
import com.bms.common.core.controller.BaseController;
|
||||
@@ -58,9 +59,10 @@ public class SurchargeController extends BaseController
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('price:surcharge:query') or @ss.hasPermi('price:set:query') or @ss.hasPermi('price:set:add')")
|
||||
@GetMapping("/byProduct/{productCode}")
|
||||
public AjaxResult listByProduct(@PathVariable String productCode)
|
||||
public AjaxResult listByProduct(@PathVariable String productCode,
|
||||
@RequestParam(required = false) String priceType)
|
||||
{
|
||||
return success(surchargeRelService.selectRelByProductCode(productCode));
|
||||
return success(surchargeRelService.selectRelByProductCode(productCode, priceType));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.bms.price.domain;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.bms.common.core.domain.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/** Redline catalog main table redline_product */
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class BmsRedlineProduct extends BaseEntity
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long productId;
|
||||
private String productCode;
|
||||
private String productName;
|
||||
private String serviceLevel;
|
||||
private String cargoAttr;
|
||||
private String destRegion;
|
||||
private String updateNote;
|
||||
/** TAX_INCLUDED / TAX_EXCLUDED */
|
||||
private String taxMode;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date effectiveDate;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date expireDate;
|
||||
/** 0 pending, 1 active, 2 expired */
|
||||
public static final String STATUS_PENDING = "0";
|
||||
public static final String STATUS_ACTIVE = "1";
|
||||
public static final String STATUS_EXPIRED = "2";
|
||||
private String status;
|
||||
private String sourceSheet;
|
||||
/** FK prc_price_excel_file.pef_id */
|
||||
private Long pefId;
|
||||
private Long tmsId;
|
||||
private Integer countryCount;
|
||||
private Integer surchargeCount;
|
||||
private List<BmsRedlineProductCountry> countries;
|
||||
/** query alias of status (0/1/2) */
|
||||
private String effectStatus;
|
||||
/** query: active = status in (0,1); expired = status 2 */
|
||||
private String statusGroup;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.bms.price.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
/** Redline country detail table redline_product_country */
|
||||
@Data
|
||||
public class BmsRedlineProductCountry implements Serializable
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long detailId;
|
||||
/** FK to product.product_id */
|
||||
private Long productId;
|
||||
private String productCode;
|
||||
private String countryName;
|
||||
private String countryCode;
|
||||
/** prc_zone.zone_id; null when product country has no zone */
|
||||
private Long zoneId;
|
||||
/** e.g. Z1; null when no zone */
|
||||
private String zoneCode;
|
||||
/** e.g. Australia(Z1); null when no zone */
|
||||
private String zoneName;
|
||||
private BigDecimal weightMin;
|
||||
private BigDecimal weightMax;
|
||||
private BigDecimal freightPrice;
|
||||
private BigDecimal registrationFee;
|
||||
private String sizeLimit;
|
||||
private String lastMile;
|
||||
private String leadTime;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date effectiveDate;
|
||||
/** null=open; closed to next import effective_date */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date expireDate;
|
||||
/** 0 pending, 1 active, 2 expired */
|
||||
private String status;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date updateTime;
|
||||
/** query only: true = expire_date is null */
|
||||
private Boolean openOnly;
|
||||
/** query alias of status (0/1/2) */
|
||||
private String effectStatus;
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
package com.bms.price.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Async product Excel import progress (Redis).
|
||||
*/
|
||||
public class BmsRedlineProductImportTask implements Serializable
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public static final String STATUS_PENDING = "PENDING";
|
||||
public static final String STATUS_RUNNING = "RUNNING";
|
||||
public static final String STATUS_SUCCESS = "SUCCESS";
|
||||
public static final String STATUS_FAIL = "FAIL";
|
||||
|
||||
private String taskId;
|
||||
private String status;
|
||||
private int percent;
|
||||
private String message;
|
||||
private String error;
|
||||
|
||||
public String getTaskId()
|
||||
{
|
||||
return taskId;
|
||||
}
|
||||
|
||||
public void setTaskId(String taskId)
|
||||
{
|
||||
this.taskId = taskId;
|
||||
}
|
||||
|
||||
public String getStatus()
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status)
|
||||
{
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public int getPercent()
|
||||
{
|
||||
return percent;
|
||||
}
|
||||
|
||||
public void setPercent(int percent)
|
||||
{
|
||||
this.percent = percent;
|
||||
}
|
||||
|
||||
public String getMessage()
|
||||
{
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message)
|
||||
{
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getError()
|
||||
{
|
||||
return error;
|
||||
}
|
||||
|
||||
public void setError(String error)
|
||||
{
|
||||
this.error = error;
|
||||
}
|
||||
}
|
||||
@@ -58,8 +58,10 @@ public class TmsPriceQuoteResult implements Serializable
|
||||
private BigDecimal surchargeAmount;
|
||||
/** product catalog total: base freight*weight + base registration (no package surcharge) */
|
||||
private BigDecimal productAmount;
|
||||
/** redline total reserved */
|
||||
/** redline total from redline_product_country (+ REDLINE surcharges) */
|
||||
private BigDecimal redlineAmount;
|
||||
/** matched redline_product_country.detail_id for REDLINE surcharge scope */
|
||||
private Long redlineCountryId;
|
||||
/** cost total (currently = product catalog amount) */
|
||||
private BigDecimal costAmount;
|
||||
/** receivable total (order bill) */
|
||||
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
package com.bms.price.mapper;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import com.bms.price.domain.BmsRedlineProductCountry;
|
||||
|
||||
/**
|
||||
* 红线价国家明细 数据层
|
||||
*
|
||||
* @author bms
|
||||
*/
|
||||
public interface BmsRedlineProductCountryMapper
|
||||
{
|
||||
public List<BmsRedlineProductCountry> selectBmsRedlineProductCountryList(BmsRedlineProductCountry country);
|
||||
|
||||
public int insertBmsRedlineProductCountry(BmsRedlineProductCountry country);
|
||||
|
||||
/** 按明细行主键更新可编辑字段(重量段/运费/挂号费/尺寸/尾程/时效/生效失效日期) */
|
||||
public int updateBmsRedlineProductCountry(BmsRedlineProductCountry country);
|
||||
|
||||
/** Import overlay: update price/zone/attrs on matched country row */
|
||||
public int updateBmsRedlineProductCountryImport(BmsRedlineProductCountry country);
|
||||
|
||||
public int deleteByProductId(Long productId);
|
||||
|
||||
public int deleteByProductCode(String productCode);
|
||||
|
||||
public int deleteByProductCodeAndEffectiveDate(
|
||||
@Param("productCode") String productCode,
|
||||
@Param("effectiveDate") Date effectiveDate);
|
||||
|
||||
/** Max open (expire_date is null) effective_date for product */
|
||||
public Date selectMaxOpenEffectiveDate(@Param("productCode") String productCode);
|
||||
|
||||
/** Close open versions: set expire_date = newEffectiveDate where effective_date < newEffectiveDate */
|
||||
public int expireOpenByProductCode(
|
||||
@Param("productCode") String productCode,
|
||||
@Param("newEffectiveDate") Date newEffectiveDate);
|
||||
|
||||
/**
|
||||
* Close open rows for one product + country where effective_date < newEffectiveDate.
|
||||
* Match by country_code when present, otherwise country_name.
|
||||
*/
|
||||
public int expireOpenByProductCodeAndCountry(
|
||||
@Param("productCode") String productCode,
|
||||
@Param("countryCode") String countryCode,
|
||||
@Param("countryName") String countryName,
|
||||
@Param("newEffectiveDate") Date newEffectiveDate);
|
||||
|
||||
/** Recompute status from effective_date / expire_date vs today */
|
||||
public int refreshStatusByDates();
|
||||
|
||||
/** Replace same-day version only */
|
||||
public int deleteOpenByProductCodeAndEffectiveDate(
|
||||
@Param("productCode") String productCode,
|
||||
@Param("effectiveDate") Date effectiveDate);
|
||||
|
||||
/**
|
||||
* Delete open rows for one product + country on a given effective_date (same-day replace).
|
||||
* Needed because unique key keeps expired same-day weight bands.
|
||||
*/
|
||||
public int deleteOpenByProductCodeAndCountryOnDate(
|
||||
@Param("productCode") String productCode,
|
||||
@Param("countryCode") String countryCode,
|
||||
@Param("countryName") String countryName,
|
||||
@Param("effectiveDate") Date effectiveDate);
|
||||
|
||||
/** Open country rows (expire_date is null) for product */
|
||||
public List<BmsRedlineProductCountry> selectOpenByProductCode(@Param("productCode") String productCode);
|
||||
|
||||
/** Copy previously open rows into a new effective_date (caller must expire first) */
|
||||
public int copyOpenCountries(
|
||||
@Param("productCode") String productCode,
|
||||
@Param("productId") Long productId,
|
||||
@Param("effectiveDate") Date effectiveDate,
|
||||
@Param("status") String status);
|
||||
|
||||
/**
|
||||
* Match by product + country + weight + bizDate (+ optional zoneCode).
|
||||
* When zoneCode is empty, prefer rows without zone.
|
||||
* When zoneCode is set, require matching zone_code.
|
||||
*/
|
||||
public BmsRedlineProductCountry selectMatchByProductCountryWeight(
|
||||
@Param("productCode") String productCode,
|
||||
@Param("countryCode") String countryCode,
|
||||
@Param("countryName") String countryName,
|
||||
@Param("zoneCode") String zoneCode,
|
||||
@Param("weight") BigDecimal weight,
|
||||
@Param("bizDate") Date bizDate);
|
||||
|
||||
/** Count product-country rows that have a zone for this product+country+bizDate */
|
||||
public int countZonedByProductCountry(
|
||||
@Param("productCode") String productCode,
|
||||
@Param("countryCode") String countryCode,
|
||||
@Param("countryName") String countryName,
|
||||
@Param("bizDate") Date bizDate);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.bms.price.mapper;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import com.bms.price.domain.BmsRedlineProduct;
|
||||
|
||||
/**
|
||||
* 产品主表 数据层
|
||||
*
|
||||
* @author bms
|
||||
*/
|
||||
public interface BmsRedlineProductMapper
|
||||
{
|
||||
public BmsRedlineProduct selectBmsRedlineProductById(Long productId);
|
||||
|
||||
/** Latest row by product_code (effective_date desc) */
|
||||
public BmsRedlineProduct selectBmsRedlineProductByCode(String productCode);
|
||||
|
||||
/** Unique business key: product_code + effective_date */
|
||||
public BmsRedlineProduct selectBmsRedlineProductByCodeAndDate(@Param("productCode") String productCode,
|
||||
@Param("effectiveDate") Date effectiveDate);
|
||||
|
||||
/** Product version effective on bizDate */
|
||||
public BmsRedlineProduct selectEffectiveBmsRedlineProductByCode(@Param("productCode") String productCode,
|
||||
@Param("bizDate") Date bizDate);
|
||||
|
||||
public List<BmsRedlineProduct> selectBmsRedlineProductList(BmsRedlineProduct product);
|
||||
|
||||
public int insertBmsRedlineProduct(BmsRedlineProduct product);
|
||||
|
||||
public int updateBmsRedlineProduct(BmsRedlineProduct product);
|
||||
|
||||
public int deleteBmsRedlineProductByIds(Long[] productIds);
|
||||
|
||||
/** Close previous open versions: expire_date = newEffectiveDate */
|
||||
public int expireOpenByProductCode(
|
||||
@Param("productCode") String productCode,
|
||||
@Param("newEffectiveDate") Date newEffectiveDate,
|
||||
@Param("excludeId") Long excludeId);
|
||||
|
||||
/** Recompute status from effective_date / expire_date vs today */
|
||||
public int refreshStatusByDates();
|
||||
}
|
||||
@@ -16,6 +16,8 @@ public interface PrcPriceExcelFileMapper
|
||||
/** Distinct PRODUCT excel files linked by product.pef_id for this product_code */
|
||||
public List<PrcPriceExcelFile> selectByProductCode(@Param("productCode") String productCode);
|
||||
|
||||
public List<PrcPriceExcelFile> selectByRedlineProductCode(@Param("productCode") String productCode);
|
||||
|
||||
public int insertPrcPriceExcelFile(PrcPriceExcelFile row);
|
||||
|
||||
public int updatePrcPriceExcelFile(PrcPriceExcelFile row);
|
||||
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package com.bms.price.service;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import com.bms.price.domain.BmsRedlineProduct;
|
||||
import com.bms.price.domain.BmsRedlineProductCountry;
|
||||
import com.bms.price.domain.BmsRedlineProductImportTask;
|
||||
|
||||
/**
|
||||
* redline product list service
|
||||
*
|
||||
* @author bms
|
||||
*/
|
||||
public interface IBmsRedlineProductService
|
||||
{
|
||||
public BmsRedlineProduct selectBmsRedlineProductById(Long productId);
|
||||
|
||||
public List<BmsRedlineProduct> selectBmsRedlineProductList(BmsRedlineProduct product);
|
||||
|
||||
public int insertBmsRedlineProduct(BmsRedlineProduct product);
|
||||
|
||||
public int updateBmsRedlineProduct(BmsRedlineProduct product);
|
||||
|
||||
public int updateBmsRedlineProductCountry(BmsRedlineProductCountry country);
|
||||
|
||||
public int deleteBmsRedlineProductByIds(Long[] productIds);
|
||||
|
||||
public List<BmsRedlineProductCountry> selectCountryListByProductCode(String productCode);
|
||||
|
||||
public List<BmsRedlineProductCountry> selectCountryListByProductCode(String productCode, Date effectiveDate);
|
||||
|
||||
public List<BmsRedlineProductCountry> selectCountryListByProductCode(String productCode, Date effectiveDate, Long productId);
|
||||
|
||||
public String importProductExcel(MultipartFile file, boolean updateSupport, String operName, Date effectiveDate) throws Exception;
|
||||
|
||||
public String importCountryProductExcel(MultipartFile file, String countryCode, String operName, Date effectiveDate) throws Exception;
|
||||
|
||||
/** Submit async all-country import; returns taskId for polling getImportTask. */
|
||||
public String submitImportProductExcelAsync(MultipartFile file, boolean updateSupport, String operName, Date effectiveDate) throws Exception;
|
||||
|
||||
/** Submit async single-country import; returns taskId for polling getImportTask. */
|
||||
public String submitImportCountryProductExcelAsync(MultipartFile file, String countryCode, String operName, Date effectiveDate) throws Exception;
|
||||
|
||||
/** Poll Redis progress for product import task. */
|
||||
public BmsRedlineProductImportTask getImportTask(String taskId);
|
||||
|
||||
/** Sync import from bytes (used by async runner; taskId may be null). */
|
||||
public String importProductExcel(byte[] bytes, String filename, boolean updateSupport, String operName, String taskId, Date effectiveDate) throws Exception;
|
||||
|
||||
/** Sync country import from bytes (used by async runner; taskId may be null). */
|
||||
public String importCountryProductExcel(byte[] bytes, String filename, String countryCode, String operName, String taskId, Date effectiveDate) throws Exception;
|
||||
|
||||
public java.util.Map<String, List<BmsRedlineProductCountry>> parseQuoteExcelByProduct(MultipartFile file) throws Exception;
|
||||
|
||||
public java.util.Map<String, List<BmsRedlineProductCountry>> parseQuoteExcelByProduct(byte[] bytes, String filename) throws Exception;
|
||||
}
|
||||
@@ -14,6 +14,8 @@ public interface IPrcPriceExcelFileService
|
||||
/** Import Excel attachments linked to this product_code via product.pef_id */
|
||||
public List<PrcPriceExcelFile> selectByProductCode(String productCode);
|
||||
|
||||
List<PrcPriceExcelFile> selectByRedlineProductCode(String productCode);
|
||||
|
||||
/**
|
||||
* Persist uploaded Excel under upload/price/excel and insert metadata row.
|
||||
*
|
||||
|
||||
@@ -13,6 +13,8 @@ public interface IPrcSurchargeRelService
|
||||
/** 查询某产品已关联的附加费(含附加费与明细展示字段) */
|
||||
public List<PrcSurchargeRel> selectRelByProductCode(String productCode);
|
||||
|
||||
public List<PrcSurchargeRel> selectRelByProductCode(String productCode, String priceType);
|
||||
|
||||
public List<PrcSurchargeRel> selectPrcSurchargeRelList(PrcSurchargeRel rel);
|
||||
|
||||
public int insertPrcSurchargeRel(PrcSurchargeRel rel);
|
||||
|
||||
+2290
File diff suppressed because it is too large
Load Diff
+11
@@ -48,6 +48,17 @@ public class PrcPriceExcelFileServiceImpl implements IPrcPriceExcelFileService
|
||||
return list == null ? Collections.emptyList() : list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PrcPriceExcelFile> selectByRedlineProductCode(String productCode)
|
||||
{
|
||||
if (productCode == null || productCode.trim().isEmpty())
|
||||
{
|
||||
return java.util.Collections.emptyList();
|
||||
}
|
||||
List<PrcPriceExcelFile> list = excelFileMapper.selectByRedlineProductCode(productCode.trim());
|
||||
return list == null ? java.util.Collections.emptyList() : list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrcPriceExcelFile saveImportExcel(String excelType, Date effectiveTime, byte[] bytes,
|
||||
String filename, String operName) throws Exception
|
||||
|
||||
+9
-19
@@ -23,30 +23,20 @@ public class PrcSurchargeRelServiceImpl implements IPrcSurchargeRelService
|
||||
|
||||
@Override
|
||||
public List<PrcSurchargeRel> selectRelByProductCode(String productCode)
|
||||
{
|
||||
return selectRelByProductCode(productCode, PrcSurchargeRel.PRICE_TYPE_PRODUCT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PrcSurchargeRel> selectRelByProductCode(String productCode, String priceType)
|
||||
{
|
||||
PrcSurchargeRel query = new PrcSurchargeRel();
|
||||
query.setProductCode(productCode);
|
||||
List<PrcSurchargeRel> list = relMapper.selectPrcSurchargeRelList(query);
|
||||
if (list == null || list.isEmpty())
|
||||
if (StringUtils.isNotEmpty(priceType))
|
||||
{
|
||||
return list;
|
||||
query.setPriceType(priceType.trim().toUpperCase());
|
||||
}
|
||||
// Product drawer: PRODUCT + REDLINE only (SALES binds psp_id/psd_id)
|
||||
List<PrcSurchargeRel> filtered = new java.util.ArrayList<PrcSurchargeRel>();
|
||||
for (PrcSurchargeRel rel : list)
|
||||
{
|
||||
if (rel == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
String type = rel.getPriceType();
|
||||
if (StringUtils.isEmpty(type) || PrcSurchargeRel.PRICE_TYPE_PRODUCT.equals(type)
|
||||
|| PrcSurchargeRel.PRICE_TYPE_REDLINE.equals(type))
|
||||
{
|
||||
filtered.add(rel);
|
||||
}
|
||||
}
|
||||
return filtered;
|
||||
return relMapper.selectPrcSurchargeRelList(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+35
@@ -25,6 +25,7 @@ import com.bms.customer.customerinfo.domain.BmsCustomer;
|
||||
import com.bms.customer.customerinfo.mapper.BmsCustomerMapper;
|
||||
import com.bms.price.domain.BmsProduct;
|
||||
import com.bms.price.domain.BmsProductCountry;
|
||||
import com.bms.price.domain.BmsRedlineProductCountry;
|
||||
import com.bms.price.domain.PrcCustomerPriceSet;
|
||||
import com.bms.price.domain.PrcPriceSet;
|
||||
import com.bms.price.domain.PrcPriceSetDetail;
|
||||
@@ -42,6 +43,7 @@ import com.bms.price.domain.vo.TmsPriceQuoteResult;
|
||||
import com.bms.price.enums.BillCustomerActiveStatus;
|
||||
import com.bms.price.mapper.BmsProductCountryMapper;
|
||||
import com.bms.price.mapper.BmsProductMapper;
|
||||
import com.bms.price.mapper.BmsRedlineProductCountryMapper;
|
||||
import com.bms.price.mapper.PrcCustomerPriceSetMapper;
|
||||
import com.bms.price.mapper.PrcPriceSetDetailMapper;
|
||||
import com.bms.price.mapper.PrcPriceSetMapper;
|
||||
@@ -94,6 +96,9 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
||||
@Autowired
|
||||
private BmsProductCountryMapper productCountryMapper;
|
||||
|
||||
@Autowired
|
||||
private BmsRedlineProductCountryMapper redlineProductCountryMapper;
|
||||
|
||||
@Autowired
|
||||
private BmsProductMapper productMapper;
|
||||
|
||||
@@ -874,6 +879,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
||||
r.setRoundMode(mode);
|
||||
r.setFromCache(false);
|
||||
fillProductAmountAndPay(r, catalog, weight, rm, mode);
|
||||
fillRedlineAmount(r, weight, rm);
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -929,6 +935,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
||||
r.setRoundMode(mode);
|
||||
r.setFromCache(false);
|
||||
fillProductAmountAndPay(r, base, weight, rm, mode);
|
||||
fillRedlineAmount(r, weight, rm);
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -961,6 +968,30 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
||||
r.setPayFeeDetails(payLines);
|
||||
}
|
||||
|
||||
/**
|
||||
* Redline catalog amount from redline_product_country (same match keys as product catalog).
|
||||
*/
|
||||
private void fillRedlineAmount(TmsPriceQuoteResult r, BigDecimal weight, RoundingMode rm)
|
||||
{
|
||||
if (r == null || weight == null || StringUtils.isEmpty(r.getProductCode()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
BmsRedlineProductCountry base = redlineProductCountryMapper.selectMatchByProductCountryWeight(
|
||||
r.getProductCode(), r.getCountryCode(), r.getCountryName(), r.getZoneCode(), weight, null);
|
||||
if (base == null || base.getFreightPrice() == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (base.getDetailId() != null)
|
||||
{
|
||||
r.setRedlineCountryId(base.getDetailId());
|
||||
}
|
||||
BigDecimal baseReg = base.getRegistrationFee() == null ? BigDecimal.ZERO : base.getRegistrationFee();
|
||||
BigDecimal freight = base.getFreightPrice().multiply(weight).setScale(2, rm);
|
||||
r.setRedlineAmount(freight.add(baseReg).setScale(2, rm));
|
||||
}
|
||||
|
||||
private void applySurchargesAndFees(TmsPriceQuoteResult result)
|
||||
{
|
||||
BigDecimal freightAmount = nvl(result.getFreightAmount());
|
||||
@@ -1140,6 +1171,10 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (PrcSurchargeRel.PRICE_TYPE_REDLINE.equals(rel.getPriceType()))
|
||||
{
|
||||
return result.getRedlineCountryId() != null && rel.getDetailId().equals(result.getRedlineCountryId());
|
||||
}
|
||||
return result.getProductCountryId() != null && rel.getDetailId().equals(result.getProductCountryId());
|
||||
}
|
||||
|
||||
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
package com.bms.price.task;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
import com.bms.common.core.redis.RedisCache;
|
||||
import com.bms.common.utils.StringUtils;
|
||||
import com.bms.price.domain.BmsRedlineProductImportTask;
|
||||
import com.bms.price.service.IBmsRedlineProductService;
|
||||
|
||||
/**
|
||||
* Async runner for product Excel import (avoids HTTP timeout on large workbooks).
|
||||
*/
|
||||
@Component
|
||||
public class BmsRedlineProductImportAsyncRunner
|
||||
{
|
||||
private static final Logger log = LoggerFactory.getLogger(BmsRedlineProductImportAsyncRunner.class);
|
||||
|
||||
public static final String CACHE_KEY_PREFIX = "price:redline:import:task:";
|
||||
public static final int TTL_MINUTES = 60;
|
||||
|
||||
@Autowired
|
||||
private IBmsRedlineProductService productService;
|
||||
|
||||
@Autowired
|
||||
private RedisCache redisCache;
|
||||
|
||||
/** Background all-country (Weishi catalog) import. */
|
||||
@Async("threadPoolTaskExecutor")
|
||||
public void runImportAll(String taskId, byte[] bytes, String filename, boolean updateSupport, String operName,
|
||||
Date effectiveDate)
|
||||
{
|
||||
try
|
||||
{
|
||||
put(taskId, BmsRedlineProductImportTask.STATUS_RUNNING, 5, "parsing excel", null);
|
||||
String message = productService.importProductExcel(bytes, filename, updateSupport, operName, taskId,
|
||||
effectiveDate);
|
||||
put(taskId, BmsRedlineProductImportTask.STATUS_SUCCESS, 100, message, null);
|
||||
log.info("async product import done, taskId={}", taskId);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.error("async product import failed, taskId={}", taskId, e);
|
||||
put(taskId, BmsRedlineProductImportTask.STATUS_FAIL, 100, "failed", errorMessage(e));
|
||||
}
|
||||
}
|
||||
|
||||
/** Background single-country cost-sheet import. */
|
||||
@Async("threadPoolTaskExecutor")
|
||||
public void runImportCountry(String taskId, byte[] bytes, String filename, String countryCode, String operName,
|
||||
Date effectiveDate)
|
||||
{
|
||||
try
|
||||
{
|
||||
put(taskId, BmsRedlineProductImportTask.STATUS_RUNNING, 5, "parsing excel", null);
|
||||
String message = productService.importCountryProductExcel(bytes, filename, countryCode, operName, taskId,
|
||||
effectiveDate);
|
||||
put(taskId, BmsRedlineProductImportTask.STATUS_SUCCESS, 100, message, null);
|
||||
log.info("async product country import done, taskId={}, country={}", taskId, countryCode);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.error("async product country import failed, taskId={}, country={}", taskId, countryCode, e);
|
||||
put(taskId, BmsRedlineProductImportTask.STATUS_FAIL, 100, "failed", errorMessage(e));
|
||||
}
|
||||
}
|
||||
|
||||
public void put(String taskId, String status, int percent, String message, String error)
|
||||
{
|
||||
BmsRedlineProductImportTask task = new BmsRedlineProductImportTask();
|
||||
task.setTaskId(taskId);
|
||||
task.setStatus(status);
|
||||
task.setPercent(percent);
|
||||
task.setMessage(message);
|
||||
task.setError(error);
|
||||
redisCache.setCacheObject(CACHE_KEY_PREFIX + taskId, task, TTL_MINUTES, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
public BmsRedlineProductImportTask get(String taskId)
|
||||
{
|
||||
return redisCache.getCacheObject(CACHE_KEY_PREFIX + taskId);
|
||||
}
|
||||
|
||||
private static String errorMessage(Exception e)
|
||||
{
|
||||
String msg = e.getMessage();
|
||||
if (StringUtils.isEmpty(msg))
|
||||
{
|
||||
msg = e.getClass().getSimpleName();
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
or c.effective_date = p.effective_date
|
||||
))
|
||||
) as country_count,
|
||||
(select count(*) from prc_surcharge_rel r where r.product_id = p.product_id) as surcharge_count
|
||||
(select count(*) from prc_surcharge_rel r where r.product_id = p.product_id and r.price_type = 'PRODUCT') as surcharge_count
|
||||
from product p
|
||||
</sql>
|
||||
|
||||
|
||||
+312
@@ -0,0 +1,312 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.bms.price.mapper.BmsRedlineProductCountryMapper">
|
||||
|
||||
<resultMap type="BmsRedlineProductCountry" id="BmsRedlineProductCountryResult">
|
||||
<id property="detailId" column="detail_id" />
|
||||
<result property="productId" column="product_id" />
|
||||
<result property="productCode" column="product_code" />
|
||||
<result property="countryName" column="country_name" />
|
||||
<result property="countryCode" column="country_code" />
|
||||
<result property="zoneId" column="zone_id" />
|
||||
<result property="zoneCode" column="zone_code" />
|
||||
<result property="zoneName" column="zone_name" />
|
||||
<result property="weightMin" column="weight_min" />
|
||||
<result property="weightMax" column="weight_max" />
|
||||
<result property="freightPrice" column="freight_price" />
|
||||
<result property="registrationFee" column="registration_fee" />
|
||||
<result property="sizeLimit" column="size_limit" />
|
||||
<result property="lastMile" column="last_mile" />
|
||||
<result property="leadTime" column="lead_time" />
|
||||
<result property="effectiveDate" column="effective_date" />
|
||||
<result property="expireDate" column="expire_date" />
|
||||
<result property="status" column="status" />
|
||||
<result property="createTime" column="create_time" />
|
||||
<result property="updateTime" column="update_time" />
|
||||
<result property="effectStatus" column="status" />
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectVo">
|
||||
select c.detail_id, c.product_id, c.product_code, c.country_name, c.country_code,
|
||||
c.zone_id, c.zone_code, c.zone_name,
|
||||
c.weight_min, c.weight_max,
|
||||
c.freight_price, c.registration_fee, c.size_limit, c.last_mile, c.lead_time,
|
||||
c.effective_date, c.expire_date, c.status, c.create_time, c.update_time
|
||||
from redline_product_country c
|
||||
</sql>
|
||||
|
||||
<select id="selectBmsRedlineProductCountryList" parameterType="BmsRedlineProductCountry" resultMap="BmsRedlineProductCountryResult">
|
||||
<include refid="selectVo"/>
|
||||
<where>
|
||||
<if test="productId != null">
|
||||
AND c.product_id = #{productId}
|
||||
</if>
|
||||
<if test="productCode != null and productCode != ''">
|
||||
AND c.product_code = #{productCode}
|
||||
</if>
|
||||
<if test="countryName != null and countryName != ''">
|
||||
AND c.country_name like concat('%', #{countryName}, '%')
|
||||
</if>
|
||||
<if test="countryCode != null and countryCode != ''">
|
||||
AND c.country_code = #{countryCode}
|
||||
</if>
|
||||
<if test="zoneId != null">
|
||||
AND c.zone_id = #{zoneId}
|
||||
</if>
|
||||
<if test="zoneCode != null and zoneCode != ''">
|
||||
AND c.zone_code = #{zoneCode}
|
||||
</if>
|
||||
<if test="weightMin != null">
|
||||
AND c.weight_min = #{weightMin}
|
||||
</if>
|
||||
<if test="weightMax != null">
|
||||
AND c.weight_max = #{weightMax}
|
||||
</if>
|
||||
<if test="effectiveDate != null">
|
||||
AND c.effective_date = #{effectiveDate}
|
||||
</if>
|
||||
<if test="openOnly != null and openOnly">
|
||||
AND c.expire_date is null
|
||||
</if>
|
||||
<if test="status != null and status != ''">
|
||||
AND c.status = #{status}
|
||||
</if>
|
||||
<if test="effectStatus != null and effectStatus != ''">
|
||||
AND c.status = #{effectStatus}
|
||||
</if>
|
||||
</where>
|
||||
order by c.effective_date desc, c.country_name asc, c.zone_code asc, c.weight_min asc, c.detail_id asc
|
||||
</select>
|
||||
|
||||
<insert id="insertBmsRedlineProductCountry" parameterType="BmsRedlineProductCountry" useGeneratedKeys="true" keyProperty="detailId">
|
||||
insert into redline_product_country (
|
||||
product_id, product_code, country_name, country_code, zone_id, zone_code, zone_name,
|
||||
weight_min, weight_max,
|
||||
freight_price, registration_fee, size_limit, last_mile, lead_time,
|
||||
effective_date, expire_date, status, create_time
|
||||
) values (
|
||||
#{productId}, #{productCode}, #{countryName}, #{countryCode}, #{zoneId}, #{zoneCode}, #{zoneName},
|
||||
#{weightMin}, #{weightMax},
|
||||
#{freightPrice}, #{registrationFee}, #{sizeLimit}, #{lastMile}, #{leadTime},
|
||||
#{effectiveDate}, #{expireDate}, IFNULL(#{status}, '0'), sysdate()
|
||||
)
|
||||
</insert>
|
||||
|
||||
<!-- Detail edit dialog: frontend submits full editable fields;
|
||||
weight_max / expire_date may be null (= open-ended) -->
|
||||
<update id="updateBmsRedlineProductCountry" parameterType="BmsRedlineProductCountry">
|
||||
update redline_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>
|
||||
|
||||
<update id="updateBmsRedlineProductCountryImport" parameterType="BmsRedlineProductCountry">
|
||||
update redline_product_country
|
||||
set product_id = #{productId},
|
||||
country_code = #{countryCode},
|
||||
zone_id = #{zoneId},
|
||||
zone_code = #{zoneCode},
|
||||
zone_name = #{zoneName},
|
||||
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},
|
||||
status = IFNULL(#{status}, status),
|
||||
update_time = sysdate()
|
||||
where detail_id = #{detailId}
|
||||
</update>
|
||||
|
||||
<delete id="deleteByProductId" parameterType="Long">
|
||||
delete from redline_product_country where product_id = #{productId}
|
||||
</delete>
|
||||
|
||||
<delete id="deleteByProductCode" parameterType="String">
|
||||
delete from redline_product_country where product_code = #{productCode}
|
||||
</delete>
|
||||
|
||||
<delete id="deleteByProductCodes">
|
||||
delete from redline_product_country where product_code in
|
||||
<foreach item="code" collection="array" open="(" separator="," close=")">
|
||||
#{code}
|
||||
</foreach>
|
||||
</delete>
|
||||
|
||||
<delete id="deleteByProductCodeAndEffectiveDate">
|
||||
delete from redline_product_country
|
||||
where product_code = #{productCode}
|
||||
and (
|
||||
(#{effectiveDate} is null and effective_date is null)
|
||||
or effective_date = #{effectiveDate}
|
||||
)
|
||||
</delete>
|
||||
|
||||
<select id="selectMaxOpenEffectiveDate" resultType="java.util.Date">
|
||||
select max(effective_date) from redline_product_country
|
||||
where product_code = #{productCode}
|
||||
and expire_date is null
|
||||
</select>
|
||||
|
||||
<update id="expireOpenByProductCode">
|
||||
update redline_product_country
|
||||
set expire_date = #{newEffectiveDate},
|
||||
update_time = sysdate()
|
||||
where product_code = #{productCode}
|
||||
and expire_date is null
|
||||
and (effective_date is null or effective_date < #{newEffectiveDate})
|
||||
</update>
|
||||
|
||||
<update id="expireOpenByProductCodeAndCountry">
|
||||
update redline_product_country
|
||||
set expire_date = #{newEffectiveDate},
|
||||
update_time = sysdate()
|
||||
where product_code = #{productCode}
|
||||
and expire_date is null
|
||||
and (effective_date is null or effective_date < #{newEffectiveDate})
|
||||
<choose>
|
||||
<when test="countryCode != null and countryCode != ''">
|
||||
and upper(ifnull(country_code,'')) = upper(#{countryCode})
|
||||
</when>
|
||||
<otherwise>
|
||||
and country_name = #{countryName}
|
||||
</otherwise>
|
||||
</choose>
|
||||
</update>
|
||||
|
||||
<update id="refreshStatusByDates">
|
||||
update redline_product_country
|
||||
set status = case
|
||||
when expire_date is not null and expire_date <= now() then '2'
|
||||
when effective_date is null or effective_date > now() then '0'
|
||||
else '1'
|
||||
end
|
||||
</update>
|
||||
|
||||
<delete id="deleteOpenByProductCodeAndEffectiveDate">
|
||||
delete from redline_product_country
|
||||
where product_code = #{productCode}
|
||||
and expire_date is null
|
||||
and (
|
||||
(#{effectiveDate} is null and effective_date is null)
|
||||
or effective_date = #{effectiveDate}
|
||||
)
|
||||
</delete>
|
||||
|
||||
<delete id="deleteOpenByProductCodeAndCountryOnDate">
|
||||
delete from redline_product_country
|
||||
where product_code = #{productCode}
|
||||
and expire_date is null
|
||||
and (
|
||||
(#{effectiveDate} is null and effective_date is null)
|
||||
or effective_date = #{effectiveDate}
|
||||
)
|
||||
<choose>
|
||||
<when test="countryCode != null and countryCode != ''">
|
||||
and upper(ifnull(country_code,'')) = upper(#{countryCode})
|
||||
</when>
|
||||
<otherwise>
|
||||
and country_name = #{countryName}
|
||||
</otherwise>
|
||||
</choose>
|
||||
</delete>
|
||||
|
||||
<select id="selectMatchByProductCountryWeight" resultMap="BmsRedlineProductCountryResult">
|
||||
<include refid="selectVo"/>
|
||||
where c.product_code = #{productCode}
|
||||
and c.weight_min <= #{weight}
|
||||
and c.weight_max >= #{weight}
|
||||
<choose>
|
||||
<when test="countryCode != null and countryCode != ''">
|
||||
and upper(c.country_code) = upper(#{countryCode})
|
||||
</when>
|
||||
<when test="countryName != null and countryName != ''">
|
||||
and c.country_name = #{countryName}
|
||||
</when>
|
||||
<otherwise>
|
||||
and 1 = 0
|
||||
</otherwise>
|
||||
</choose>
|
||||
<choose>
|
||||
<when test="zoneCode != null and zoneCode != ''">
|
||||
and upper(ifnull(c.zone_code,'')) = upper(#{zoneCode})
|
||||
</when>
|
||||
<otherwise>
|
||||
and (c.zone_code is null or c.zone_code = '')
|
||||
</otherwise>
|
||||
</choose>
|
||||
<if test="bizDate != null">
|
||||
and (c.effective_date is null or c.effective_date <= #{bizDate})
|
||||
and (c.expire_date is null or c.expire_date > #{bizDate})
|
||||
</if>
|
||||
<if test="bizDate == null">
|
||||
and c.expire_date is null
|
||||
</if>
|
||||
order by c.effective_date desc, c.weight_min asc, c.detail_id asc
|
||||
limit 1
|
||||
</select>
|
||||
|
||||
<select id="countZonedByProductCountry" resultType="int">
|
||||
select count(1)
|
||||
from redline_product_country
|
||||
where product_code = #{productCode}
|
||||
and zone_code is not null and zone_code <> ''
|
||||
<choose>
|
||||
<when test="countryCode != null and countryCode != ''">
|
||||
and upper(country_code) = upper(#{countryCode})
|
||||
</when>
|
||||
<when test="countryName != null and countryName != ''">
|
||||
and country_name = #{countryName}
|
||||
</when>
|
||||
<otherwise>
|
||||
and 1 = 0
|
||||
</otherwise>
|
||||
</choose>
|
||||
<if test="bizDate != null">
|
||||
and (effective_date is null or effective_date <= #{bizDate})
|
||||
and (expire_date is null or expire_date > #{bizDate})
|
||||
</if>
|
||||
<if test="bizDate == null">
|
||||
and expire_date is null
|
||||
</if>
|
||||
</select>
|
||||
|
||||
<select id="selectOpenByProductCode" resultMap="BmsRedlineProductCountryResult">
|
||||
<include refid="selectVo"/>
|
||||
where c.product_code = #{productCode}
|
||||
and c.expire_date is null
|
||||
order by c.country_name asc, c.zone_code asc, c.weight_min asc, c.detail_id asc
|
||||
</select>
|
||||
|
||||
<insert id="copyOpenCountries">
|
||||
insert into redline_product_country (
|
||||
product_id, product_code, country_name, country_code, zone_id, zone_code, zone_name,
|
||||
weight_min, weight_max, freight_price, registration_fee, size_limit, last_mile, lead_time,
|
||||
effective_date, expire_date, status, create_time
|
||||
)
|
||||
select
|
||||
#{productId}, product_code, country_name, country_code, zone_id, zone_code, zone_name,
|
||||
weight_min, weight_max, freight_price, registration_fee, size_limit, last_mile, lead_time,
|
||||
#{effectiveDate}, null, IFNULL(#{status}, '0'), sysdate()
|
||||
from redline_product_country
|
||||
where product_code = #{productCode}
|
||||
and expire_date is null
|
||||
and (effective_date is null or effective_date < #{effectiveDate})
|
||||
order by detail_id asc
|
||||
</insert>
|
||||
|
||||
</mapper>
|
||||
@@ -0,0 +1,219 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.bms.price.mapper.BmsRedlineProductMapper">
|
||||
|
||||
<resultMap type="BmsRedlineProduct" id="BmsRedlineProductResult">
|
||||
<id property="productId" column="product_id" />
|
||||
<result property="productCode" column="product_code" />
|
||||
<result property="productName" column="product_name" />
|
||||
<result property="serviceLevel" column="service_level" />
|
||||
<result property="cargoAttr" column="cargo_attr" />
|
||||
<result property="destRegion" column="dest_region" />
|
||||
<result property="updateNote" column="update_note" />
|
||||
<result property="taxMode" column="tax_mode" />
|
||||
<result property="effectiveDate" column="effective_date" />
|
||||
<result property="expireDate" column="expire_date" />
|
||||
<result property="status" column="status" />
|
||||
<result property="sourceSheet" column="source_sheet" />
|
||||
<result property="pefId" column="pef_id" />
|
||||
<result property="remark" column="remark" />
|
||||
<result property="tmsId" column="tms_id" />
|
||||
<result property="createBy" column="created_by" />
|
||||
<result property="createTime" column="create_time" />
|
||||
<result property="updateBy" column="updated_by" />
|
||||
<result property="updateTime" column="update_time" />
|
||||
<result property="countryCount" column="country_count" />
|
||||
<result property="surchargeCount" column="surcharge_count" />
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectBmsRedlineProductVo">
|
||||
select p.product_id, p.product_code, p.product_name, p.service_level, p.cargo_attr,
|
||||
p.dest_region, p.update_note, p.tax_mode, p.effective_date, p.expire_date, p.status,
|
||||
p.source_sheet, p.pef_id, p.remark, p.tms_id, p.created_by, p.create_time, p.updated_by, p.update_time,
|
||||
(select count(distinct c.country_name) from redline_product_country c
|
||||
where c.product_id = p.product_id
|
||||
or (c.product_id is null and c.product_code = p.product_code
|
||||
and (
|
||||
(p.effective_date is null and c.effective_date is null)
|
||||
or c.effective_date = p.effective_date
|
||||
))
|
||||
) as country_count,
|
||||
(select count(*) from prc_surcharge_rel r where r.product_id = p.product_id and r.price_type = 'REDLINE') as surcharge_count
|
||||
from redline_product p
|
||||
</sql>
|
||||
|
||||
<select id="selectBmsRedlineProductById" parameterType="Long" resultMap="BmsRedlineProductResult">
|
||||
<include refid="selectBmsRedlineProductVo"/>
|
||||
where p.product_id = #{productId}
|
||||
</select>
|
||||
|
||||
<select id="selectBmsRedlineProductByCode" parameterType="String" resultMap="BmsRedlineProductResult">
|
||||
<include refid="selectBmsRedlineProductVo"/>
|
||||
where p.product_code = #{productCode}
|
||||
order by p.effective_date desc, p.product_id desc
|
||||
limit 1
|
||||
</select>
|
||||
|
||||
<select id="selectBmsRedlineProductByCodeAndDate" resultMap="BmsRedlineProductResult">
|
||||
<include refid="selectBmsRedlineProductVo"/>
|
||||
where p.product_code = #{productCode}
|
||||
<choose>
|
||||
<when test="effectiveDate != null">
|
||||
AND p.effective_date = #{effectiveDate}
|
||||
</when>
|
||||
<otherwise>
|
||||
AND p.effective_date is null
|
||||
</otherwise>
|
||||
</choose>
|
||||
limit 1
|
||||
</select>
|
||||
|
||||
<select id="selectEffectiveBmsRedlineProductByCode" resultMap="BmsRedlineProductResult">
|
||||
<include refid="selectBmsRedlineProductVo"/>
|
||||
where p.product_code = #{productCode}
|
||||
and (p.effective_date is null or #{bizDate} is null or p.effective_date <= #{bizDate})
|
||||
and (p.expire_date is null or #{bizDate} is null or p.expire_date > #{bizDate})
|
||||
order by p.effective_date desc, p.product_id desc
|
||||
limit 1
|
||||
</select>
|
||||
|
||||
<select id="selectBmsRedlineProductList" parameterType="BmsRedlineProduct" resultMap="BmsRedlineProductResult">
|
||||
<include refid="selectBmsRedlineProductVo"/>
|
||||
<where>
|
||||
<if test="productCode != null and productCode != ''">
|
||||
AND p.product_code like concat('%', #{productCode}, '%')
|
||||
</if>
|
||||
<if test="productName != null and productName != ''">
|
||||
AND p.product_name like concat('%', #{productName}, '%')
|
||||
</if>
|
||||
<if test="taxMode != null and taxMode != ''">
|
||||
AND p.tax_mode = #{taxMode}
|
||||
</if>
|
||||
<if test="status != null and status != ''">
|
||||
AND p.status = #{status}
|
||||
</if>
|
||||
<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>
|
||||
<if test="params != null and params.keyword != null and params.keyword != ''">
|
||||
AND (p.product_code like concat('%', #{params.keyword}, '%')
|
||||
OR p.product_name like concat('%', #{params.keyword}, '%'))
|
||||
</if>
|
||||
</where>
|
||||
<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="insertBmsRedlineProduct" parameterType="BmsRedlineProduct" useGeneratedKeys="true" keyProperty="productId">
|
||||
insert into redline_product (
|
||||
<if test="productCode != null and productCode != ''">product_code,</if>
|
||||
<if test="productName != null and productName != ''">product_name,</if>
|
||||
<if test="serviceLevel != null">service_level,</if>
|
||||
<if test="cargoAttr != null">cargo_attr,</if>
|
||||
<if test="destRegion != null">dest_region,</if>
|
||||
<if test="updateNote != null">update_note,</if>
|
||||
<if test="taxMode != null and taxMode != ''">tax_mode,</if>
|
||||
<if test="effectiveDate != null">effective_date,</if>
|
||||
<if test="expireDate != null">expire_date,</if>
|
||||
<if test="status != null and status != ''">status,</if>
|
||||
<if test="sourceSheet != null">source_sheet,</if>
|
||||
<if test="pefId != null">pef_id,</if>
|
||||
<if test="remark != null">remark,</if>
|
||||
<if test="tmsId != null">tms_id,</if>
|
||||
<if test="createBy != null and createBy != ''">created_by,</if>
|
||||
create_time
|
||||
) values (
|
||||
<if test="productCode != null and productCode != ''">#{productCode},</if>
|
||||
<if test="productName != null and productName != ''">#{productName},</if>
|
||||
<if test="serviceLevel != null">#{serviceLevel},</if>
|
||||
<if test="cargoAttr != null">#{cargoAttr},</if>
|
||||
<if test="destRegion != null">#{destRegion},</if>
|
||||
<if test="updateNote != null">#{updateNote},</if>
|
||||
<if test="taxMode != null and taxMode != ''">#{taxMode},</if>
|
||||
<if test="effectiveDate != null">#{effectiveDate},</if>
|
||||
<if test="expireDate != null">#{expireDate},</if>
|
||||
<if test="status != null and status != ''">#{status},</if>
|
||||
<if test="sourceSheet != null">#{sourceSheet},</if>
|
||||
<if test="pefId != null">#{pefId},</if>
|
||||
<if test="remark != null">#{remark},</if>
|
||||
<if test="tmsId != null">#{tmsId},</if>
|
||||
<if test="createBy != null and createBy != ''">#{createBy},</if>
|
||||
sysdate()
|
||||
)
|
||||
</insert>
|
||||
|
||||
<update id="updateBmsRedlineProduct" parameterType="BmsRedlineProduct">
|
||||
update product
|
||||
<set>
|
||||
<if test="productCode != null and productCode != ''">product_code = #{productCode},</if>
|
||||
<if test="productName != null and productName != ''">product_name = #{productName},</if>
|
||||
<if test="serviceLevel != null">service_level = #{serviceLevel},</if>
|
||||
<if test="cargoAttr != null">cargo_attr = #{cargoAttr},</if>
|
||||
<if test="destRegion != null">dest_region = #{destRegion},</if>
|
||||
<if test="updateNote != null">update_note = #{updateNote},</if>
|
||||
<if test="taxMode != null and taxMode != ''">tax_mode = #{taxMode},</if>
|
||||
<if test="effectiveDate != null">effective_date = #{effectiveDate},</if>
|
||||
expire_date = #{expireDate},
|
||||
<if test="status != null and status != ''">status = #{status},</if>
|
||||
<if test="sourceSheet != null">source_sheet = #{sourceSheet},</if>
|
||||
<if test="pefId != null">pef_id = #{pefId},</if>
|
||||
<if test="remark != null">remark = #{remark},</if>
|
||||
<if test="tmsId != null">tms_id = #{tmsId},</if>
|
||||
<if test="updateBy != null and updateBy != ''">updated_by = #{updateBy},</if>
|
||||
update_time = sysdate()
|
||||
</set>
|
||||
where product_id = #{productId}
|
||||
</update>
|
||||
|
||||
<delete id="deleteBmsRedlineProductByIds" parameterType="Long">
|
||||
delete from redline_product where product_id in
|
||||
<foreach item="productId" collection="array" open="(" separator="," close=")">
|
||||
#{productId}
|
||||
</foreach>
|
||||
</delete>
|
||||
|
||||
<update id="expireOpenByProductCode">
|
||||
update product
|
||||
set expire_date = #{newEffectiveDate},
|
||||
update_time = sysdate()
|
||||
where product_code = #{productCode}
|
||||
and expire_date is null
|
||||
and (effective_date is null or effective_date < #{newEffectiveDate})
|
||||
<if test="excludeId != null">
|
||||
and product_id <> #{excludeId}
|
||||
</if>
|
||||
</update>
|
||||
|
||||
<update id="refreshStatusByDates">
|
||||
update product
|
||||
set status = case
|
||||
when expire_date is not null and expire_date <= now() then '2'
|
||||
when effective_date is null or effective_date > now() then '0'
|
||||
else '1'
|
||||
end
|
||||
</update>
|
||||
|
||||
</mapper>
|
||||
@@ -113,4 +113,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
</foreach>
|
||||
</delete>
|
||||
|
||||
|
||||
<select id="selectByRedlineProductCode" resultMap="PrcPriceExcelFileResult">
|
||||
select distinct f.pef_id, f.excel_type, f.effective_time, f.file_name, f.file_path, f.file_size,
|
||||
f.content_type, f.file_extension, f.oss_id, f.sheet_names, f.remark,
|
||||
f.created_by, f.create_time, f.updated_by, f.update_time
|
||||
from prc_price_excel_file f
|
||||
inner join redline_product p on p.pef_id = f.pef_id
|
||||
where p.product_code = #{productCode}
|
||||
and p.pef_id is not null
|
||||
order by f.effective_time desc, f.pef_id desc
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
||||
@@ -35,11 +35,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
r.created_by, r.create_time, r.updated_by, r.update_time,
|
||||
s.sc_name, s.fee_type, s.charge_type, s.charge_value, s.currency,
|
||||
s.round_mode, s.ss_code, s.sc_note,
|
||||
c.country_name, c.weight_min, c.weight_max
|
||||
ifnull(c.country_name, rc.country_name) as country_name, ifnull(c.weight_min, rc.weight_min) as weight_min, ifnull(c.weight_max, rc.weight_max) as weight_max
|
||||
from prc_surcharge_rel r
|
||||
join prc_surcharge s on s.sc_id = r.sc_id
|
||||
left join product_country c on c.detail_id = r.detail_id
|
||||
and r.price_type in ('PRODUCT', 'REDLINE')
|
||||
left join product_country c on c.detail_id = r.detail_id and r.price_type = 'PRODUCT'
|
||||
left join redline_product_country rc on rc.detail_id = r.detail_id and r.price_type = 'REDLINE'
|
||||
</sql>
|
||||
|
||||
<select id="selectPrcSurchargeRelList" parameterType="PrcSurchargeRel" resultMap="PrcSurchargeRelResult">
|
||||
@@ -102,7 +102,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<foreach item="code" collection="array" open="(" separator="," close=")">
|
||||
#{code}
|
||||
</foreach>
|
||||
and price_type in ('PRODUCT', 'REDLINE')
|
||||
and price_type = 'PRODUCT'
|
||||
</delete>
|
||||
|
||||
<delete id="deleteByDetailIdsAndPriceType">
|
||||
|
||||
@@ -345,6 +345,68 @@ CREATE TABLE `product_country` (
|
||||
CONSTRAINT `fk_product_country_pid` FOREIGN KEY (`product_id`) REFERENCES `product` (`product_id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=1609 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='产品国家服务明细-重量段+尺寸/尾程/时效(不含运费)';
|
||||
|
||||
-- redline_product / redline_product_country: mirror of product / product_country
|
||||
-- Run once on existing DB
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `redline_product` (
|
||||
`product_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '红线价产品ID',
|
||||
`product_code` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '产品对接代码,如 WSP037',
|
||||
`product_name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '产品名称',
|
||||
`service_level` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '时效等级',
|
||||
`cargo_attr` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '走货属性',
|
||||
`dest_region` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '目的区域',
|
||||
`update_note` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新内容说明',
|
||||
`tax_mode` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'TAX_INCLUDED' COMMENT 'TAX_INCLUDED包税 TAX_EXCLUDED不包税',
|
||||
`effective_date` datetime DEFAULT NULL COMMENT '目录生效时间(到小时)',
|
||||
`expire_date` datetime DEFAULT NULL COMMENT '失效时间(空=长期)',
|
||||
`status` char(1) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '0' COMMENT '0待生效 1已生效 2已失效',
|
||||
`source_sheet` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '来源Excel sheet名',
|
||||
`pef_id` bigint(20) DEFAULT NULL COMMENT '关联导入价表Excel附件 prc_price_excel_file.pef_id',
|
||||
`remark` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`tms_id` int(11) DEFAULT NULL COMMENT '多租户ID',
|
||||
`created_by` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_by` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`product_id`) USING BTREE,
|
||||
UNIQUE KEY `uk_redline_product_code_eff` (`product_code`,`effective_date`),
|
||||
KEY `idx_redline_tax_status` (`tax_mode`,`status`) USING BTREE,
|
||||
KEY `idx_redline_product_code` (`product_code`),
|
||||
KEY `idx_redline_product_pef_id` (`pef_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='红线价产品主表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `redline_product_country` (
|
||||
`detail_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '明细ID',
|
||||
`product_id` bigint(20) DEFAULT NULL COMMENT '红线价产品ID redline_product.product_id',
|
||||
`product_code` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '产品代码',
|
||||
`country_name` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '国家中文名',
|
||||
`country_code` char(2) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'ISO2',
|
||||
`zone_id` bigint(20) DEFAULT NULL COMMENT 'prc_zone.zone_id',
|
||||
`zone_code` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`zone_name` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`weight_min` decimal(10,3) DEFAULT NULL COMMENT '重量段最小值KG(含)',
|
||||
`weight_max` decimal(10,3) DEFAULT NULL COMMENT '重量段最大值KG(含)',
|
||||
`freight_price` decimal(10,4) DEFAULT NULL COMMENT '运费 RMB/KG',
|
||||
`registration_fee` decimal(10,2) DEFAULT NULL COMMENT '挂号费 RMB/票',
|
||||
`size_limit` varchar(1000) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`last_mile` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`lead_time` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`effective_date` datetime DEFAULT NULL COMMENT '生效时间(到小时)',
|
||||
`expire_date` datetime DEFAULT NULL COMMENT '失效时间(空=开放)',
|
||||
`status` char(1) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '0' COMMENT '0待生效 1已生效 2已失效',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`detail_id`) USING BTREE,
|
||||
UNIQUE KEY `uk_redline_country_zone_weight_eff` (`product_code`,`country_name`,`zone_code`,`weight_min`,`weight_max`,`effective_date`),
|
||||
KEY `idx_redline_country_code` (`country_code`) USING BTREE,
|
||||
KEY `idx_redline_weight_match` (`product_code`,`country_code`,`weight_min`,`weight_max`) USING BTREE,
|
||||
KEY `idx_redline_product_eff` (`product_code`,`effective_date`,`expire_date`),
|
||||
KEY `idx_redline_country_pid` (`product_id`),
|
||||
KEY `idx_redline_country_zone` (`zone_id`),
|
||||
CONSTRAINT `fk_redline_country_pid` FOREIGN KEY (`product_id`) REFERENCES `redline_product` (`product_id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='红线价国家服务明细';
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
-- 表结构:`region`
|
||||
-- ----------------------------
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
-- redline_product / redline_product_country: mirror of product / product_country
|
||||
-- Run once on existing DB
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `redline_product` (
|
||||
`product_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '红线价产品ID',
|
||||
`product_code` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '产品对接代码,如 WSP037',
|
||||
`product_name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '产品名称',
|
||||
`service_level` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '时效等级',
|
||||
`cargo_attr` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '走货属性',
|
||||
`dest_region` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '目的区域',
|
||||
`update_note` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新内容说明',
|
||||
`tax_mode` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'TAX_INCLUDED' COMMENT 'TAX_INCLUDED包税 TAX_EXCLUDED不包税',
|
||||
`effective_date` datetime DEFAULT NULL COMMENT '目录生效时间(到小时)',
|
||||
`expire_date` datetime DEFAULT NULL COMMENT '失效时间(空=长期)',
|
||||
`status` char(1) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '0' COMMENT '0待生效 1已生效 2已失效',
|
||||
`source_sheet` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '来源Excel sheet名',
|
||||
`pef_id` bigint(20) DEFAULT NULL COMMENT '关联导入价表Excel附件 prc_price_excel_file.pef_id',
|
||||
`remark` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`tms_id` int(11) DEFAULT NULL COMMENT '多租户ID',
|
||||
`created_by` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_by` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`product_id`) USING BTREE,
|
||||
UNIQUE KEY `uk_redline_product_code_eff` (`product_code`,`effective_date`),
|
||||
KEY `idx_redline_tax_status` (`tax_mode`,`status`) USING BTREE,
|
||||
KEY `idx_redline_product_code` (`product_code`),
|
||||
KEY `idx_redline_product_pef_id` (`pef_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='红线价产品主表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `redline_product_country` (
|
||||
`detail_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '明细ID',
|
||||
`product_id` bigint(20) DEFAULT NULL COMMENT '红线价产品ID redline_product.product_id',
|
||||
`product_code` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '产品代码',
|
||||
`country_name` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '国家中文名',
|
||||
`country_code` char(2) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'ISO2',
|
||||
`zone_id` bigint(20) DEFAULT NULL COMMENT 'prc_zone.zone_id',
|
||||
`zone_code` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`zone_name` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`weight_min` decimal(10,3) DEFAULT NULL COMMENT '重量段最小值KG(含)',
|
||||
`weight_max` decimal(10,3) DEFAULT NULL COMMENT '重量段最大值KG(含)',
|
||||
`freight_price` decimal(10,4) DEFAULT NULL COMMENT '运费 RMB/KG',
|
||||
`registration_fee` decimal(10,2) DEFAULT NULL COMMENT '挂号费 RMB/票',
|
||||
`size_limit` varchar(1000) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`last_mile` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`lead_time` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`effective_date` datetime DEFAULT NULL COMMENT '生效时间(到小时)',
|
||||
`expire_date` datetime DEFAULT NULL COMMENT '失效时间(空=开放)',
|
||||
`status` char(1) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '0' COMMENT '0待生效 1已生效 2已失效',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`detail_id`) USING BTREE,
|
||||
UNIQUE KEY `uk_redline_country_zone_weight_eff` (`product_code`,`country_name`,`zone_code`,`weight_min`,`weight_max`,`effective_date`),
|
||||
KEY `idx_redline_country_code` (`country_code`) USING BTREE,
|
||||
KEY `idx_redline_weight_match` (`product_code`,`country_code`,`weight_min`,`weight_max`) USING BTREE,
|
||||
KEY `idx_redline_product_eff` (`product_code`,`effective_date`,`expire_date`),
|
||||
KEY `idx_redline_country_pid` (`product_id`),
|
||||
KEY `idx_redline_country_zone` (`zone_id`),
|
||||
CONSTRAINT `fk_redline_country_pid` FOREIGN KEY (`product_id`) REFERENCES `redline_product` (`product_id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='红线价国家服务明细';
|
||||
|
||||
-- Menu: under 价栶基础 parent (lookup by path price/basic or menu_name)
|
||||
-- Adjust @parentId if your env differs
|
||||
SET @parentId := (
|
||||
SELECT menu_id FROM sys_menu
|
||||
WHERE path = 'basic' AND menu_type = 'M'
|
||||
ORDER BY menu_id ASC LIMIT 1
|
||||
);
|
||||
SET @parentId := IFNULL(@parentId, (
|
||||
SELECT parent_id FROM sys_menu
|
||||
WHERE perms = 'price:product:list' OR component LIKE '%price/basic/product%'
|
||||
ORDER BY menu_id ASC LIMIT 1
|
||||
));
|
||||
|
||||
-- skip insert if already exists
|
||||
SET @exists := (
|
||||
SELECT COUNT(1) FROM sys_menu WHERE perms = 'price:redline:list' OR component = 'price/basic/redline/index'
|
||||
);
|
||||
|
||||
-- Only insert when parent found and not exists
|
||||
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, remark)
|
||||
SELECT '红线价', @parentId, 15, 'redline', 'price/basic/redline/index', 1, 0, 'C', '0', '0', 'price:redline:list', 'money', 'admin', sysdate(), '红线价目录'
|
||||
WHERE @parentId IS NOT NULL AND @exists = 0;
|
||||
|
||||
SET @menuId := (SELECT menu_id FROM sys_menu WHERE perms = 'price:redline:list' 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, 1, '', '', 1, 0, 'F', '0', '0', 'price:redline:query', '#', 'admin', sysdate()
|
||||
WHERE @menuId IS NOT NULL AND NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'price:redline:query');
|
||||
|
||||
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, 2, '', '', 1, 0, 'F', '0', '0', 'price:redline:add', '#', 'admin', sysdate()
|
||||
WHERE @menuId IS NOT NULL AND NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'price:redline:add');
|
||||
|
||||
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, 3, '', '', 1, 0, 'F', '0', '0', 'price:redline:edit', '#', 'admin', sysdate()
|
||||
WHERE @menuId IS NOT NULL AND NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'price:redline:edit');
|
||||
|
||||
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, 4, '', '', 1, 0, 'F', '0', '0', 'price:redline:remove', '#', 'admin', sysdate()
|
||||
WHERE @menuId IS NOT NULL AND NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'price:redline:remove');
|
||||
|
||||
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, 5, '', '', 1, 0, 'F', '0', '0', 'price:redline:import', '#', 'admin', sysdate()
|
||||
WHERE @menuId IS NOT NULL AND NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'price:redline:import');
|
||||
@@ -0,0 +1,177 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
/** 查询红线价列表 */
|
||||
export function listRedline(query) {
|
||||
return request({
|
||||
url: '/price/redline/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
/** 产品下拉选项(非分页,不要求 price:redline:list 权限) */
|
||||
export function listRedlineOptions(query) {
|
||||
return request({
|
||||
url: '/price/redline/optionselect',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
/** 查询产品详情 */
|
||||
export function getRedline(productId) {
|
||||
return request({
|
||||
url: '/price/redline/' + productId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
/** 查询产品国家明细(优先 productId,绑定到具体产品版本行) */
|
||||
export function listRedlineCountries(productCode, effectiveDate, productId) {
|
||||
const params = {}
|
||||
if (effectiveDate) params.effectiveDate = effectiveDate
|
||||
if (productId) params.productId = productId
|
||||
return request({
|
||||
url: '/price/redline/' + productCode + '/countries',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
/** 修改产品国家明细行(重量段/运费/挂号费/尺寸/尾程/时效/生效失效日期) */
|
||||
export function updateRedlineCountry(data) {
|
||||
return request({
|
||||
url: '/price/redline/country',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
/** 新增红线价 */
|
||||
export function addRedline(data) {
|
||||
return request({
|
||||
url: '/price/redline',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
/** 修改产品 */
|
||||
export function updateRedline(data) {
|
||||
return request({
|
||||
url: '/price/redline',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
/** 删除产品 */
|
||||
export function delRedline(productIds) {
|
||||
return request({
|
||||
url: '/price/redline/' + productIds,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
export function getRedlineImportTask(taskId) {
|
||||
return request({
|
||||
url: '/price/redline/import/task/' + taskId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
function pickImportTaskId(res) {
|
||||
if (!res) return null
|
||||
const d = res.data
|
||||
if (d && typeof d === 'object' && d.taskId) return String(d.taskId)
|
||||
if (typeof d === 'string' && d.trim()) return d.trim()
|
||||
if (typeof res.msg === 'string') {
|
||||
const m = res.msg.trim()
|
||||
if (/^[a-f0-9]{32}$/i.test(m)) return m
|
||||
if (/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i.test(m)) {
|
||||
return m.replace(/-/g, '')
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
async function pollProductImportTask(taskId, onProgress) {
|
||||
const started = Date.now()
|
||||
const maxMs = 30 * 60 * 1000
|
||||
while (Date.now() - started < maxMs) {
|
||||
const res = await getRedlineImportTask(taskId)
|
||||
const task = res.data || {}
|
||||
if (typeof onProgress === 'function') {
|
||||
onProgress(task)
|
||||
}
|
||||
if (task.status === 'SUCCESS') {
|
||||
return { task }
|
||||
}
|
||||
if (task.status === 'FAIL') {
|
||||
const err = new Error(task.error || task.message || 'import failed')
|
||||
err.task = task
|
||||
throw err
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, 800))
|
||||
}
|
||||
throw new Error('import timeout')
|
||||
}
|
||||
|
||||
/**
|
||||
* Async all-country product import; poll until done.
|
||||
* @param {File|Blob} file
|
||||
* @param {string} effectiveDate yyyy-MM-dd (required)
|
||||
* @param {(task: object) => void} [onProgress]
|
||||
*/
|
||||
export async function importRedlineExcelAsync(file, effectiveDate, onProgress) {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
const params = new URLSearchParams()
|
||||
params.set('updateSupport', '0')
|
||||
if (effectiveDate) params.set('effectiveDate', effectiveDate)
|
||||
const submitRes = await request({
|
||||
url: '/price/redline/importData?' + params.toString(),
|
||||
method: 'post',
|
||||
data: formData,
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
})
|
||||
const taskId = pickImportTaskId(submitRes)
|
||||
if (!taskId) {
|
||||
throw new Error('missing import taskId')
|
||||
}
|
||||
return pollProductImportTask(taskId, onProgress)
|
||||
}
|
||||
|
||||
/**
|
||||
* Async single-country product import; poll until done.
|
||||
* @param {File|Blob} file
|
||||
* @param {string} countryCode
|
||||
* @param {string} effectiveDate yyyy-MM-dd
|
||||
* @param {(task: object) => void} [onProgress]
|
||||
*/
|
||||
export async function importRedlineCountryExcelAsync(file, countryCode, effectiveDate, onProgress) {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
const params = new URLSearchParams()
|
||||
params.set('countryCode', countryCode)
|
||||
if (effectiveDate) params.set('effectiveDate', effectiveDate)
|
||||
const submitRes = await request({
|
||||
url: '/price/redline/importCountryData?' + params.toString(),
|
||||
method: 'post',
|
||||
data: formData,
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
})
|
||||
const taskId = pickImportTaskId(submitRes)
|
||||
if (!taskId) {
|
||||
throw new Error('missing import taskId')
|
||||
}
|
||||
return pollProductImportTask(taskId, onProgress)
|
||||
}
|
||||
|
||||
/** List imported Excel attachments for product_code */
|
||||
export function listProductExcelFiles(productCode) {
|
||||
return request({
|
||||
url: '/price/redline/' + encodeURIComponent(productCode) + '/excelFiles',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
@@ -20,10 +20,11 @@ export function getSurcharge(scId) {
|
||||
}
|
||||
|
||||
/** 查询指定产品的附加费关联(非分页,含附加费与明细展示字段) */
|
||||
export function listSurchargeByProduct(productCode) {
|
||||
export function listSurchargeByProduct(productCode, priceType) {
|
||||
return request({
|
||||
url: '/price/surcharge/byProduct/' + productCode,
|
||||
method: 'get'
|
||||
method: 'get',
|
||||
params: priceType ? { priceType } : undefined
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ function open(row) {
|
||||
function loadList() {
|
||||
if (!product.value) return
|
||||
loading.value = true
|
||||
listSurchargeByProduct(product.value.productCode).then(res => {
|
||||
listSurchargeByProduct(product.value.productCode, 'PRODUCT').then(res => {
|
||||
list.value = res.data || []
|
||||
}).finally(() => {
|
||||
loading.value = false
|
||||
|
||||
@@ -0,0 +1,281 @@
|
||||
<template>
|
||||
<el-drawer v-model="visible" size="80%" :title="title" append-to-body>
|
||||
<div class="mb8" style="display: flex; align-items: center; gap: 12px">
|
||||
<el-input v-model="keyword" placeholder="搜索国家/分区(中文名 / ISO2 / 分区)" clearable style="width: 260px" />
|
||||
</div>
|
||||
<simple-table
|
||||
:data="filteredList"
|
||||
:columns="columns"
|
||||
:loading="loading"
|
||||
border
|
||||
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>
|
||||
</template>
|
||||
<template #col-sizeLimit="{ row }">
|
||||
<span class="pre-wrap">{{ row.sizeLimit }}</span>
|
||||
</template>
|
||||
<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:redline: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="datetime" format="YYYY-MM-DD HH:00" value-format="YYYY-MM-DD HH:00:00" 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="datetime" format="YYYY-MM-DD HH:00" value-format="YYYY-MM-DD HH:00:00" 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 { listRedlineCountries, updateRedlineCountry } from '@/api/price/redline'
|
||||
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' },
|
||||
{ prop: 'zoneName', label: '分区名称', width: 140, showOverflowTooltip: true, formatter: (row) => row.zoneName || '-' },
|
||||
{
|
||||
prop: 'weightMin',
|
||||
label: '重量段(KG)',
|
||||
width: 130,
|
||||
align: 'center',
|
||||
formatter: (row) => (row.weightMin != null || row.weightMax != null
|
||||
? formatWeightLabel(row.weightMin, row.weightMax)
|
||||
: '-')
|
||||
},
|
||||
{ prop: 'freightPrice', label: '运费(RMB/KG)', width: 110, align: 'right' },
|
||||
{ prop: 'registrationFee', label: '挂号费(RMB/票)', width: 120, align: 'right' },
|
||||
{ prop: 'sizeLimit', label: '尺寸限制', minWidth: 140 },
|
||||
{ prop: 'lastMile', label: '尾程', minWidth: 110 },
|
||||
{ prop: 'leadTime', label: '时效', width: 100, showOverflowTooltip: true },
|
||||
{ prop: 'effectiveDate', label: '生效时间', width: 160, 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: 'action', label: '操作', width: 70, fixed: 'right', slot: 'action' }
|
||||
]
|
||||
|
||||
const visible = ref(false)
|
||||
const title = ref('')
|
||||
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()
|
||||
const st = status.value
|
||||
return list.value.filter(r => {
|
||||
if (kw && !(String(r.countryName || '').toLowerCase().includes(kw) ||
|
||||
String(r.countryCode || '').toLowerCase().includes(kw) ||
|
||||
String(r.zoneCode || '').toLowerCase().includes(kw) ||
|
||||
String(r.zoneName || '').toLowerCase().includes(kw))) return false
|
||||
if (st && String(r.status) !== String(st)) return false
|
||||
return true
|
||||
})
|
||||
})
|
||||
|
||||
function formatEffDate(v) {
|
||||
if (!v) return undefined
|
||||
return String(v).slice(0, 10)
|
||||
}
|
||||
|
||||
function load() {
|
||||
if (!currentProduct.value) return
|
||||
const row = currentProduct.value
|
||||
loading.value = true
|
||||
listRedlineCountries(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 || ''))
|
||||
if (c !== 0) return c
|
||||
return String(a.zoneCode || '').localeCompare(String(b.zoneCode || ''))
|
||||
})
|
||||
}).finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
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({})
|
||||
function normalizeDrawerHour(val) {
|
||||
if (val == null || val === '') return ''
|
||||
const s = String(val).trim().replace('T', ' ')
|
||||
const m = s.match(/^(\d{4}-\d{2}-\d{2})[ ](\d{2})/)
|
||||
if (m) return m[1] + ' ' + m[2] + ':00:00'
|
||||
if (/^\d{4}-\d{2}-\d{2}$/.test(s)) return s + ' 00:00:00'
|
||||
return s
|
||||
}
|
||||
|
||||
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 ? normalizeDrawerHour(row.effectiveDate) : '',
|
||||
expireDate: row.expireDate ? normalizeDrawerHour(row.expireDate) : '',
|
||||
})
|
||||
editVisible.value = true
|
||||
nextTick(() => editFormRef.value?.clearValidate())
|
||||
}
|
||||
|
||||
function saveEdit() {
|
||||
editFormRef.value.validate(valid => {
|
||||
if (!valid) return
|
||||
saving.value = true
|
||||
updateRedlineCountry({ ...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 || '')
|
||||
}
|
||||
|
||||
function spanMethod({ column, rowIndex }) {
|
||||
if (!['countryName', 'countryCode', 'zoneCode', 'zoneName', 'zoneId'].includes(column.property)) return
|
||||
const rows = filteredList.value
|
||||
if (!rows.length) return
|
||||
const cur = rowGroupKey(rows[rowIndex])
|
||||
if (rowIndex > 0 && rowGroupKey(rows[rowIndex - 1]) === cur) {
|
||||
return { rowspan: 0, colspan: 0 }
|
||||
}
|
||||
let count = 1
|
||||
while (rowIndex + count < rows.length && rowGroupKey(rows[rowIndex + count]) === cur) count++
|
||||
return { rowspan: count, colspan: 1 }
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
|
||||
<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>
|
||||
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<el-drawer v-model="visible" size="860px" :title="title" append-to-body>
|
||||
<el-table v-loading="loading" :data="list" border max-height="calc(100vh - 160px)" empty-text="该产品暂无导入附件">
|
||||
<el-table-column label="文件名称" prop="fileName" min-width="220" show-overflow-tooltip />
|
||||
<el-table-column label="生效时间" prop="effectiveTime" width="170" align="center" />
|
||||
<el-table-column label="大小" width="100" align="right">
|
||||
<template #default="{ row }">{{ formatSize(row.fileSize) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="上传人" prop="createBy" width="110" align="center" show-overflow-tooltip />
|
||||
<el-table-column label="上传时间" prop="createTime" width="170" align="center" />
|
||||
<el-table-column label="操作" width="90" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" :disabled="!row.filePath" @click="handleDownload(row)">下载</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { listProductExcelFiles } from '@/api/price/redline'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const visible = ref(false)
|
||||
const title = ref('')
|
||||
const loading = ref(false)
|
||||
const list = ref([])
|
||||
const product = ref(null)
|
||||
|
||||
function open(row) {
|
||||
product.value = row
|
||||
title.value = (row.productCode || '') + ' · ' + (row.productName || '') + ' · 导入附件'
|
||||
visible.value = true
|
||||
loadList()
|
||||
}
|
||||
|
||||
function loadList() {
|
||||
if (!product.value || !product.value.productCode) return
|
||||
loading.value = true
|
||||
listProductExcelFiles(product.value.productCode).then(res => {
|
||||
list.value = res.data || []
|
||||
}).finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function formatSize(bytes) {
|
||||
const n = Number(bytes)
|
||||
if (!n || n < 0) return '-'
|
||||
if (n < 1024) return n + ' B'
|
||||
if (n < 1024 * 1024) return (n / 1024).toFixed(1) + ' KB'
|
||||
return (n / (1024 * 1024)).toFixed(2) + ' MB'
|
||||
}
|
||||
|
||||
function handleDownload(row) {
|
||||
if (!row || !row.filePath) {
|
||||
proxy.$modal.msgWarning('文件路径不存在')
|
||||
return
|
||||
}
|
||||
proxy.$download.resource(row.filePath)
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
@@ -0,0 +1,148 @@
|
||||
<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="120" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
title="价格"
|
||||
icon="Document"
|
||||
@click="excelFileRef.open(row)"
|
||||
v-hasPermi="['price:redline:query']"
|
||||
></el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
title="红线价明细"
|
||||
icon="View"
|
||||
@click="emit('view-country', row)"
|
||||
v-hasPermi="['price:redline: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"
|
||||
/>
|
||||
<redline-excel-file-drawer ref="excelFileRef" />
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { listRedline } from '@/api/price/redline'
|
||||
import RedlineExcelFileDrawer from './RedlineExcelFileDrawer.vue'
|
||||
|
||||
const emit = defineEmits(['view-country'])
|
||||
const { proxy } = getCurrentInstance()
|
||||
const { bms_product_effect_status } = useDict('bms_product_effect_status')
|
||||
|
||||
const excelFileRef = ref()
|
||||
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)
|
||||
}
|
||||
listRedline(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>
|
||||
@@ -0,0 +1,167 @@
|
||||
<template>
|
||||
<el-dialog :title="title" v-model="visible" width="640px" append-to-body>
|
||||
<basic-form ref="formRef" v-model="form" :schemas="schemas" label-width="100px" />
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="visible = false">取 消</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import BasicForm from '@/components/BasicForm'
|
||||
import { getRedline, addRedline, updateRedline } from '@/api/price/redline'
|
||||
|
||||
const emit = defineEmits(['success'])
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const formRef = ref()
|
||||
const visible = ref(false)
|
||||
const title = ref('')
|
||||
const form = ref(emptyForm())
|
||||
|
||||
const schemas = computed(() => [
|
||||
{
|
||||
label: '产品代码',
|
||||
prop: 'productCode',
|
||||
component: 'input',
|
||||
placeholder: '如 WSP037',
|
||||
span: 12,
|
||||
width: '100%',
|
||||
disabled: !!form.value.productId,
|
||||
props: { maxlength: 20 },
|
||||
rules: [{ required: true, message: '产品代码不能为空', trigger: 'blur' }]
|
||||
},
|
||||
{
|
||||
label: '产品名称',
|
||||
prop: 'productName',
|
||||
component: 'input',
|
||||
placeholder: '产品名称',
|
||||
span: 12,
|
||||
width: '100%',
|
||||
props: { maxlength: 100 },
|
||||
rules: [{ required: true, message: '产品名称不能为空', trigger: 'blur' }]
|
||||
},
|
||||
{
|
||||
label: '时效等级',
|
||||
prop: 'serviceLevel',
|
||||
component: 'input',
|
||||
placeholder: '如 标准',
|
||||
span: 12,
|
||||
width: '100%',
|
||||
props: { maxlength: 20 }
|
||||
},
|
||||
{
|
||||
label: '走货属性',
|
||||
prop: 'cargoAttr',
|
||||
component: 'input',
|
||||
placeholder: '普货/带电等',
|
||||
span: 12,
|
||||
width: '100%',
|
||||
props: { maxlength: 100 }
|
||||
},
|
||||
{
|
||||
label: '目的区域',
|
||||
prop: 'destRegion',
|
||||
component: 'input',
|
||||
placeholder: '如 欧盟',
|
||||
span: 12,
|
||||
width: '100%',
|
||||
props: { maxlength: 50 }
|
||||
},
|
||||
// {
|
||||
// label: '税模式',
|
||||
// prop: 'taxMode',
|
||||
// component: 'select',
|
||||
// placeholder: '请选择',
|
||||
// dict: 'bms_tax_mode',
|
||||
// span: 12,
|
||||
// width: '100%',
|
||||
// rules: [{ required: true, message: '请选择税模式', trigger: 'change' }]
|
||||
// },
|
||||
{
|
||||
label: '生效时间',
|
||||
prop: 'effectiveDate',
|
||||
component: 'datetime',
|
||||
placeholder: '选择时间',
|
||||
span: 12,
|
||||
width: '100%',
|
||||
rules: [{ required: true, message: '生效时间不能为空', trigger: 'change' }]
|
||||
},
|
||||
{
|
||||
label: '失效时间',
|
||||
prop: 'expireDate',
|
||||
component: 'datetime',
|
||||
placeholder: '选择时间',
|
||||
span: 12,
|
||||
width: '100%'
|
||||
},
|
||||
{
|
||||
label: '更新内容',
|
||||
prop: 'updateNote',
|
||||
component: 'textarea',
|
||||
span: 24,
|
||||
width: '100%',
|
||||
props: { rows: 2, maxlength: 500 }
|
||||
},
|
||||
{
|
||||
label: '备注',
|
||||
prop: 'remark',
|
||||
component: 'textarea',
|
||||
span: 24,
|
||||
width: '100%',
|
||||
props: { rows: 2, maxlength: 500 }
|
||||
}
|
||||
])
|
||||
|
||||
function emptyForm() {
|
||||
return {
|
||||
productId: undefined,
|
||||
productCode: undefined,
|
||||
productName: undefined,
|
||||
serviceLevel: undefined,
|
||||
cargoAttr: undefined,
|
||||
destRegion: undefined,
|
||||
updateNote: undefined,
|
||||
taxMode: 'TAX_INCLUDED',
|
||||
effectiveDate: undefined,
|
||||
expireDate: undefined,
|
||||
status: '0',
|
||||
remark: undefined
|
||||
}
|
||||
}
|
||||
|
||||
function reset() {
|
||||
form.value = emptyForm()
|
||||
nextTick(() => formRef.value?.clearValidate())
|
||||
}
|
||||
|
||||
function open(row) {
|
||||
reset()
|
||||
if (!row) {
|
||||
title.value = '新建红线价'
|
||||
visible.value = true
|
||||
return
|
||||
}
|
||||
getRedline(row.productId).then(res => {
|
||||
form.value = { ...emptyForm(), ...(res.data || {}) }
|
||||
title.value = '修改产品'
|
||||
visible.value = true
|
||||
nextTick(() => formRef.value?.clearValidate())
|
||||
})
|
||||
}
|
||||
|
||||
function submitForm() {
|
||||
formRef.value.validate(valid => {
|
||||
if (!valid) return
|
||||
const req = form.value.productId ? updateRedline(form.value) : addRedline(form.value)
|
||||
req.then(() => {
|
||||
proxy.$modal.msgSuccess('操作成功')
|
||||
visible.value = false
|
||||
emit('success')
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
@@ -0,0 +1,148 @@
|
||||
<template>
|
||||
<el-drawer v-model="visible" size="920px" :title="title" append-to-body>
|
||||
<div class="mb8" style="display: flex; justify-content: space-between">
|
||||
<el-button type="primary" plain icon="Plus" @click="handleRelAdd" v-hasPermi="['price:surcharge:add']">新增关联</el-button>
|
||||
</div>
|
||||
<el-table v-loading="loading" :data="list" border max-height="calc(100vh - 160px)" empty-text="该红线价暂无关联附加费">
|
||||
<el-table-column label="附加费名称" prop="scName" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="价格类型" width="100" align="center">
|
||||
<template #default="{ row }">{{ priceTypeLabel(row.priceType) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="计费方式" width="110" align="center">
|
||||
<template #default="{ row }">{{ chargeTypeLabel(row.chargeType) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="计费数值" width="140" align="right">
|
||||
<template #default="{ row }">
|
||||
<span class="charge-value">{{ formatChargeValue(row) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="取值方式" width="100" align="center">
|
||||
<template #default="{ row }">{{ roundModeLabel(row.roundMode) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="适用国家" width="110" align="center">
|
||||
<template #default="{ row }">{{ countryLabel(row.countryCode) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="作用范围" width="170" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.detailId == null" type="primary" size="small" effect="plain">产品级 · 全部明细</el-tag>
|
||||
<el-tag v-else type="warning" size="small" effect="plain">{{ row.countryName || '-' }} · {{ row.weightMin }}-{{ row.weightMax }}kg</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="80" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="String(row.ssCode).toLowerCase() === 'on' ? 'success' : 'info'" size="small">
|
||||
{{ String(row.ssCode).toLowerCase() === 'on' ? '有效' : '停用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" prop="scNote" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="90" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="danger" icon="Delete" @click="handleRelDelete(row.relId)" v-hasPermi="['price:surcharge:remove']">解绑</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<redline-surcharge-rel-dialog ref="relRef" @success="handleRelSuccess" />
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted } from 'vue'
|
||||
import { listSurchargeByProduct, delSurchargeRel } from '@/api/price/surcharge'
|
||||
import { listRegionOptions } from '@/api/data/region'
|
||||
import RedlineSurchargeRelDialog from './RedlineSurchargeRelDialog.vue'
|
||||
|
||||
const emit = defineEmits(['success'])
|
||||
const { proxy } = getCurrentInstance()
|
||||
const { bms_round_mode, bms_charge_type } = useDict('bms_round_mode', 'bms_charge_type')
|
||||
|
||||
const relRef = ref()
|
||||
const visible = ref(false)
|
||||
const title = ref('')
|
||||
const loading = ref(false)
|
||||
const list = ref([])
|
||||
const product = ref(null)
|
||||
const regionOptions = ref([])
|
||||
|
||||
function open(row) {
|
||||
product.value = row
|
||||
title.value = row.productCode + ' · ' + row.productName + ' · 附加费关联'
|
||||
visible.value = true
|
||||
loadList()
|
||||
}
|
||||
|
||||
function loadList() {
|
||||
if (!product.value) return
|
||||
loading.value = true
|
||||
listSurchargeByProduct(product.value.productCode, 'REDLINE').then(res => {
|
||||
const rows = res.data || []
|
||||
list.value = product.value && product.value.productId != null
|
||||
? rows.filter(r => Number(r.productId) === Number(product.value.productId) || r.priceType === 'REDLINE')
|
||||
: rows
|
||||
}).finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function handleRelAdd() {
|
||||
relRef.value.open(product.value)
|
||||
}
|
||||
|
||||
function handleRelSuccess() {
|
||||
loadList()
|
||||
emit('success')
|
||||
}
|
||||
|
||||
function handleRelDelete(relId) {
|
||||
proxy.$modal.confirm('是否确认解绑该附加费?').then(() => {
|
||||
return delSurchargeRel(relId)
|
||||
}).then(() => {
|
||||
proxy.$modal.msgSuccess('解绑成功')
|
||||
loadList()
|
||||
emit('success')
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
function priceTypeLabel(val) {
|
||||
if (val === 'REDLINE') return '红线价'
|
||||
if (val === 'SALES') return '销售价'
|
||||
return '产品价'
|
||||
}
|
||||
|
||||
function chargeTypeLabel(val) {
|
||||
const found = bms_charge_type.value.find(c => c.value === val)
|
||||
return found ? found.label : (val || '-')
|
||||
}
|
||||
|
||||
function roundModeLabel(val) {
|
||||
const found = bms_round_mode.value.find(d => d.value === val)
|
||||
return found ? found.label : (val || '-')
|
||||
}
|
||||
|
||||
function formatChargeValue(row) {
|
||||
if (row.chargeValue == null) return '-'
|
||||
const v = Number(row.chargeValue)
|
||||
if (row.chargeType === 'RATE') return (v * 100).toFixed(2) + '%'
|
||||
return v + ' ' + (row.currency || 'CNY')
|
||||
}
|
||||
|
||||
function countryLabel(code) {
|
||||
if (!code) return '全部'
|
||||
const found = regionOptions.value.find(r => r.countryCode2 === code)
|
||||
return found ? (found.countryNameCn || found.countryNameEn || code) : code
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
listRegionOptions().then(res => {
|
||||
regionOptions.value = res.data || []
|
||||
})
|
||||
})
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mb8 { margin-bottom: 8px; }
|
||||
.charge-value { font-weight: 600; color: #d63031; }
|
||||
</style>
|
||||
@@ -0,0 +1,121 @@
|
||||
<template>
|
||||
<el-dialog :title="title" v-model="visible" width="560px" append-to-body>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
|
||||
<el-form-item label="附加费" prop="scId">
|
||||
<el-select v-model="form.scId" placeholder="请选择附加费" filterable style="width: 100%">
|
||||
<el-option v-for="s in surchargePool" :key="s.scId" :label="s.scName" :value="s.scId" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="价格类型">
|
||||
<el-tag type="danger" effect="plain">红线价</el-tag>
|
||||
</el-form-item>
|
||||
<el-form-item label="适用国家" prop="countryCode">
|
||||
<el-select v-model="form.countryCode" clearable filterable placeholder="空=全部国家" style="width: 100%">
|
||||
<el-option
|
||||
v-for="r in regionOptions"
|
||||
:key="r.countryCode2"
|
||||
:label="(r.countryNameCn || r.countryNameEn || r.countryCode2) + ' (' + r.countryCode2 + ')'"
|
||||
:value="r.countryCode2"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="作用范围" prop="scope">
|
||||
<el-radio-group v-model="form.scope">
|
||||
<el-radio value="PRODUCT">产品级(全部明细)</el-radio>
|
||||
<el-radio value="DETAIL">明细级(指定国家×重量段)</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="form.scope === 'DETAIL'" label="明细" prop="detailId">
|
||||
<el-select v-model="form.detailId" placeholder="请选择明细" filterable style="width: 100%">
|
||||
<el-option v-for="d in countryList" :key="d.detailId" :label="detailLabel(d)" :value="d.detailId" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" @click="submit">确 定</el-button>
|
||||
<el-button @click="visible = false">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { listRedlineCountries } from '@/api/price/redline'
|
||||
import { listSurcharge, addSurchargeRel } from '@/api/price/surcharge'
|
||||
import { listRegionOptions } from '@/api/data/region'
|
||||
|
||||
const emit = defineEmits(['success'])
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const formRef = ref()
|
||||
const visible = ref(false)
|
||||
const title = ref('新增附加费关联')
|
||||
const product = ref(null)
|
||||
const surchargePool = ref([])
|
||||
const countryList = ref([])
|
||||
const regionOptions = ref([])
|
||||
const form = ref({})
|
||||
const rules = {
|
||||
scId: [{ required: true, message: '请选择附加费', trigger: 'change' }],
|
||||
|
||||
}
|
||||
|
||||
function detailLabel(d) {
|
||||
const w = d.weightMin != null && d.weightMax != null ? (d.weightMin + '-' + d.weightMax + 'kg') : '-'
|
||||
return (d.countryName || d.countryCode || '-') + ' · ' + w
|
||||
}
|
||||
|
||||
function reset() {
|
||||
form.value = {
|
||||
scId: undefined,
|
||||
priceType: 'REDLINE',
|
||||
countryCode: undefined,
|
||||
scope: 'PRODUCT',
|
||||
detailId: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
||||
function open(row) {
|
||||
product.value = row
|
||||
reset()
|
||||
visible.value = true
|
||||
listSurcharge({ ssCode: 'on', pageNum: 1, pageSize: 1000 }).then(res => {
|
||||
surchargePool.value = res.rows || []
|
||||
})
|
||||
listRegionOptions().then(res => {
|
||||
regionOptions.value = res.data || []
|
||||
})
|
||||
if (row && row.productCode) {
|
||||
listRedlineCountries(row.productCode, undefined, row.productId).then(res => {
|
||||
countryList.value = res.data || []
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function submit() {
|
||||
formRef.value.validate(valid => {
|
||||
if (!valid) return
|
||||
if (form.value.scope === 'DETAIL' && !form.value.detailId) {
|
||||
proxy.$modal.msgError('请选择明细')
|
||||
return
|
||||
}
|
||||
const payload = {
|
||||
scId: form.value.scId,
|
||||
productId: product.value ? product.value.productId : undefined,
|
||||
productCode: product.value ? product.value.productCode : undefined,
|
||||
detailId: form.value.scope === 'DETAIL' ? form.value.detailId : null,
|
||||
priceType: 'REDLINE',
|
||||
countryCode: form.value.countryCode || null
|
||||
}
|
||||
addSurchargeRel(payload).then(() => {
|
||||
proxy.$modal.msgSuccess('关联成功')
|
||||
visible.value = false
|
||||
emit('success')
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
@@ -0,0 +1,375 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-alert
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
class="mb16"
|
||||
title="导入前须先选择生效时间;红线价与国家明细均按此时间落库,上一版产品/明细失效时间设为该时间。多国家价表用「全部导入」,单国专线成本价用「按国家导入」。相同产品代码+生效日期不覆盖主数据。"
|
||||
/>
|
||||
|
||||
<basic-table
|
||||
ref="tableRef"
|
||||
:extra-params="extraParams"
|
||||
:columns="columns"
|
||||
:search-columns="searchColumns"
|
||||
:query="queryList"
|
||||
row-key="productId"
|
||||
>
|
||||
<template #toolbar="{ selection }">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['price:redline:add']">新建红线价</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-dropdown @command="handleImportCommand">
|
||||
<el-button type="info" plain icon="Upload" v-hasPermi="['price:redline:import']">
|
||||
导入 Excel<el-icon class="el-icon--right"><arrow-down /></el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="all">全部导入(多国家价表)</el-dropdown-item>
|
||||
<el-dropdown-item command="country">按国家导入(单国专线成本价)</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button plain icon="Clock" @click="expiredRef.open()" v-hasPermi="['price:redline:list']">失效价格</el-button>
|
||||
</el-col>
|
||||
</template>
|
||||
|
||||
<template #col-cargoAttr="{ row }">
|
||||
<el-tag type="primary" size="small" effect="plain">{{ row.cargoAttr || '-' }}</el-tag>
|
||||
</template>
|
||||
|
||||
<template #col-status="{ row }">
|
||||
<dict-tag :options="bms_product_effect_status" :value="row.status" />
|
||||
</template>
|
||||
|
||||
<template #col-action="{ row }">
|
||||
<el-button link type="primary" title="价格" icon="Document" @click="excelFileRef.open(row)" v-hasPermi="['price:redline:query']"></el-button>
|
||||
<el-button link type="primary" title="红线价明细" icon="View" @click="countryRef.open(row)" v-hasPermi="['price:redline: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:redline:edit']"></el-button>
|
||||
</template>
|
||||
</basic-table>
|
||||
|
||||
<redline-form-dialog ref="formRef" @success="() => tableRef.refresh()" />
|
||||
<redline-excel-file-drawer ref="excelFileRef" />
|
||||
<redline-country-drawer ref="countryRef" />
|
||||
<redline-surcharge-drawer ref="surchargeRef" @success="() => tableRef.refresh()" />
|
||||
<redline-expired-drawer ref="expiredRef" @view-country="(row) => countryRef.open(row)" />
|
||||
<excel-import-dialog
|
||||
ref="importRef"
|
||||
title="红线价 Excel 导入"
|
||||
width="560px"
|
||||
:show-update-support="false"
|
||||
template-url="/templates/product/product-import-all-template.xlsx"
|
||||
template-file-name="产品多国家价表导入模板"
|
||||
:import-desc="allImportDesc"
|
||||
:before-submit="validateImportEffectiveDate"
|
||||
:submit-handler="runImportAllAsync"
|
||||
@success="() => tableRef.refresh()"
|
||||
>
|
||||
<template #extra>
|
||||
<el-form label-width="90px">
|
||||
<el-form-item label="生效时间" required>
|
||||
<el-date-picker
|
||||
v-model="importEffectiveDate"
|
||||
type="datetime"
|
||||
format="YYYY-MM-DD HH:00"
|
||||
value-format="YYYY-MM-DD HH:00:00"
|
||||
popper-class="redline-import-hour-picker"
|
||||
placeholder="请选择生效时间"
|
||||
style="width: 100%"
|
||||
@change="onImportEffectiveTimeChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
</excel-import-dialog>
|
||||
<excel-import-dialog
|
||||
ref="countryImportRef"
|
||||
title="按国家导入"
|
||||
width="560px"
|
||||
:show-update-support="false"
|
||||
:before-submit="validateCountryImport"
|
||||
:import-desc="countryImportDesc"
|
||||
template-url="/templates/product/product-import-country-template.xlsx"
|
||||
template-file-name="产品按国家导入模板"
|
||||
:submit-handler="runImportCountryAsync"
|
||||
@success="() => tableRef.refresh()"
|
||||
>
|
||||
<template #extra>
|
||||
<el-form label-width="90px">
|
||||
<el-form-item label="生效时间" required>
|
||||
<el-date-picker
|
||||
v-model="importEffectiveDate"
|
||||
type="datetime"
|
||||
format="YYYY-MM-DD HH:00"
|
||||
value-format="YYYY-MM-DD HH:00:00"
|
||||
popper-class="redline-import-hour-picker"
|
||||
placeholder="请选择生效时间"
|
||||
style="width: 100%"
|
||||
@change="onImportEffectiveTimeChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="目的国家" required>
|
||||
<el-select v-model="importCountryCode" filterable clearable placeholder="请选择国家" style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in regionOptions"
|
||||
:key="item.countryCode2"
|
||||
:label="regionLabel(item)"
|
||||
:value="item.countryCode2"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
</excel-import-dialog>
|
||||
|
||||
<el-dialog
|
||||
v-model="importProgressVisible"
|
||||
title="正在导入产品"
|
||||
width="420px"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
:show-close="false"
|
||||
append-to-body
|
||||
>
|
||||
<div class="import-progress">
|
||||
<el-progress :percentage="importProgressPercent" :status="importProgressStatus" />
|
||||
<div class="import-progress-msg">{{ importProgressMessage }}</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="PriceProduct">
|
||||
import ExcelImportDialog from '@/components/ExcelImportDialog'
|
||||
import RedlineFormDialog from './components/RedlineFormDialog.vue'
|
||||
import RedlineExcelFileDrawer from './components/RedlineExcelFileDrawer.vue'
|
||||
import RedlineCountryDrawer from './components/RedlineCountryDrawer.vue'
|
||||
import RedlineSurchargeDrawer from './components/RedlineSurchargeDrawer.vue'
|
||||
import RedlineExpiredDrawer from './components/RedlineExpiredDrawer.vue'
|
||||
import { listRedline, importRedlineExcelAsync, importRedlineCountryExcelAsync } from '@/api/price/redline'
|
||||
import { listRegionOptions } from '@/api/data/region'
|
||||
|
||||
const { bms_product_effect_status } = useDict('bms_product_effect_status')
|
||||
|
||||
const extraParams = { statusGroup: 'active' }
|
||||
const tableRef = ref()
|
||||
const formRef = ref()
|
||||
const excelFileRef = ref()
|
||||
const countryRef = ref()
|
||||
const surchargeRef = ref()
|
||||
const expiredRef = ref()
|
||||
const importRef = ref()
|
||||
const countryImportRef = ref()
|
||||
const importCountryCode = ref('')
|
||||
const importEffectiveDate = ref('')
|
||||
|
||||
function pad2(n) {
|
||||
return String(n).padStart(2, '0')
|
||||
}
|
||||
|
||||
function formatImportHour(d) {
|
||||
if (!d || Number.isNaN(d.getTime())) return undefined
|
||||
return d.getFullYear() + '-' + pad2(d.getMonth() + 1) + '-' + pad2(d.getDate())
|
||||
+ ' ' + pad2(d.getHours()) + ':00:00'
|
||||
}
|
||||
|
||||
function normalizeImportToHour(val) {
|
||||
if (val == null || val === '') return val
|
||||
if (val instanceof Date) return formatImportHour(val)
|
||||
const s = String(val).trim().replace('T', ' ')
|
||||
const m = s.match(/^(\d{4}-\d{2}-\d{2})[ ](\d{2})/)
|
||||
if (m) return m[1] + ' ' + m[2] + ':00:00'
|
||||
if (/^\d{4}-\d{2}-\d{2}$/.test(s)) return s + ' 00:00:00'
|
||||
return formatImportHour(new Date(s.replace(/-/g, '/'))) || val
|
||||
}
|
||||
|
||||
function onImportEffectiveTimeChange(val) {
|
||||
importEffectiveDate.value = normalizeImportToHour(val) || ''
|
||||
}
|
||||
const regionOptions = ref([])
|
||||
const importProgressVisible = ref(false)
|
||||
const importProgressPercent = ref(0)
|
||||
const importProgressMessage = ref('')
|
||||
const importProgressStatus = ref(undefined)
|
||||
const allImportDesc = [
|
||||
'适用多国家价表(B2C 小包价表等),按目录 sheet 匹配国家导入。',
|
||||
'必选生效时间:产品与 product_country 明细均按此时间计费;上一版产品/明细失效时间设为该时间。',
|
||||
'请在弹窗底部下载模板,按模板格式整理后上传。相同产品代码+生效日期不覆盖主数据。'
|
||||
]
|
||||
const countryImportDesc = [
|
||||
'选择目的国家后上传专线成本价表,目录中的产品都会记到该国家。',
|
||||
'必选生效时间:产品与 product_country 明细均按此时间计费;上一版产品/明细失效时间设为该时间。',
|
||||
'邮编、赔偿标准等 sheet 不导入。可在弹窗底部下载模板。'
|
||||
]
|
||||
|
||||
const columns = [
|
||||
{ prop: 'productCode', label: '产品代码', minWidth: 110 },
|
||||
{ prop: 'productName', label: '产品名称', minWidth: 140, showOverflowTooltip: true },
|
||||
{ prop: 'serviceLevel', label: '时效等级', width: 90, align: 'center' },
|
||||
{ prop: 'cargoAttr', label: '走货属性', minWidth: 120, slot: 'cargoAttr' },
|
||||
{ prop: 'destRegion', label: '目的区域', width: 90, align: 'center' },
|
||||
{ prop: 'effectiveDate', label: '生效时间', width: 160, align: 'center' },
|
||||
{ prop: 'expireDate', label: '失效时间', width: 160, align: 'center' },
|
||||
{ prop: 'countryCount', label: '国家数', width: 80, align: 'center' },
|
||||
{ prop: 'surchargeCount', label: '附加费数', width: 90, align: 'center' },
|
||||
{ prop: 'status', label: '状态', width: 90, align: 'center', slot: 'status' },
|
||||
{ prop: 'updateNote', label: '更新内容', minWidth: 160, showOverflowTooltip: true },
|
||||
{ prop: 'action', label: '操作', width: 240, fixed: 'right', slot: 'action' }
|
||||
]
|
||||
|
||||
const searchColumns = [
|
||||
{ prop: 'productCode', label: '产品代码', component: 'input', placeholder: '产品代码' },
|
||||
{ prop: 'productName', label: '产品名称', component: 'input', placeholder: '产品名称' }
|
||||
]
|
||||
|
||||
function queryList(params) {
|
||||
return listRedline(params)
|
||||
}
|
||||
|
||||
function handleAdd() {
|
||||
formRef.value.open()
|
||||
}
|
||||
|
||||
function handleUpdate(row) {
|
||||
formRef.value.open(row)
|
||||
}
|
||||
|
||||
function handleImportCommand(command) {
|
||||
if (command === 'country') {
|
||||
handleCountryImport()
|
||||
return
|
||||
}
|
||||
handleImport()
|
||||
}
|
||||
|
||||
function handleImport() {
|
||||
importEffectiveDate.value = ''
|
||||
importRef.value.open()
|
||||
}
|
||||
|
||||
function regionLabel(item) {
|
||||
const name = item.countryNameCn || item.countryNameEn || ''
|
||||
const code = item.countryCode2 || ''
|
||||
return name && code ? name + ' (' + code + ')' : (name || code)
|
||||
}
|
||||
|
||||
function loadRegionOptions() {
|
||||
if (regionOptions.value.length) return
|
||||
listRegionOptions().then(res => {
|
||||
regionOptions.value = res.data || []
|
||||
})
|
||||
}
|
||||
|
||||
function handleCountryImport() {
|
||||
importCountryCode.value = ''
|
||||
importEffectiveDate.value = ''
|
||||
loadRegionOptions()
|
||||
countryImportRef.value.open()
|
||||
}
|
||||
|
||||
function validateImportEffectiveDate() {
|
||||
if (!importEffectiveDate.value) {
|
||||
return '请先选择生效时间'
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
function validateCountryImport() {
|
||||
const effErr = validateImportEffectiveDate()
|
||||
if (effErr) return effErr
|
||||
if (!importCountryCode.value) {
|
||||
return '请先选择目的国家'
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
function applyImportProgress(t) {
|
||||
importProgressPercent.value = Math.min(100, Number(t.percent) || 0)
|
||||
if (t.status === 'PENDING') importProgressMessage.value = '排队中…'
|
||||
else if (t.status === 'RUNNING') {
|
||||
const m = t.message || ''
|
||||
if (m === 'parsing excel') importProgressMessage.value = '正在解析价表…'
|
||||
else if (m === 'finishing') importProgressMessage.value = '收尾中…'
|
||||
else if (m === 'queued') importProgressMessage.value = '排队中…'
|
||||
else if (m.indexOf('importing ') === 0) {
|
||||
const rest = m.slice('importing '.length)
|
||||
importProgressMessage.value = '正在导入产品 ' + rest
|
||||
} else importProgressMessage.value = m || '正在导入…'
|
||||
} else if (t.status === 'SUCCESS') {
|
||||
importProgressMessage.value = t.message || '导入完成'
|
||||
importProgressStatus.value = 'success'
|
||||
} else if (t.status === 'FAIL') {
|
||||
importProgressMessage.value = t.error || t.message || '导入失败'
|
||||
importProgressStatus.value = 'exception'
|
||||
}
|
||||
}
|
||||
|
||||
async function runImportWithProgress(runner) {
|
||||
importProgressVisible.value = true
|
||||
importProgressPercent.value = 0
|
||||
importProgressMessage.value = '上传并解析中…'
|
||||
importProgressStatus.value = undefined
|
||||
try {
|
||||
const { task } = await runner(applyImportProgress)
|
||||
importProgressPercent.value = 100
|
||||
importProgressStatus.value = 'success'
|
||||
return (task && task.message) ? task.message : '导入完成'
|
||||
} catch (e) {
|
||||
importProgressStatus.value = 'exception'
|
||||
const tip = (e && e.task && e.task.error) || (e && e.message) || '导入失败'
|
||||
importProgressMessage.value = tip
|
||||
await new Promise(resolve => setTimeout(resolve, 600))
|
||||
throw e
|
||||
} finally {
|
||||
importProgressVisible.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function runImportAllAsync(uploadFile) {
|
||||
const raw = uploadFile && (uploadFile.raw || uploadFile)
|
||||
if (!raw) {
|
||||
throw new Error('请先选择文件')
|
||||
}
|
||||
if (!importEffectiveDate.value) {
|
||||
throw new Error('请先选择生效时间')
|
||||
}
|
||||
const eff = importEffectiveDate.value
|
||||
return runImportWithProgress((onProgress) => importRedlineExcelAsync(raw, eff, onProgress))
|
||||
}
|
||||
|
||||
async function runImportCountryAsync(uploadFile) {
|
||||
const raw = uploadFile && (uploadFile.raw || uploadFile)
|
||||
if (!raw) {
|
||||
throw new Error('请先选择文件')
|
||||
}
|
||||
if (!importEffectiveDate.value) {
|
||||
throw new Error('请先选择生效时间')
|
||||
}
|
||||
if (!importCountryCode.value) {
|
||||
throw new Error('请先选择目的国家')
|
||||
}
|
||||
const code = importCountryCode.value
|
||||
const eff = importEffectiveDate.value
|
||||
return runImportWithProgress((onProgress) => importRedlineCountryExcelAsync(raw, code, eff, onProgress))
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mb16 { margin-bottom: 16px; }
|
||||
.import-progress { padding: 8px 4px 4px; }
|
||||
.import-progress-msg {
|
||||
margin-top: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
word-break: break-word;
|
||||
max-height: 160px;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user