应付配置文件导入保存该文件

This commit is contained in:
liuminghui
2026-11-05 10:31:02 +08:00
parent 51a89b18e1
commit 116b3b74e4
21 changed files with 557 additions and 31 deletions
@@ -21,6 +21,7 @@ import com.bms.common.core.domain.AjaxResult;
import com.bms.common.core.page.TableDataInfo;
import com.bms.common.enums.BusinessType;
import com.bms.common.exception.ServiceException;
import com.bms.common.utils.file.FileUtils;
import com.bms.common.utils.poi.ExcelUtil;
import com.bms.price.domain.ChannelProductMap;
import com.bms.price.domain.PayProduct;
@@ -86,6 +87,18 @@ public class PayProductController extends BaseController
return success(payOrderCostService.generate(billId));
}
/** 下载某一版导入时上传的源文件 */
@PreAuthorize("@ss.hasPermi('price:pay:query')")
@PostMapping("/{productId}/source")
public void downloadSource(@PathVariable Long productId, HttpServletResponse response) throws Exception
{
String filename = payProductService.sourceFileName(productId);
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
FileUtils.setAttachmentResponseHeader(response, filename);
payProductService.writeSource(productId, response.getOutputStream());
}
@PreAuthorize("@ss.hasPermi('price:pay:query')")
@GetMapping("/{productId}")
public AjaxResult getInfo(@PathVariable Long productId)
@@ -52,6 +52,7 @@ class PayWorkbookParserTest
.filter(fee -> "高风险地区附加费".equals(fee.getFeeName()) && "AF".equals(fee.getCalcParam())).findFirst().orElseThrow();
assertEquals("Afghanistan", afghan.getZoneName());
assertEquals("RMB", afghan.getCurrency());
assertEquals("RMB/KG", afghan.getPriceUnit());
assertTrue(afghan.getChargeText().contains("10.4") && afghan.getChargeText().contains("RMB/kg"));
assertTrue(decimalFee(afghan, "10.4"));
assertEquals("MANUAL", afghan.getCalcRule());
@@ -532,6 +533,40 @@ class PayWorkbookParserTest
assertTrue(ctt.getWeightBands().stream().noneMatch(band -> band.getWeightLabel() != null && band.getWeightLabel().contains("预估")));
}
@Test
void posteItalianeKeepsTwoDeliveryServicesAndSizeRules() throws Exception
{
Path file = Path.of("D:/payment/PosteItaliane.xlsx");
Assumptions.assumeTrue(Files.exists(file));
PayPreview preview = parser.parseMatched(Files.readAllBytes(file), file.getFileName().toString(), null, List.of("PosteItaliane"));
PayProduct poste = preview.getProducts().get(0);
assertEquals("OK", poste.getParseStatus());
assertEquals(18, poste.getWeightBands().size(), poste.getWeightBands().stream()
.map(band -> band.getChargeName() + "/" + band.getZoneName() + "/" + band.getWeightLabel() + "/" + band.getPrice())
.toList().toString());
assertEquals(9, poste.getWeightBands().stream().filter(band -> "派送费 Crono Express".equals(band.getChargeName())).count());
assertEquals(9, poste.getWeightBands().stream().filter(band -> "派送费 Crono".equals(band.getChargeName())).count());
assertTrue(poste.getWeightBands().stream().anyMatch(band -> "派送费 Crono Express".equals(band.getChargeName())
&& "City".equals(band.getZoneName()) && band.getWeightLabel().contains("0") && decimal(band, "3")
&& "EUR/ITEM".equals(band.getPriceUnit()) && "意大利(IT)".equals(band.getCountryName()) && "IT".equals(band.getCountryCode())));
assertTrue(poste.getWeightBands().stream().anyMatch(band -> "派送费 Crono Express".equals(band.getChargeName())
&& "Province".equals(band.getZoneName()) && band.getWeightLabel().contains("5") && decimal(band, "6.63")));
assertTrue(poste.getWeightBands().stream().anyMatch(band -> "派送费 Crono".equals(band.getChargeName())
&& "Rural".equals(band.getZoneName()) && band.getWeightLabel().contains("2") && decimal(band, "4.38")
&& "意大利(IT)".equals(band.getCountryName())));
assertTrue(poste.getWeightBands().stream().anyMatch(band -> "派送费 Crono".equals(band.getChargeName())
&& "Province".equals(band.getZoneName()) && band.getWeightLabel().contains("5") && decimal(band, "5.83")));
assertTrue(poste.getExtraLines().stream().anyMatch(line -> "配置".equals(line.getLineType())
&& line.getLineText().contains("尺寸限制") && line.getLineText().contains("长+宽+高≤130cm")
&& line.getLineText().contains("最长边≤60cm")));
assertTrue(poste.getExtraLines().stream().anyMatch(line -> line.getLineText().contains("计费说明") && line.getLineText().contains("计费重")));
assertTrue(poste.getSurcharges().stream().anyMatch(fee -> "销毁费".equals(fee.getFeeName()) && decimalFee(fee, "0.2")
&& "EUR/ITEM".equals(fee.getPriceUnit()) && "产生时收取".equals(fee.getChargeTiming())));
assertTrue(poste.getSurcharges().stream().anyMatch(fee -> "退件重派费".equals(fee.getFeeName())
&& fee.getChargeText().contains("同正向运费") && "产生时收取".equals(fee.getChargeTiming())));
assertTrue(poste.getSurcharges().stream().filter(fee -> "产生时收取".equals(fee.getChargeTiming())).count() >= 6);
}
private PayPreview parse(String prefix) throws Exception
{
Path dir = Path.of("D:/应付供应商");
@@ -557,7 +592,8 @@ class PayWorkbookParserTest
&& "速递运费".equals(band.getChargeName()) && decimal(band, "0.23") && "EUR/KG".equals(band.getPriceUnit())));
assertTrue(dhl.getServiceNote() != null && dhl.getServiceNote().contains("报价已含燃油"));
assertTrue(dhl.getSurcharges().stream().anyMatch(fee -> "其他费用".equals(fee.getSectionName())
&& "道路通行费".equals(fee.getFeeName()) && decimalFee(fee, "0.19")));
&& "道路通行费".equals(fee.getFeeName()) && decimalFee(fee, "0.19")
&& "EUR".equals(fee.getCurrency()) && "EUR/ITEM".equals(fee.getPriceUnit())));
assertTrue(dhl.getSurcharges().stream().anyMatch(fee -> "旺季附加费".equals(fee.getFeeName()) && decimalFee(fee, "0.19")
&& fee.getEffectiveStart() != null));
assertTrue(dhl.getSurcharges().stream().anyMatch(fee -> "高峰旺季附加费".equals(fee.getFeeName()) && decimalFee(fee, "0.5")));
@@ -33,16 +33,20 @@ public class ExcelGrid
/** 合并区域左上角横向占几列,其余单元格为 0 */
private final int[][] colSpan;
/** 纵向合并时,被盖住的格子指向左上角行;没有纵向合并是 -1 */
private final int[][] rowAnchor;
private final int rows;
private final int cols;
private ExcelGrid(String[][] text, Double[][] number, String[][] linkSheet, int[][] colSpan, int rows, int cols)
private ExcelGrid(String[][] text, Double[][] number, String[][] linkSheet, int[][] colSpan, int[][] rowAnchor, int rows, int cols)
{
this.text = text;
this.number = number;
this.linkSheet = linkSheet;
this.colSpan = colSpan;
this.rowAnchor = rowAnchor;
this.rows = rows;
this.cols = cols;
}
@@ -88,6 +92,14 @@ public class ExcelGrid
Double[][] number = new Double[Math.max(rows, 0)][Math.max(cols, 0)];
String[][] linkSheet = new String[Math.max(rows, 0)][Math.max(cols, 0)];
int[][] colSpan = new int[Math.max(rows, 0)][Math.max(cols, 0)];
int[][] rowAnchor = new int[Math.max(rows, 0)][Math.max(cols, 0)];
for (int r = 0; r < rowAnchor.length; r++)
{
for (int c = 0; c < rowAnchor[r].length; c++)
{
rowAnchor[r][c] = -1;
}
}
if (sheet != null && rows > 0 && cols > 0)
{
for (int r = 0; r < rows; r++)
@@ -115,9 +127,21 @@ public class ExcelGrid
{
colSpan[r0][c0] = width;
}
int bottom = Math.min(region.getLastRow(), rows - 1);
int right = Math.min(region.getLastColumn(), cols - 1);
if (bottom > r0)
{
for (int r = r0 + 1; r <= bottom; r++)
{
for (int c = c0; c <= right; c++)
{
rowAnchor[r][c] = r0;
}
}
}
}
}
return new ExcelGrid(text, number, linkSheet, colSpan, rows, cols);
return new ExcelGrid(text, number, linkSheet, colSpan, rowAnchor, rows, cols);
}
public int rows()
@@ -139,6 +163,18 @@ public class ExcelGrid
return text[row][col] == null ? "" : text[row][col];
}
/** 本格有字就用本格。纵向合并盖住的空格,用合并区左上角的原文。 */
public String mergedText(int row, int col)
{
String own = text(row, col);
if (!own.isEmpty() || row < 0 || col < 0 || row >= rows || col >= cols)
{
return own;
}
int anchor = rowAnchor[row][col];
return anchor < 0 ? "" : text(anchor, col);
}
public Double number(int row, int col)
{
if (row < 0 || col < 0 || row >= rows || col >= cols)
@@ -14,6 +14,9 @@ public class PayPreview
{
private String sourceFile;
/** 本次价卡在 OSS 上的地址,保存时写到每个产品版本 */
private String filePath;
private String fileMd5;
private String supplierName;
@@ -93,6 +93,9 @@ public class PayProduct extends BaseEntity
@Excel(name = "来源文件", width = 36)
private String sourceFile;
/** 源文件在 OSS 上的地址,形如 oss://bucket/key。历史记录按它下载 */
private String filePath;
private String fileMd5;
@Excel(name = "价卡归属", width = 12)
@@ -37,6 +37,9 @@ public class PayProductTerm extends BaseEntity
private String sourceFile;
/** 这一版源文件的 OSS 地址,空表示导入时还没有存文件 */
private String filePath;
private Integer weightCount;
private Integer surchargeCount;
@@ -30,8 +30,12 @@ public class PaySurcharge
/** 从收费标准里解析出的金额;百分比时这里是百分数 */
private BigDecimal amount;
/** 币种代码,计费时和订单币种比对 */
private String currency;
/** 货币和计费单位,与重量段单位同一写法,如 EUR/ITEM、RMB/KG、% */
private String priceUnit;
/** 费用说明 */
private String feeDesc;
@@ -35,7 +35,7 @@ public final class PayDetailExporter
header(weightSheet, "产品名称", "报价工作表", "区块", "国家", "国家代码", "分区", "重量段", "下限KG", "上限KG",
"体积", "费用项", "单价", "单位");
Sheet feeSheet = workbook.createSheet("附加费");
header(feeSheet, "产品名称", "报价工作表", "区块", "费用名称", "分区", "收费标准", "金额", "币种", "收取时点",
header(feeSheet, "产品名称", "报价工作表", "区块", "费用名称", "分区", "收费标准", "金额", "币种", "单位", "收取时点",
"生效开始", "生效结束", "时效原文", "费用说明");
Sheet extraSheet = workbook.createSheet("其他配置");
header(extraSheet, "产品名称", "报价工作表", "类型", "配置内容");
@@ -121,11 +121,12 @@ public final class PayDetailExporter
cell(row, 5, fee.getChargeText());
cell(row, 6, plain(fee.getAmount()));
cell(row, 7, fee.getCurrency());
cell(row, 8, fee.getChargeTiming());
cell(row, 9, day(fee.getEffectiveStart()));
cell(row, 10, day(fee.getEffectiveEnd()));
cell(row, 11, fee.getPeriodText());
cell(row, 12, fee.getFeeDesc());
cell(row, 8, fee.getPriceUnit());
cell(row, 9, fee.getChargeTiming());
cell(row, 10, day(fee.getEffectiveStart()));
cell(row, 11, day(fee.getEffectiveEnd()));
cell(row, 12, fee.getPeriodText());
cell(row, 13, fee.getFeeDesc());
}
return rowIndex;
}
@@ -384,6 +384,7 @@ public class PayWorkbookParser
copy.setChargeText(fee.getChargeText());
copy.setAmount(fee.getAmount());
copy.setCurrency(fee.getCurrency());
copy.setPriceUnit(fee.getPriceUnit());
copy.setFeeDesc(fee.getFeeDesc());
copy.setPeriodText(fee.getPeriodText());
copy.setEffectiveStart(fee.getEffectiveStart());
@@ -857,9 +858,10 @@ public class PayWorkbookParser
fee.setSectionName(cut(new StringBuilder(sheetName), 200));
fee.setFeeName("高风险地区附加费");
fee.setZoneName(cut(new StringBuilder(country.isEmpty() ? code : country), 200));
fee.setChargeText(cut(new StringBuilder(amount.toPlainString() + " " + currency + (unit.contains("/kg") ? "/kg" : "")), 500));
fee.setAmount(amount);
fee.setCurrency(currency);
fee.setPriceUnit(currency + (unit.toLowerCase(Locale.ROOT).contains("/kg") ? "/KG" : ""));
fee.setChargeText(cut(new StringBuilder(amount.toPlainString() + " " + currency + (unit.contains("/kg") ? "/kg" : "")), 500));
fee.setFeeDesc(desc);
fee.setCalcRule(PaySurchargeRules.MANUAL);
fee.setCalcParam(code);
@@ -1090,6 +1092,12 @@ public class PayWorkbookParser
pending = "";
continue;
}
if (isNoteTitle(grid, row))
{
row = readNoteBlock(grid, row, product);
pending = "";
continue;
}
if (isLooseSurcharge(grid, row))
{
addLooseSurcharge(grid, row, pending, product);
@@ -1320,10 +1328,18 @@ public class PayWorkbookParser
for (int col = 0; col < grid.cols(); col++)
{
String text = grid.text(data, col);
if ((kind[col] == 2 || kind[col] == 3) && text.isBlank())
{
text = grid.mergedText(data, col);
}
if (text.isBlank())
{
continue;
}
if (kind[col] == 0 && parent[col].contains("尺寸"))
{
keepLimit(product, parent[col], text);
}
if (kind[col] == 1 || (weight.isEmpty() && PayTexts.looksLikeWeight(text)))
{
if (weight.isEmpty() || kind[col] == 1)
@@ -1364,7 +1380,8 @@ public class PayWorkbookParser
{
continue;
}
if (col > 0 && parent[col].equals(parent[col - 1]))
if (col > 0 && parent[col].equals(parent[col - 1])
&& zoneOf(sub[col], parent[col]).equals(zoneOf(sub[col - 1], parent[col - 1])))
{
BigDecimal previous = priceOf(grid, data, col - 1);
if (previous != null && previous.compareTo(price) == 0)
@@ -1384,7 +1401,7 @@ public class PayWorkbookParser
band.setWeightMax(bounds[1]);
if (feeHead)
{
band.setChargeName(shortCharge(parent[col]));
band.setChargeName(chargeTitle(parent[col]));
band.setZoneName(zoneOf(sub[col], parent[col]));
}
else
@@ -1793,6 +1810,7 @@ public class PayWorkbookParser
{
fee.setAmount(null);
fee.setCurrency("");
fee.setPriceUnit("");
return;
}
Matcher matcher = Pattern.compile("(\\d+(?:\\.\\d+)?)\\s*欧元").matcher(sentence);
@@ -1805,12 +1823,15 @@ public class PayWorkbookParser
}
if (hits == 1)
{
fee.setAmount(new BigDecimal(amount));
BigDecimal price = new BigDecimal(amount);
fee.setAmount(price);
fee.setCurrency("EUR");
fee.setPriceUnit(surchargeUnit(new PayTexts.Money(price, "EUR", ""), sentence));
return;
}
fee.setAmount(null);
fee.setCurrency("");
fee.setPriceUnit("");
}
private static void markManual(PayProduct product)
@@ -2039,6 +2060,7 @@ public class PayWorkbookParser
fee.setChargeText(String.join("、", factors.keySet()));
fee.setAmount(null);
fee.setCurrency("");
fee.setPriceUnit("");
markManual(product);
}
@@ -2369,6 +2391,10 @@ public class PayWorkbookParser
fee.setChargeText(priceText.trim());
fee.setAmount(BigDecimal.ZERO);
fee.setCurrency(fee.getCurrency() == null || fee.getCurrency().isBlank() ? "EUR" : fee.getCurrency());
if (fee.getPriceUnit() == null || fee.getPriceUnit().isBlank())
{
fee.setPriceUnit(fee.getCurrency());
}
}
row++;
}
@@ -2838,6 +2864,10 @@ public class PayWorkbookParser
{
remarkCol = col;
}
else if (text.contains("收取") || text.contains("时点"))
{
remarkCol = remarkCol < 0 ? col : remarkCol;
}
else if (nameCol < 0 && isFeeNameHead(text))
{
nameCol = col;
@@ -3125,6 +3155,7 @@ public class PayWorkbookParser
fee.setChargeText(cut(new StringBuilder(blankTo(standard, money.amount == null ? "" : money.amount.toPlainString())), 500));
fee.setAmount(money.amount);
fee.setCurrency(money.currency);
fee.setPriceUnit(cut(new StringBuilder(surchargeUnit(money, blankTo(standard, combined))), 40));
fee.setFeeDesc(cut(new StringBuilder(joinDesc(desc, remark)), 2000));
fee.setPeriodText(period.text == null ? "" : cut(new StringBuilder(period.text), 500));
fee.setEffectiveStart(PayTexts.toDate(period.start));
@@ -3134,6 +3165,7 @@ public class PayWorkbookParser
{
fee.setAmount(null);
fee.setCurrency("");
fee.setPriceUnit("");
}
fee.setSortNo(product.getSurcharges().size() + 1);
PaySurchargeRules.apply(fee);
@@ -3588,6 +3620,76 @@ public class PayWorkbookParser
return next < grid.rows() && rowHasWeight(grid, next) && countNumbers(grid, next) >= 2;
}
/** 一、计费说明这种标题。后面的句子是配置,不是费用行。 */
private static boolean isNoteTitle(ExcelGrid grid, int row)
{
String line = joinRow(grid, row).trim();
if (line.isEmpty() || line.length() > 24 || countText(grid, row) > 2 || line.contains("。"))
{
return false;
}
return line.matches("^[一二三四五六七八九十]+、.+");
}
private int readNoteBlock(ExcelGrid grid, int row, PayProduct product)
{
String title = joinRow(grid, row).trim();
StringBuilder body = new StringBuilder();
row++;
while (row < grid.rows() && !grid.blankRow(row) && !isHardHeader(grid, row) && !isNoteTitle(grid, row)
&& !isSurchargeHeader(grid, row))
{
String line = joinRow(grid, row).replace('\n', ' ').trim();
if (!line.isEmpty())
{
if (body.length() > 0)
{
body.append(' ');
}
body.append(line);
}
row++;
}
if (body.length() > 0)
{
addConfig(product, title + " | " + body);
}
return row;
}
/** 尺寸限制是整段产品的规则,不写进每一档单价。 */
private void keepLimit(PayProduct product, String head, String text)
{
String body = text.replace('\n', ';').replace(";;", ";").trim();
if (body.isEmpty())
{
return;
}
String title = head.contains("尺寸") ? "尺寸限制" : shortCharge(head);
addConfig(product, title + " | " + body);
}
private void addConfig(PayProduct product, String text)
{
String line = cut(new StringBuilder(text), 1000);
if (line.isEmpty())
{
return;
}
for (PayExtraLine existing : product.getExtraLines())
{
if (line.equals(existing.getLineText()))
{
return;
}
}
PayExtraLine extra = new PayExtraLine();
extra.setLineType("配置");
extra.setLineText(line);
extra.setSortNo(product.getExtraLines().size() + 1);
product.getExtraLines().add(extra);
}
/** 单独的「附加费」标题。后面跟着金额说明或条件费率,而不是收费标准表。 */
private static boolean isFeeSectionTitle(ExcelGrid grid, int row)
{
@@ -4122,6 +4224,39 @@ public class PayWorkbookParser
return currency.isEmpty() ? "每票" : currency + "/票";
}
/** 附加费的货币单位跟重量段一样,价格数字单独放 amount,这里只放 EUR/ITEM、RMB/KG、%。 */
private static String surchargeUnit(PayTexts.Money money, String standard)
{
if (money == null || money.amount == null)
{
return "";
}
if ("%".equals(money.unit))
{
return "%";
}
String fromText = unitOf(standard == null ? "" : standard);
String currency = money.currency == null ? "" : money.currency;
String unit = money.unit == null ? "" : money.unit;
if (fromText.contains("/"))
{
return fromText;
}
if (unit.isEmpty() && ("ITEM".equals(fromText) || "KG".equals(fromText)))
{
unit = fromText;
}
if (!currency.isEmpty() && !unit.isEmpty())
{
return currency + "/" + unit;
}
if (!fromText.isEmpty() && currency.isEmpty())
{
return fromText;
}
return currency.isEmpty() ? unit : currency;
}
private static String unitOf(String header)
{
String value = header.toUpperCase(Locale.ROOT);
@@ -4135,6 +4270,19 @@ public class PayWorkbookParser
return per.isEmpty() ? currency : currency + "/" + per;
}
/** 去掉表头里的币种括号,留下服务名。两段派送费才能分成 Crono Express 和 Crono。 */
private static String chargeTitle(String head)
{
if (head == null || head.isBlank())
{
return "";
}
String value = head.replace('\n', ' ').replace('\r', ' ');
value = value.replaceAll("(?i)[((]\\s*(?:EUR|GBP|USD|RMB|CNY|欧元|英镑|美元|人民币)\\s*/\\s*[^))]{1,12}[))]", " ");
value = value.replaceAll("\\s+", " ").trim();
return shortCharge(value);
}
private static String shortCharge(String text)
{
if (text == null)
@@ -4169,7 +4317,7 @@ public class PayWorkbookParser
{
if (isPriceHead(head))
{
return shortCharge(head);
return chargeTitle(head);
}
}
return "价格";
@@ -1,5 +1,6 @@
package com.bms.price.service;
import java.io.OutputStream;
import java.util.List;
import java.util.Date;
import com.bms.price.domain.PayPreview;
@@ -66,5 +67,11 @@ public interface IPayProductService
/** 产品及其重量段、附加费、邮编配置 */
PayProduct selectPayProductDetail(Long productId);
/** 确认这一版有源文件,返回原始文件名 */
String sourceFileName(Long productId);
/** 把这一版的源文件写到输出流 */
void writeSource(Long productId, OutputStream output);
int deletePayProducts(Long[] productIds);
}
@@ -1,20 +1,29 @@
package com.bms.price.service.impl;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.bms.common.core.domain.entity.SysDictData;
import com.bms.common.exception.ServiceException;
import com.bms.common.utils.DateUtils;
import com.bms.common.utils.SecurityUtils;
import com.bms.common.utils.file.OssStorage;
import com.bms.common.utils.uuid.IdUtils;
import com.bms.price.domain.ChannelProductMap;
import com.bms.price.domain.PayExtraLine;
import com.bms.price.domain.PayPreview;
@@ -58,14 +67,26 @@ public class PayProductServiceImpl implements IPayProductService
@Autowired
private ISysDictTypeService dictTypeService;
@Autowired
private OssStorage ossStorage;
private final PayWorkbookParser parser = new PayWorkbookParser();
/** 预览时已经传到 OSS 的文件。保存不再只靠前端把地址传回来。 */
private final Map<String, UploadedSource> uploadedSources = new ConcurrentHashMap<>();
private static final long SOURCE_REMEMBER_MS = 6L * 60 * 60 * 1000;
@Override
public PayPreview preview(byte[] bytes, String filename, String dictValue)
{
PayPreview preview = parser.parseMatched(bytes, filename, dictValue, loadDictValues());
String stored = storeSource(bytes, filename);
rememberSource(preview.getFileMd5(), stored);
preview.setFilePath(stored);
for (PayProduct product : preview.getProducts())
{
product.setFilePath(stored);
Long existsId = isBlank(product.getDictValue()) ? null : payProductMapper.selectTemplateId(product.getDictValue());
product.setDuplicated(existsId != null);
product.setExistsId(existsId);
@@ -102,6 +123,7 @@ public class PayProductServiceImpl implements IPayProductService
continue;
}
product.setProductType(requireProductType(product.getProductType()));
product.setFilePath(resolveSource(product));
prepareCounts(product);
fitColumns(product);
product.setProductId(null);
@@ -152,6 +174,7 @@ public class PayProductServiceImpl implements IPayProductService
product.setProductType(isBlank(product.getProductType()) ? existing.getProductType() : payTypeOf(product.getProductType()));
product.setFileMd5(blankTo(product.getFileMd5(), existing.getFileMd5()));
product.setSourceFile(blankTo(product.getSourceFile(), existing.getSourceFile()));
product.setFilePath(blankTo(sourcePath(product.getFilePath()), existing.getFilePath()));
product.setSheetName(blankTo(product.getSheetName(), existing.getSheetName()));
product.setUpdateBy(SecurityUtils.getUsername());
prepareCounts(product);
@@ -334,6 +357,7 @@ public class PayProductServiceImpl implements IPayProductService
copy.setSheetName(blankTo(edited.getSheetName(), origin.getSheetName()));
copy.setRelatedSheet(blankTo(edited.getRelatedSheet(), origin.getRelatedSheet()));
copy.setSourceFile(blankTo(edited.getSourceFile(), origin.getSourceFile()));
copy.setFilePath(blankTo(sourcePath(edited.getFilePath()), origin.getFilePath()));
copy.setFileMd5(blankTo(edited.getFileMd5(), origin.getFileMd5()));
copy.setSupplierName(blankTo(edited.getSupplierName(), origin.getSupplierName()));
copy.setEffectiveDate(edited.getEffectiveDate() != null ? edited.getEffectiveDate() : origin.getEffectiveDate());
@@ -453,6 +477,7 @@ public class PayProductServiceImpl implements IPayProductService
term.setProductId(product.getProductId());
term.setVersionNo(product.getVersionNo() == null ? 1 : product.getVersionNo());
term.setEffectiveStart(product.getEffectiveDate());
term.setFilePath(sourcePath(product.getFilePath()));
term.setStatus("1");
term.setCreateBy(product.getCreateBy());
payProductTermMapper.insertTerm(term);
@@ -477,6 +502,130 @@ public class PayProductServiceImpl implements IPayProductService
return products;
}
@Override
public String sourceFileName(Long productId)
{
return sourceOf(productId).getSourceFile();
}
@Override
public void writeSource(Long productId, OutputStream output)
{
try
{
ossStorage.download(sourceOf(productId).getFilePath(), output);
}
catch (IOException ex)
{
throw new ServiceException("源文件下载失败");
}
}
private PayProduct sourceOf(Long productId)
{
PayProduct product = productId == null ? null : payProductMapper.selectPayProductById(productId);
if (product == null)
{
throw new ServiceException("产品不存在或已删除");
}
String path = sourcePath(product.getFilePath());
if (path == null)
{
throw new ServiceException("这一版没有可下载的源文件");
}
product.setFilePath(path);
if (isBlank(product.getSourceFile()))
{
product.setSourceFile("价卡.xlsx");
}
return product;
}
/** 识别完成后把原文件放到 OSS,同一份文件只传一次。 */
private String storeSource(byte[] bytes, String filename)
{
String original = isBlank(filename) ? "import.xlsx" : filename.trim();
String ext = "xlsx";
int dot = original.lastIndexOf('.');
if (dot >= 0 && dot < original.length() - 1)
{
ext = original.substring(dot + 1).toLowerCase(Locale.ROOT);
}
if (!"xlsx".equals(ext) && !"xls".equals(ext))
{
ext = "xlsx";
}
String contentType = "xls".equals(ext)
? "application/vnd.ms-excel"
: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
String objectKey = "price/pay/" + DateUtils.datePath() + "/" + IdUtils.fastSimpleUUID() + "." + ext;
try
{
return ossStorage.upload(new ByteArrayInputStream(bytes), bytes.length, objectKey, contentType);
}
catch (IOException ex)
{
throw new ServiceException("价卡文件上传失败");
}
}
/** 保存时用预览传回的地址;前端没带上时,用刚才上传记住的那一份。 */
private String resolveSource(PayProduct product)
{
String path = sourcePath(product.getFilePath());
if (path == null)
{
path = rememberedSource(product.getFileMd5());
}
if (path == null)
{
throw new ServiceException("源文件还没有上传成功,请重新选择价卡后再保存");
}
return path;
}
private void rememberSource(String fileMd5, String path)
{
if (isBlank(fileMd5) || sourcePath(path) == null)
{
return;
}
long now = System.currentTimeMillis();
Iterator<Map.Entry<String, UploadedSource>> it = uploadedSources.entrySet().iterator();
while (it.hasNext())
{
if (now - it.next().getValue().at > SOURCE_REMEMBER_MS)
{
it.remove();
}
}
uploadedSources.put(fileMd5, new UploadedSource(path, now));
}
private String rememberedSource(String fileMd5)
{
if (isBlank(fileMd5))
{
return null;
}
UploadedSource stored = uploadedSources.get(fileMd5);
if (stored == null || System.currentTimeMillis() - stored.at > SOURCE_REMEMBER_MS)
{
return null;
}
return stored.path;
}
/** 只接受本次应付价卡目录下的 OSS 地址,避免把别的文件地址写进来。 */
private static String sourcePath(String path)
{
if (path == null || !path.startsWith("oss://") || !path.contains("/price/pay/"))
{
return null;
}
return path.trim();
}
@Override
public PayProduct selectPayProductDetail(Long productId)
{
@@ -647,6 +796,7 @@ public class PayProductServiceImpl implements IPayProductService
product.setSheetName(cut(product.getSheetName(), 200));
product.setRelatedSheet(cut(product.getRelatedSheet(), 500));
product.setSourceFile(cut(product.getSourceFile(), 500));
product.setFilePath(cut(product.getFilePath(), 500));
product.setSupplierName(cut(product.getSupplierName(), 100));
product.setCurrency(cut(product.getCurrency(), 20));
product.setServiceNote(cut(product.getServiceNote(), 4000));
@@ -675,6 +825,7 @@ public class PayProductServiceImpl implements IPayProductService
fee.setZoneName(cut(fee.getZoneName(), 200));
fee.setChargeText(cut(fee.getChargeText(), 500));
fee.setCurrency(cut(fee.getCurrency(), 20));
fee.setPriceUnit(cut(fee.getPriceUnit(), 40));
fee.setFeeDesc(cut(fee.getFeeDesc(), 2000));
fee.setPeriodText(cut(fee.getPeriodText(), 500));
fee.setChargeTiming(cut(fee.getChargeTiming(), 40));
@@ -1074,4 +1225,17 @@ public class PayProductServiceImpl implements IPayProductService
{
return isBlank(value) ? fallback : value;
}
private static final class UploadedSource
{
private final String path;
private final long at;
private UploadedSource(String path, long at)
{
this.path = path;
this.at = at;
}
}
}
@@ -122,6 +122,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<update id="updateServiceChannel" parameterType="com.bms.masterdata.domain.ServiceChannel">
update srv_service_channel
set channel_code = #{channelCode},
channel_name = #{channelName},
service_code = #{serviceCode},
service_name = #{serviceName},
@@ -23,6 +23,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="sheetName" column="sheet_name"/>
<result property="relatedSheet" column="related_sheet"/>
<result property="sourceFile" column="source_file"/>
<result property="filePath" column="file_path"/>
<result property="fileMd5" column="file_md5"/>
<result property="supplierName" column="supplier_name"/>
<result property="effectiveDate" column="effective_date"/>
@@ -112,6 +113,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
ifnull(cur.extra_per_kg, pay_product.extra_per_kg) as extra_per_kg, pay_product.service_type,
pay_product.classification, pay_product.catalog_remark, pay_product.update_content, pay_product.weight_limit,
pay_product.sheet_name, pay_product.related_sheet, ifnull(cur.source_file, pay_product.source_file) as source_file,
case when cur.product_id is null then pay_product.file_path else cur.file_path end as file_path,
pay_product.file_md5,
pay_product.supplier_name, pay_product.effective_date, ifnull(cur.currency, pay_product.currency) as currency,
pay_product.service_note,
@@ -137,7 +139,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<select id="selectPayProductById" parameterType="long" resultMap="PayProductResult">
select product_id, product_name, product_type, dict_value, origin_id, channel_id, version_no, country_scope, charge_type, extra_per_kg, service_type,
classification, catalog_remark, update_content, weight_limit, sheet_name, related_sheet, source_file,
classification, catalog_remark, update_content, weight_limit, sheet_name, related_sheet, source_file, file_path,
file_md5, supplier_name, effective_date, currency, service_note, parse_status, weight_count,
surcharge_count, extra_count, created_by, create_time, updated_by, update_time, remark
from pay_product
@@ -153,13 +155,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<insert id="insertPayProduct" parameterType="com.bms.price.domain.PayProduct" useGeneratedKeys="true" keyProperty="productId">
insert into pay_product (
product_name, product_type, dict_value, origin_id, channel_id, version_no, country_scope, charge_type, extra_per_kg, service_type, classification,
catalog_remark, update_content, weight_limit, sheet_name, related_sheet, source_file, file_md5,
catalog_remark, update_content, weight_limit, sheet_name, related_sheet, source_file, file_path, file_md5,
supplier_name, effective_date, currency, service_note, parse_status, weight_count, surcharge_count,
extra_count, created_by, create_time, remark
) values (
#{productName}, #{productType}, #{dictValue}, #{originId}, #{channelId}, ifnull(#{versionNo}, 1), #{countryScope}, #{chargeType}, #{extraPerKg},
#{serviceType}, #{classification}, #{catalogRemark}, #{updateContent}, #{weightLimit}, #{sheetName},
#{relatedSheet}, #{sourceFile}, #{fileMd5}, #{supplierName}, #{effectiveDate}, #{currency},
#{relatedSheet}, #{sourceFile}, #{filePath}, #{fileMd5}, #{supplierName}, #{effectiveDate}, #{currency},
#{serviceNote}, #{parseStatus}, #{weightCount}, #{surchargeCount}, #{extraCount}, #{createBy}, sysdate(),
#{remark}
)
@@ -184,6 +186,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
sheet_name = #{sheetName},
related_sheet = #{relatedSheet},
source_file = #{sourceFile},
file_path = #{filePath},
file_md5 = #{fileMd5},
supplier_name = #{supplierName},
effective_date = #{effectiveDate},
@@ -246,12 +249,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<insert id="insertSurcharges">
insert into pay_surcharge (
product_id, version_no, section_name, fee_name, zone_name, charge_text, amount, currency, fee_desc, period_text,
product_id, version_no, section_name, fee_name, zone_name, charge_text, amount, currency, price_unit, fee_desc, period_text,
effective_start, effective_end, charge_timing, calc_rule, calc_param, sort_no
) values
<foreach collection="list" item="row" separator=",">
(#{row.productId}, ifnull(#{row.versionNo}, 1), #{row.sectionName}, #{row.feeName}, #{row.zoneName}, #{row.chargeText}, #{row.amount},
#{row.currency}, #{row.feeDesc}, #{row.periodText}, #{row.effectiveStart}, #{row.effectiveEnd},
#{row.currency}, #{row.priceUnit}, #{row.feeDesc}, #{row.periodText}, #{row.effectiveStart}, #{row.effectiveEnd},
#{row.chargeTiming}, ifnull(#{row.calcRule}, 'MANUAL'), #{row.calcParam}, #{row.sortNo})
</foreach>
</insert>
@@ -275,7 +278,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<select id="selectSurcharges" resultType="com.bms.price.domain.PaySurcharge">
select surcharge_id as surchargeId, product_id as productId, version_no as versionNo, section_name as sectionName, fee_name as feeName,
zone_name as zoneName, charge_text as chargeText, amount, currency, fee_desc as feeDesc,
zone_name as zoneName, charge_text as chargeText, amount, currency, price_unit as priceUnit, fee_desc as feeDesc,
period_text as periodText, effective_start as effectiveStart, effective_end as effectiveEnd,
charge_timing as chargeTiming, calc_rule as calcRule, calc_param as calcParam, sort_no as sortNo
from pay_surcharge
@@ -8,7 +8,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
select t.term_id as termId, t.dict_value as dictValue, t.product_id as productId, t.version_no as versionNo,
t.effective_start as effectiveStart, t.effective_end as effectiveEnd, t.status,
t.created_by as createBy, t.create_time as createTime, t.remark,
p.product_name as productName, p.source_file as sourceFile, p.weight_count as weightCount,
p.product_name as productName, p.source_file as sourceFile,
ifnull(t.file_path, p.file_path) as filePath, p.weight_count as weightCount,
p.surcharge_count as surchargeCount, p.currency
from pay_product_term t
join pay_product p on p.product_id = t.product_id
@@ -34,9 +35,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<insert id="insertTerm" parameterType="com.bms.price.domain.PayProductTerm" useGeneratedKeys="true" keyProperty="termId">
insert into pay_product_term (
dict_value, product_id, version_no, effective_start, effective_end, status, created_by, create_time, remark
dict_value, product_id, version_no, effective_start, effective_end, file_path, status, created_by, create_time, remark
) values (
#{dictValue}, #{productId}, ifnull(#{versionNo}, 1), #{effectiveStart}, #{effectiveEnd},
#{dictValue}, #{productId}, ifnull(#{versionNo}, 1), #{effectiveStart}, #{effectiveEnd}, #{filePath},
ifnull(#{status}, '0'), #{createBy}, sysdate(), #{remark}
)
</insert>
+2
View File
@@ -14,6 +14,7 @@ CREATE TABLE IF NOT EXISTS `pay_product` (
`sheet_name` varchar(200) NOT NULL COMMENT '报价工作表',
`related_sheet` varchar(500) DEFAULT NULL COMMENT '目录里一并指向的分区或附加表,不单独入库',
`source_file` varchar(500) NOT NULL COMMENT '来源文件名',
`file_path` varchar(500) DEFAULT NULL COMMENT '源文件OSS地址,oss://bucket/key',
`file_md5` char(32) NOT NULL COMMENT '来源文件MD5',
`supplier_name` varchar(100) DEFAULT NULL COMMENT '价卡归属,如威狮、易德、GWE',
`effective_date` date DEFAULT NULL COMMENT '价卡生效日',
@@ -65,6 +66,7 @@ CREATE TABLE IF NOT EXISTS `pay_surcharge` (
`charge_text` varchar(500) DEFAULT NULL COMMENT '收费标准原文',
`amount` decimal(12,4) DEFAULT NULL COMMENT '能解析出的金额或百分比',
`currency` varchar(20) DEFAULT NULL COMMENT '币种',
`price_unit` varchar(40) DEFAULT NULL COMMENT '货币和计费单位,如EUR/ITEM、RMB/KG、%',
`fee_desc` varchar(2000) DEFAULT NULL COMMENT '费用说明',
`period_text` varchar(500) DEFAULT NULL COMMENT '时效原文',
`effective_start` date DEFAULT NULL COMMENT '生效开始,空表示价卡期内一直有效',
+16
View File
@@ -0,0 +1,16 @@
-- 应付价卡每一版保存源文件在 OSS 上的地址,历史记录按这个地址下载。可重复执行。
SET NAMES utf8mb4;
SET @ddl = (
SELECT IF(
EXISTS (
SELECT 1 FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'pay_product' AND COLUMN_NAME = 'file_path'
),
'SELECT 1',
'ALTER TABLE pay_product ADD COLUMN file_path varchar(500) DEFAULT NULL COMMENT ''源文件OSS地址,oss://bucket/key'' AFTER source_file'
)
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
+1
View File
@@ -8,6 +8,7 @@ CREATE TABLE IF NOT EXISTS `pay_product_term` (
`version_no` int(11) NOT NULL DEFAULT 1 COMMENT '版本号',
`effective_start` date DEFAULT NULL COMMENT '生效开始',
`effective_end` date DEFAULT NULL COMMENT '生效结束,空表示不限',
`file_path` varchar(500) DEFAULT NULL COMMENT '这一版源文件OSS地址,oss://bucket/key',
`status` char(1) NOT NULL DEFAULT '0' COMMENT '0生效 1失效',
`created_by` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
@@ -0,0 +1,16 @@
-- 历史版本记下源文件地址。可重复执行。
SET NAMES utf8mb4;
SET @ddl = (
SELECT IF(
EXISTS (
SELECT 1 FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'pay_product_term' AND COLUMN_NAME = 'file_path'
),
'SELECT 1',
'ALTER TABLE pay_product_term ADD COLUMN file_path varchar(500) DEFAULT NULL COMMENT ''这一版源文件OSS地址,oss://bucket/key'' AFTER effective_end'
)
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
@@ -0,0 +1,16 @@
-- 附加费的价格和货币分开。金额仍在 amount,货币和计费单位在 price_unit,如 EUR/ITEM。可重复执行。
SET NAMES utf8mb4;
SET @ddl = (
SELECT IF(
EXISTS (
SELECT 1 FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'pay_surcharge' AND COLUMN_NAME = 'price_unit'
),
'SELECT 1',
'ALTER TABLE pay_surcharge ADD COLUMN price_unit varchar(40) DEFAULT NULL COMMENT ''货币和计费单位,如EUR/ITEM、RMB/KG、%'' AFTER currency'
)
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
@@ -78,7 +78,7 @@
<el-tab-pane :label="`附加费(${(pane.item.surcharges || []).length})`" name="fee">
<div class="pay-edit-bar">
<el-input v-model="pane.feeKeyword" clearable placeholder="筛选附加费用、地区、收费标准或费用解释" class="pay-filter" @input="pane.feePageNo = 1" />
<el-input v-model="pane.feeKeyword" clearable placeholder="筛选附加费用、地区、价格、货币或费用解释" class="pay-filter" @input="pane.feePageNo = 1" />
<el-button v-if="editable" size="small" type="primary" plain @click="addFee(pane)">添加附加费</el-button>
</div>
<el-table :data="pageFees(pane)" border size="small" max-height="360" empty-text="没有附加费">
@@ -88,8 +88,23 @@
<el-table-column prop="zoneName" label="地区" min-width="160" show-overflow-tooltip>
<template #default="{ row }"><cell-text v-model="row.zoneName" :edit="editable" /></template>
</el-table-column>
<el-table-column prop="chargeText" label="收费标准" min-width="180" show-overflow-tooltip>
<template #default="{ row }"><cell-text v-model="row.chargeText" :edit="editable" /></template>
<el-table-column label="价格" width="110" align="right">
<template #default="{ row }">
<el-input v-if="editable" v-model="row.amount" size="small" />
<span v-else>{{ money(row.amount) }}</span>
</template>
</el-table-column>
<el-table-column label="货币" width="120" show-overflow-tooltip>
<template #default="{ row }">
<cell-text v-if="editable" v-model="row.priceUnit" :edit="true" />
<span v-else>{{ row.priceUnit || row.currency || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="收费标准" min-width="180" show-overflow-tooltip>
<template #default="{ row }">
<cell-text v-if="editable && (!row.chargeText || feeNote(row))" v-model="row.chargeText" :edit="true" />
<span v-else>{{ feeNote(row) || '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="feeDesc" label="费用解释" min-width="240" show-overflow-tooltip>
<template #default="{ row }"><cell-text v-model="row.feeDesc" :edit="editable" /></template>
@@ -214,7 +229,7 @@ function filteredBands(pane) {
}
function filteredFees(pane) {
return filterRows(pane.item.surcharges || [], pane.feeKeyword, ['feeName', 'zoneName', 'chargeText', 'feeDesc', 'calcRule', 'calcParam'])
return filterRows(pane.item.surcharges || [], pane.feeKeyword, ['feeName', 'zoneName', 'chargeText', 'amount', 'currency', 'priceUnit', 'feeDesc', 'calcRule', 'calcParam'])
}
function addBand(pane) {
@@ -225,7 +240,7 @@ function addBand(pane) {
function addFee(pane) {
if (!pane.item.surcharges) pane.item.surcharges = []
pane.item.surcharges.push({ feeName: '', zoneName: '', chargeText: '', feeDesc: '', calcRule: 'MANUAL', calcParam: '' })
pane.item.surcharges.push({ feeName: '', zoneName: '', chargeText: '', amount: '', priceUnit: '', currency: '', feeDesc: '', calcRule: 'MANUAL', calcParam: '' })
pane.feePageNo = Math.ceil(pane.item.surcharges.length / pageSize) || 1
}
@@ -290,6 +305,19 @@ function money(value) {
return Number.isNaN(num) ? value : String(num)
}
/** 价格和货币已经拆开时,收费标准只留说明,不再重复「0.19 EUR/ITEM」。 */
function feeNote(row) {
const text = (row.chargeText || '').trim()
if (!text || row.amount === null || row.amount === undefined || row.amount === '') return text
const amount = Number(row.amount)
if (!Number.isFinite(amount)) return text
const match = text.match(/^\s*(\d+(?:\.\d+)?)\s*(?:EUR|GBP|USD|RMB|CNY|欧元|英镑|人民币|美元)?\s*(?:\/\s*[A-Za-z\u4e00-\u9fa5]+)?\s*([\s\S]*)$/i)
if (!match || Number(match[1]) !== amount) return text
const rest = (match[2] || '').trim()
if (!rest || rest === '%' || rest === (row.priceUnit || '') || /^[。..、,,]+$/.test(rest)) return ''
return rest
}
function periodLabel(row) {
if (row.effectiveStart || row.effectiveEnd) {
return `${row.effectiveStart || '…'} 至 ${row.effectiveEnd || '…'}`
+27 -3
View File
@@ -24,6 +24,11 @@
</el-col>
</template>
<template #col-sourceFile="{ row }">
<el-button v-if="row.filePath" link type="primary" @click="downloadSource(row)">{{ row.sourceFile }}</el-button>
<span v-else>{{ row.sourceFile || '-' }}</span>
</template>
<template #col-parseStatus="{ row }">
<el-tag :type="statusType(row.parseStatus)" size="small">{{ statusText(row.parseStatus) }}</el-tag>
</template>
@@ -56,7 +61,7 @@
<pay-import-dialog ref="importRef" @saved="tableRef.refresh()" />
<el-drawer v-model="historyVisible" :title="historyTitle" size="860px" destroy-on-close>
<el-drawer v-model="historyVisible" :title="historyTitle" size="980px" destroy-on-close>
<div class="history-bar">
<el-button type="primary" plain :disabled="historyPicked.length !== 2" @click="openCompare">比对所选版本</el-button>
<span>勾选两个版本后比对。历史订单按生效时间取价,不看当前开关。</span>
@@ -64,7 +69,12 @@
<el-table v-loading="historyLoading" :data="historyRows" border size="small" empty-text="还没有价格版本" @selection-change="onHistoryPick">
<el-table-column type="selection" width="42" align="center" />
<el-table-column prop="versionNo" label="版本" width="70" align="center" />
<el-table-column prop="sourceFile" label="来源文件" min-width="160" show-overflow-tooltip />
<el-table-column label="来源文件" min-width="160" show-overflow-tooltip>
<template #default="{ row }">
<el-button v-if="row.filePath" link type="primary" @click="downloadSource(row)">{{ row.sourceFile || '下载源文件' }}</el-button>
<span v-else>{{ row.sourceFile || '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="weightCount" label="重量段" width="80" align="center" />
<el-table-column prop="surchargeCount" label="附加费" width="80" align="center" />
<el-table-column label="生效开始" width="110" align="center">
@@ -88,8 +98,9 @@
/>
</template>
</el-table-column>
<el-table-column label="操作" width="150" align="center">
<el-table-column label="操作" width="210" align="center">
<template #default="{ row }">
<el-button link type="primary" @click="downloadSource(row)" v-hasPermi="['price:pay:query']">下载</el-button>
<el-button link type="primary" @click="openTermDialog(row)" v-hasPermi="['price:pay:edit']">修改有效期</el-button>
<el-button link type="primary" @click="openDetail(row)" v-hasPermi="['price:pay:query']">查看</el-button>
</template>
@@ -126,6 +137,7 @@
<el-drawer v-model="detailVisible" :title="detailTitle" size="78%" destroy-on-close>
<pay-config-detail v-loading="detailLoading" :product="detail" :editable="editing" />
<template #footer>
<el-button v-if="detail" @click="downloadSource(detail)" v-hasPermi="['price:pay:query']">下载源文件</el-button>
<el-button v-if="!editing" type="primary" @click="editing = true" v-hasPermi="['price:pay:import']">编辑</el-button>
<el-button v-else type="primary" :loading="saving" @click="saveDetail">保存</el-button>
<el-button @click="detailVisible = false">关闭</el-button>
@@ -229,6 +241,18 @@ function currentQuery() {
return query
}
function downloadSource(row) {
const id = row.versionProductId || row.productId
if (!id || !row.filePath) {
proxy.$modal.msgWarning('这一版导入时没有保存源文件,请重新导入后再下载')
return
}
proxy.download('/price/pay/' + id + '/source', {}, row.sourceFile || '价卡.xlsx', {
timeout: 120000,
headers: { repeatSubmit: false }
})
}
function handleExport() {
proxy.download('/price/pay/export', currentQuery(), `应付配置_${Date.now()}.xlsx`)
}