应付配置文件导入保存该文件
This commit is contained in:
+13
@@ -21,6 +21,7 @@ import com.bms.common.core.domain.AjaxResult;
|
|||||||
import com.bms.common.core.page.TableDataInfo;
|
import com.bms.common.core.page.TableDataInfo;
|
||||||
import com.bms.common.enums.BusinessType;
|
import com.bms.common.enums.BusinessType;
|
||||||
import com.bms.common.exception.ServiceException;
|
import com.bms.common.exception.ServiceException;
|
||||||
|
import com.bms.common.utils.file.FileUtils;
|
||||||
import com.bms.common.utils.poi.ExcelUtil;
|
import com.bms.common.utils.poi.ExcelUtil;
|
||||||
import com.bms.price.domain.ChannelProductMap;
|
import com.bms.price.domain.ChannelProductMap;
|
||||||
import com.bms.price.domain.PayProduct;
|
import com.bms.price.domain.PayProduct;
|
||||||
@@ -86,6 +87,18 @@ public class PayProductController extends BaseController
|
|||||||
return success(payOrderCostService.generate(billId));
|
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')")
|
@PreAuthorize("@ss.hasPermi('price:pay:query')")
|
||||||
@GetMapping("/{productId}")
|
@GetMapping("/{productId}")
|
||||||
public AjaxResult getInfo(@PathVariable Long productId)
|
public AjaxResult getInfo(@PathVariable Long productId)
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ class PayWorkbookParserTest
|
|||||||
.filter(fee -> "高风险地区附加费".equals(fee.getFeeName()) && "AF".equals(fee.getCalcParam())).findFirst().orElseThrow();
|
.filter(fee -> "高风险地区附加费".equals(fee.getFeeName()) && "AF".equals(fee.getCalcParam())).findFirst().orElseThrow();
|
||||||
assertEquals("Afghanistan", afghan.getZoneName());
|
assertEquals("Afghanistan", afghan.getZoneName());
|
||||||
assertEquals("RMB", afghan.getCurrency());
|
assertEquals("RMB", afghan.getCurrency());
|
||||||
|
assertEquals("RMB/KG", afghan.getPriceUnit());
|
||||||
assertTrue(afghan.getChargeText().contains("10.4") && afghan.getChargeText().contains("RMB/kg"));
|
assertTrue(afghan.getChargeText().contains("10.4") && afghan.getChargeText().contains("RMB/kg"));
|
||||||
assertTrue(decimalFee(afghan, "10.4"));
|
assertTrue(decimalFee(afghan, "10.4"));
|
||||||
assertEquals("MANUAL", afghan.getCalcRule());
|
assertEquals("MANUAL", afghan.getCalcRule());
|
||||||
@@ -532,6 +533,40 @@ class PayWorkbookParserTest
|
|||||||
assertTrue(ctt.getWeightBands().stream().noneMatch(band -> band.getWeightLabel() != null && band.getWeightLabel().contains("预估")));
|
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
|
private PayPreview parse(String prefix) throws Exception
|
||||||
{
|
{
|
||||||
Path dir = Path.of("D:/应付供应商");
|
Path dir = Path.of("D:/应付供应商");
|
||||||
@@ -557,7 +592,8 @@ class PayWorkbookParserTest
|
|||||||
&& "速递运费".equals(band.getChargeName()) && decimal(band, "0.23") && "EUR/KG".equals(band.getPriceUnit())));
|
&& "速递运费".equals(band.getChargeName()) && decimal(band, "0.23") && "EUR/KG".equals(band.getPriceUnit())));
|
||||||
assertTrue(dhl.getServiceNote() != null && dhl.getServiceNote().contains("报价已含燃油"));
|
assertTrue(dhl.getServiceNote() != null && dhl.getServiceNote().contains("报价已含燃油"));
|
||||||
assertTrue(dhl.getSurcharges().stream().anyMatch(fee -> "其他费用".equals(fee.getSectionName())
|
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")
|
assertTrue(dhl.getSurcharges().stream().anyMatch(fee -> "旺季附加费".equals(fee.getFeeName()) && decimalFee(fee, "0.19")
|
||||||
&& fee.getEffectiveStart() != null));
|
&& fee.getEffectiveStart() != null));
|
||||||
assertTrue(dhl.getSurcharges().stream().anyMatch(fee -> "高峰旺季附加费".equals(fee.getFeeName()) && decimalFee(fee, "0.5")));
|
assertTrue(dhl.getSurcharges().stream().anyMatch(fee -> "高峰旺季附加费".equals(fee.getFeeName()) && decimalFee(fee, "0.5")));
|
||||||
|
|||||||
@@ -33,16 +33,20 @@ public class ExcelGrid
|
|||||||
/** 合并区域左上角横向占几列,其余单元格为 0 */
|
/** 合并区域左上角横向占几列,其余单元格为 0 */
|
||||||
private final int[][] colSpan;
|
private final int[][] colSpan;
|
||||||
|
|
||||||
|
/** 纵向合并时,被盖住的格子指向左上角行;没有纵向合并是 -1 */
|
||||||
|
private final int[][] rowAnchor;
|
||||||
|
|
||||||
private final int rows;
|
private final int rows;
|
||||||
|
|
||||||
private final int cols;
|
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.text = text;
|
||||||
this.number = number;
|
this.number = number;
|
||||||
this.linkSheet = linkSheet;
|
this.linkSheet = linkSheet;
|
||||||
this.colSpan = colSpan;
|
this.colSpan = colSpan;
|
||||||
|
this.rowAnchor = rowAnchor;
|
||||||
this.rows = rows;
|
this.rows = rows;
|
||||||
this.cols = cols;
|
this.cols = cols;
|
||||||
}
|
}
|
||||||
@@ -88,6 +92,14 @@ public class ExcelGrid
|
|||||||
Double[][] number = new Double[Math.max(rows, 0)][Math.max(cols, 0)];
|
Double[][] number = new Double[Math.max(rows, 0)][Math.max(cols, 0)];
|
||||||
String[][] linkSheet = new String[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[][] 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)
|
if (sheet != null && rows > 0 && cols > 0)
|
||||||
{
|
{
|
||||||
for (int r = 0; r < rows; r++)
|
for (int r = 0; r < rows; r++)
|
||||||
@@ -115,9 +127,21 @@ public class ExcelGrid
|
|||||||
{
|
{
|
||||||
colSpan[r0][c0] = width;
|
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()
|
public int rows()
|
||||||
@@ -139,6 +163,18 @@ public class ExcelGrid
|
|||||||
return text[row][col] == null ? "" : text[row][col];
|
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)
|
public Double number(int row, int col)
|
||||||
{
|
{
|
||||||
if (row < 0 || col < 0 || row >= rows || col >= cols)
|
if (row < 0 || col < 0 || row >= rows || col >= cols)
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ public class PayPreview
|
|||||||
{
|
{
|
||||||
private String sourceFile;
|
private String sourceFile;
|
||||||
|
|
||||||
|
/** 本次价卡在 OSS 上的地址,保存时写到每个产品版本 */
|
||||||
|
private String filePath;
|
||||||
|
|
||||||
private String fileMd5;
|
private String fileMd5;
|
||||||
|
|
||||||
private String supplierName;
|
private String supplierName;
|
||||||
|
|||||||
@@ -93,6 +93,9 @@ public class PayProduct extends BaseEntity
|
|||||||
@Excel(name = "来源文件", width = 36)
|
@Excel(name = "来源文件", width = 36)
|
||||||
private String sourceFile;
|
private String sourceFile;
|
||||||
|
|
||||||
|
/** 源文件在 OSS 上的地址,形如 oss://bucket/key。历史记录按它下载 */
|
||||||
|
private String filePath;
|
||||||
|
|
||||||
private String fileMd5;
|
private String fileMd5;
|
||||||
|
|
||||||
@Excel(name = "价卡归属", width = 12)
|
@Excel(name = "价卡归属", width = 12)
|
||||||
|
|||||||
@@ -37,6 +37,9 @@ public class PayProductTerm extends BaseEntity
|
|||||||
|
|
||||||
private String sourceFile;
|
private String sourceFile;
|
||||||
|
|
||||||
|
/** 这一版源文件的 OSS 地址,空表示导入时还没有存文件 */
|
||||||
|
private String filePath;
|
||||||
|
|
||||||
private Integer weightCount;
|
private Integer weightCount;
|
||||||
|
|
||||||
private Integer surchargeCount;
|
private Integer surchargeCount;
|
||||||
|
|||||||
@@ -30,8 +30,12 @@ public class PaySurcharge
|
|||||||
/** 从收费标准里解析出的金额;百分比时这里是百分数 */
|
/** 从收费标准里解析出的金额;百分比时这里是百分数 */
|
||||||
private BigDecimal amount;
|
private BigDecimal amount;
|
||||||
|
|
||||||
|
/** 币种代码,计费时和订单币种比对 */
|
||||||
private String currency;
|
private String currency;
|
||||||
|
|
||||||
|
/** 货币和计费单位,与重量段单位同一写法,如 EUR/ITEM、RMB/KG、% */
|
||||||
|
private String priceUnit;
|
||||||
|
|
||||||
/** 费用说明 */
|
/** 费用说明 */
|
||||||
private String feeDesc;
|
private String feeDesc;
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ public final class PayDetailExporter
|
|||||||
header(weightSheet, "产品名称", "报价工作表", "区块", "国家", "国家代码", "分区", "重量段", "下限KG", "上限KG",
|
header(weightSheet, "产品名称", "报价工作表", "区块", "国家", "国家代码", "分区", "重量段", "下限KG", "上限KG",
|
||||||
"体积", "费用项", "单价", "单位");
|
"体积", "费用项", "单价", "单位");
|
||||||
Sheet feeSheet = workbook.createSheet("附加费");
|
Sheet feeSheet = workbook.createSheet("附加费");
|
||||||
header(feeSheet, "产品名称", "报价工作表", "区块", "费用名称", "分区", "收费标准", "金额", "币种", "收取时点",
|
header(feeSheet, "产品名称", "报价工作表", "区块", "费用名称", "分区", "收费标准", "金额", "币种", "单位", "收取时点",
|
||||||
"生效开始", "生效结束", "时效原文", "费用说明");
|
"生效开始", "生效结束", "时效原文", "费用说明");
|
||||||
Sheet extraSheet = workbook.createSheet("其他配置");
|
Sheet extraSheet = workbook.createSheet("其他配置");
|
||||||
header(extraSheet, "产品名称", "报价工作表", "类型", "配置内容");
|
header(extraSheet, "产品名称", "报价工作表", "类型", "配置内容");
|
||||||
@@ -121,11 +121,12 @@ public final class PayDetailExporter
|
|||||||
cell(row, 5, fee.getChargeText());
|
cell(row, 5, fee.getChargeText());
|
||||||
cell(row, 6, plain(fee.getAmount()));
|
cell(row, 6, plain(fee.getAmount()));
|
||||||
cell(row, 7, fee.getCurrency());
|
cell(row, 7, fee.getCurrency());
|
||||||
cell(row, 8, fee.getChargeTiming());
|
cell(row, 8, fee.getPriceUnit());
|
||||||
cell(row, 9, day(fee.getEffectiveStart()));
|
cell(row, 9, fee.getChargeTiming());
|
||||||
cell(row, 10, day(fee.getEffectiveEnd()));
|
cell(row, 10, day(fee.getEffectiveStart()));
|
||||||
cell(row, 11, fee.getPeriodText());
|
cell(row, 11, day(fee.getEffectiveEnd()));
|
||||||
cell(row, 12, fee.getFeeDesc());
|
cell(row, 12, fee.getPeriodText());
|
||||||
|
cell(row, 13, fee.getFeeDesc());
|
||||||
}
|
}
|
||||||
return rowIndex;
|
return rowIndex;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -384,6 +384,7 @@ public class PayWorkbookParser
|
|||||||
copy.setChargeText(fee.getChargeText());
|
copy.setChargeText(fee.getChargeText());
|
||||||
copy.setAmount(fee.getAmount());
|
copy.setAmount(fee.getAmount());
|
||||||
copy.setCurrency(fee.getCurrency());
|
copy.setCurrency(fee.getCurrency());
|
||||||
|
copy.setPriceUnit(fee.getPriceUnit());
|
||||||
copy.setFeeDesc(fee.getFeeDesc());
|
copy.setFeeDesc(fee.getFeeDesc());
|
||||||
copy.setPeriodText(fee.getPeriodText());
|
copy.setPeriodText(fee.getPeriodText());
|
||||||
copy.setEffectiveStart(fee.getEffectiveStart());
|
copy.setEffectiveStart(fee.getEffectiveStart());
|
||||||
@@ -857,9 +858,10 @@ public class PayWorkbookParser
|
|||||||
fee.setSectionName(cut(new StringBuilder(sheetName), 200));
|
fee.setSectionName(cut(new StringBuilder(sheetName), 200));
|
||||||
fee.setFeeName("高风险地区附加费");
|
fee.setFeeName("高风险地区附加费");
|
||||||
fee.setZoneName(cut(new StringBuilder(country.isEmpty() ? code : country), 200));
|
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.setAmount(amount);
|
||||||
fee.setCurrency(currency);
|
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.setFeeDesc(desc);
|
||||||
fee.setCalcRule(PaySurchargeRules.MANUAL);
|
fee.setCalcRule(PaySurchargeRules.MANUAL);
|
||||||
fee.setCalcParam(code);
|
fee.setCalcParam(code);
|
||||||
@@ -1090,6 +1092,12 @@ public class PayWorkbookParser
|
|||||||
pending = "";
|
pending = "";
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (isNoteTitle(grid, row))
|
||||||
|
{
|
||||||
|
row = readNoteBlock(grid, row, product);
|
||||||
|
pending = "";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (isLooseSurcharge(grid, row))
|
if (isLooseSurcharge(grid, row))
|
||||||
{
|
{
|
||||||
addLooseSurcharge(grid, row, pending, product);
|
addLooseSurcharge(grid, row, pending, product);
|
||||||
@@ -1320,10 +1328,18 @@ public class PayWorkbookParser
|
|||||||
for (int col = 0; col < grid.cols(); col++)
|
for (int col = 0; col < grid.cols(); col++)
|
||||||
{
|
{
|
||||||
String text = grid.text(data, col);
|
String text = grid.text(data, col);
|
||||||
|
if ((kind[col] == 2 || kind[col] == 3) && text.isBlank())
|
||||||
|
{
|
||||||
|
text = grid.mergedText(data, col);
|
||||||
|
}
|
||||||
if (text.isBlank())
|
if (text.isBlank())
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (kind[col] == 0 && parent[col].contains("尺寸"))
|
||||||
|
{
|
||||||
|
keepLimit(product, parent[col], text);
|
||||||
|
}
|
||||||
if (kind[col] == 1 || (weight.isEmpty() && PayTexts.looksLikeWeight(text)))
|
if (kind[col] == 1 || (weight.isEmpty() && PayTexts.looksLikeWeight(text)))
|
||||||
{
|
{
|
||||||
if (weight.isEmpty() || kind[col] == 1)
|
if (weight.isEmpty() || kind[col] == 1)
|
||||||
@@ -1364,7 +1380,8 @@ public class PayWorkbookParser
|
|||||||
{
|
{
|
||||||
continue;
|
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);
|
BigDecimal previous = priceOf(grid, data, col - 1);
|
||||||
if (previous != null && previous.compareTo(price) == 0)
|
if (previous != null && previous.compareTo(price) == 0)
|
||||||
@@ -1384,7 +1401,7 @@ public class PayWorkbookParser
|
|||||||
band.setWeightMax(bounds[1]);
|
band.setWeightMax(bounds[1]);
|
||||||
if (feeHead)
|
if (feeHead)
|
||||||
{
|
{
|
||||||
band.setChargeName(shortCharge(parent[col]));
|
band.setChargeName(chargeTitle(parent[col]));
|
||||||
band.setZoneName(zoneOf(sub[col], parent[col]));
|
band.setZoneName(zoneOf(sub[col], parent[col]));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -1793,6 +1810,7 @@ public class PayWorkbookParser
|
|||||||
{
|
{
|
||||||
fee.setAmount(null);
|
fee.setAmount(null);
|
||||||
fee.setCurrency("");
|
fee.setCurrency("");
|
||||||
|
fee.setPriceUnit("");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Matcher matcher = Pattern.compile("(\\d+(?:\\.\\d+)?)\\s*欧元").matcher(sentence);
|
Matcher matcher = Pattern.compile("(\\d+(?:\\.\\d+)?)\\s*欧元").matcher(sentence);
|
||||||
@@ -1805,12 +1823,15 @@ public class PayWorkbookParser
|
|||||||
}
|
}
|
||||||
if (hits == 1)
|
if (hits == 1)
|
||||||
{
|
{
|
||||||
fee.setAmount(new BigDecimal(amount));
|
BigDecimal price = new BigDecimal(amount);
|
||||||
|
fee.setAmount(price);
|
||||||
fee.setCurrency("EUR");
|
fee.setCurrency("EUR");
|
||||||
|
fee.setPriceUnit(surchargeUnit(new PayTexts.Money(price, "EUR", ""), sentence));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fee.setAmount(null);
|
fee.setAmount(null);
|
||||||
fee.setCurrency("");
|
fee.setCurrency("");
|
||||||
|
fee.setPriceUnit("");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void markManual(PayProduct product)
|
private static void markManual(PayProduct product)
|
||||||
@@ -2039,6 +2060,7 @@ public class PayWorkbookParser
|
|||||||
fee.setChargeText(String.join("、", factors.keySet()));
|
fee.setChargeText(String.join("、", factors.keySet()));
|
||||||
fee.setAmount(null);
|
fee.setAmount(null);
|
||||||
fee.setCurrency("");
|
fee.setCurrency("");
|
||||||
|
fee.setPriceUnit("");
|
||||||
markManual(product);
|
markManual(product);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2369,6 +2391,10 @@ public class PayWorkbookParser
|
|||||||
fee.setChargeText(priceText.trim());
|
fee.setChargeText(priceText.trim());
|
||||||
fee.setAmount(BigDecimal.ZERO);
|
fee.setAmount(BigDecimal.ZERO);
|
||||||
fee.setCurrency(fee.getCurrency() == null || fee.getCurrency().isBlank() ? "EUR" : fee.getCurrency());
|
fee.setCurrency(fee.getCurrency() == null || fee.getCurrency().isBlank() ? "EUR" : fee.getCurrency());
|
||||||
|
if (fee.getPriceUnit() == null || fee.getPriceUnit().isBlank())
|
||||||
|
{
|
||||||
|
fee.setPriceUnit(fee.getCurrency());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
row++;
|
row++;
|
||||||
}
|
}
|
||||||
@@ -2838,6 +2864,10 @@ public class PayWorkbookParser
|
|||||||
{
|
{
|
||||||
remarkCol = col;
|
remarkCol = col;
|
||||||
}
|
}
|
||||||
|
else if (text.contains("收取") || text.contains("时点"))
|
||||||
|
{
|
||||||
|
remarkCol = remarkCol < 0 ? col : remarkCol;
|
||||||
|
}
|
||||||
else if (nameCol < 0 && isFeeNameHead(text))
|
else if (nameCol < 0 && isFeeNameHead(text))
|
||||||
{
|
{
|
||||||
nameCol = col;
|
nameCol = col;
|
||||||
@@ -3125,6 +3155,7 @@ public class PayWorkbookParser
|
|||||||
fee.setChargeText(cut(new StringBuilder(blankTo(standard, money.amount == null ? "" : money.amount.toPlainString())), 500));
|
fee.setChargeText(cut(new StringBuilder(blankTo(standard, money.amount == null ? "" : money.amount.toPlainString())), 500));
|
||||||
fee.setAmount(money.amount);
|
fee.setAmount(money.amount);
|
||||||
fee.setCurrency(money.currency);
|
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.setFeeDesc(cut(new StringBuilder(joinDesc(desc, remark)), 2000));
|
||||||
fee.setPeriodText(period.text == null ? "" : cut(new StringBuilder(period.text), 500));
|
fee.setPeriodText(period.text == null ? "" : cut(new StringBuilder(period.text), 500));
|
||||||
fee.setEffectiveStart(PayTexts.toDate(period.start));
|
fee.setEffectiveStart(PayTexts.toDate(period.start));
|
||||||
@@ -3134,6 +3165,7 @@ public class PayWorkbookParser
|
|||||||
{
|
{
|
||||||
fee.setAmount(null);
|
fee.setAmount(null);
|
||||||
fee.setCurrency("");
|
fee.setCurrency("");
|
||||||
|
fee.setPriceUnit("");
|
||||||
}
|
}
|
||||||
fee.setSortNo(product.getSurcharges().size() + 1);
|
fee.setSortNo(product.getSurcharges().size() + 1);
|
||||||
PaySurchargeRules.apply(fee);
|
PaySurchargeRules.apply(fee);
|
||||||
@@ -3588,6 +3620,76 @@ public class PayWorkbookParser
|
|||||||
return next < grid.rows() && rowHasWeight(grid, next) && countNumbers(grid, next) >= 2;
|
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)
|
private static boolean isFeeSectionTitle(ExcelGrid grid, int row)
|
||||||
{
|
{
|
||||||
@@ -4122,6 +4224,39 @@ public class PayWorkbookParser
|
|||||||
return currency.isEmpty() ? "每票" : currency + "/票";
|
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)
|
private static String unitOf(String header)
|
||||||
{
|
{
|
||||||
String value = header.toUpperCase(Locale.ROOT);
|
String value = header.toUpperCase(Locale.ROOT);
|
||||||
@@ -4135,6 +4270,19 @@ public class PayWorkbookParser
|
|||||||
return per.isEmpty() ? currency : currency + "/" + per;
|
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)
|
private static String shortCharge(String text)
|
||||||
{
|
{
|
||||||
if (text == null)
|
if (text == null)
|
||||||
@@ -4169,7 +4317,7 @@ public class PayWorkbookParser
|
|||||||
{
|
{
|
||||||
if (isPriceHead(head))
|
if (isPriceHead(head))
|
||||||
{
|
{
|
||||||
return shortCharge(head);
|
return chargeTitle(head);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "价格";
|
return "价格";
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.bms.price.service;
|
package com.bms.price.service;
|
||||||
|
|
||||||
|
import java.io.OutputStream;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import com.bms.price.domain.PayPreview;
|
import com.bms.price.domain.PayPreview;
|
||||||
@@ -66,5 +67,11 @@ public interface IPayProductService
|
|||||||
/** 产品及其重量段、附加费、邮编配置 */
|
/** 产品及其重量段、附加费、邮编配置 */
|
||||||
PayProduct selectPayProductDetail(Long productId);
|
PayProduct selectPayProductDetail(Long productId);
|
||||||
|
|
||||||
|
/** 确认这一版有源文件,返回原始文件名 */
|
||||||
|
String sourceFileName(Long productId);
|
||||||
|
|
||||||
|
/** 把这一版的源文件写到输出流 */
|
||||||
|
void writeSource(Long productId, OutputStream output);
|
||||||
|
|
||||||
int deletePayProducts(Long[] productIds);
|
int deletePayProducts(Long[] productIds);
|
||||||
}
|
}
|
||||||
|
|||||||
+164
@@ -1,20 +1,29 @@
|
|||||||
package com.bms.price.service.impl;
|
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.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import com.bms.common.core.domain.entity.SysDictData;
|
import com.bms.common.core.domain.entity.SysDictData;
|
||||||
import com.bms.common.exception.ServiceException;
|
import com.bms.common.exception.ServiceException;
|
||||||
|
import com.bms.common.utils.DateUtils;
|
||||||
import com.bms.common.utils.SecurityUtils;
|
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.ChannelProductMap;
|
||||||
import com.bms.price.domain.PayExtraLine;
|
import com.bms.price.domain.PayExtraLine;
|
||||||
import com.bms.price.domain.PayPreview;
|
import com.bms.price.domain.PayPreview;
|
||||||
@@ -58,14 +67,26 @@ public class PayProductServiceImpl implements IPayProductService
|
|||||||
@Autowired
|
@Autowired
|
||||||
private ISysDictTypeService dictTypeService;
|
private ISysDictTypeService dictTypeService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private OssStorage ossStorage;
|
||||||
|
|
||||||
private final PayWorkbookParser parser = new PayWorkbookParser();
|
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
|
@Override
|
||||||
public PayPreview preview(byte[] bytes, String filename, String dictValue)
|
public PayPreview preview(byte[] bytes, String filename, String dictValue)
|
||||||
{
|
{
|
||||||
PayPreview preview = parser.parseMatched(bytes, filename, dictValue, loadDictValues());
|
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())
|
for (PayProduct product : preview.getProducts())
|
||||||
{
|
{
|
||||||
|
product.setFilePath(stored);
|
||||||
Long existsId = isBlank(product.getDictValue()) ? null : payProductMapper.selectTemplateId(product.getDictValue());
|
Long existsId = isBlank(product.getDictValue()) ? null : payProductMapper.selectTemplateId(product.getDictValue());
|
||||||
product.setDuplicated(existsId != null);
|
product.setDuplicated(existsId != null);
|
||||||
product.setExistsId(existsId);
|
product.setExistsId(existsId);
|
||||||
@@ -102,6 +123,7 @@ public class PayProductServiceImpl implements IPayProductService
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
product.setProductType(requireProductType(product.getProductType()));
|
product.setProductType(requireProductType(product.getProductType()));
|
||||||
|
product.setFilePath(resolveSource(product));
|
||||||
prepareCounts(product);
|
prepareCounts(product);
|
||||||
fitColumns(product);
|
fitColumns(product);
|
||||||
product.setProductId(null);
|
product.setProductId(null);
|
||||||
@@ -152,6 +174,7 @@ public class PayProductServiceImpl implements IPayProductService
|
|||||||
product.setProductType(isBlank(product.getProductType()) ? existing.getProductType() : payTypeOf(product.getProductType()));
|
product.setProductType(isBlank(product.getProductType()) ? existing.getProductType() : payTypeOf(product.getProductType()));
|
||||||
product.setFileMd5(blankTo(product.getFileMd5(), existing.getFileMd5()));
|
product.setFileMd5(blankTo(product.getFileMd5(), existing.getFileMd5()));
|
||||||
product.setSourceFile(blankTo(product.getSourceFile(), existing.getSourceFile()));
|
product.setSourceFile(blankTo(product.getSourceFile(), existing.getSourceFile()));
|
||||||
|
product.setFilePath(blankTo(sourcePath(product.getFilePath()), existing.getFilePath()));
|
||||||
product.setSheetName(blankTo(product.getSheetName(), existing.getSheetName()));
|
product.setSheetName(blankTo(product.getSheetName(), existing.getSheetName()));
|
||||||
product.setUpdateBy(SecurityUtils.getUsername());
|
product.setUpdateBy(SecurityUtils.getUsername());
|
||||||
prepareCounts(product);
|
prepareCounts(product);
|
||||||
@@ -334,6 +357,7 @@ public class PayProductServiceImpl implements IPayProductService
|
|||||||
copy.setSheetName(blankTo(edited.getSheetName(), origin.getSheetName()));
|
copy.setSheetName(blankTo(edited.getSheetName(), origin.getSheetName()));
|
||||||
copy.setRelatedSheet(blankTo(edited.getRelatedSheet(), origin.getRelatedSheet()));
|
copy.setRelatedSheet(blankTo(edited.getRelatedSheet(), origin.getRelatedSheet()));
|
||||||
copy.setSourceFile(blankTo(edited.getSourceFile(), origin.getSourceFile()));
|
copy.setSourceFile(blankTo(edited.getSourceFile(), origin.getSourceFile()));
|
||||||
|
copy.setFilePath(blankTo(sourcePath(edited.getFilePath()), origin.getFilePath()));
|
||||||
copy.setFileMd5(blankTo(edited.getFileMd5(), origin.getFileMd5()));
|
copy.setFileMd5(blankTo(edited.getFileMd5(), origin.getFileMd5()));
|
||||||
copy.setSupplierName(blankTo(edited.getSupplierName(), origin.getSupplierName()));
|
copy.setSupplierName(blankTo(edited.getSupplierName(), origin.getSupplierName()));
|
||||||
copy.setEffectiveDate(edited.getEffectiveDate() != null ? edited.getEffectiveDate() : origin.getEffectiveDate());
|
copy.setEffectiveDate(edited.getEffectiveDate() != null ? edited.getEffectiveDate() : origin.getEffectiveDate());
|
||||||
@@ -453,6 +477,7 @@ public class PayProductServiceImpl implements IPayProductService
|
|||||||
term.setProductId(product.getProductId());
|
term.setProductId(product.getProductId());
|
||||||
term.setVersionNo(product.getVersionNo() == null ? 1 : product.getVersionNo());
|
term.setVersionNo(product.getVersionNo() == null ? 1 : product.getVersionNo());
|
||||||
term.setEffectiveStart(product.getEffectiveDate());
|
term.setEffectiveStart(product.getEffectiveDate());
|
||||||
|
term.setFilePath(sourcePath(product.getFilePath()));
|
||||||
term.setStatus("1");
|
term.setStatus("1");
|
||||||
term.setCreateBy(product.getCreateBy());
|
term.setCreateBy(product.getCreateBy());
|
||||||
payProductTermMapper.insertTerm(term);
|
payProductTermMapper.insertTerm(term);
|
||||||
@@ -477,6 +502,130 @@ public class PayProductServiceImpl implements IPayProductService
|
|||||||
return products;
|
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
|
@Override
|
||||||
public PayProduct selectPayProductDetail(Long productId)
|
public PayProduct selectPayProductDetail(Long productId)
|
||||||
{
|
{
|
||||||
@@ -647,6 +796,7 @@ public class PayProductServiceImpl implements IPayProductService
|
|||||||
product.setSheetName(cut(product.getSheetName(), 200));
|
product.setSheetName(cut(product.getSheetName(), 200));
|
||||||
product.setRelatedSheet(cut(product.getRelatedSheet(), 500));
|
product.setRelatedSheet(cut(product.getRelatedSheet(), 500));
|
||||||
product.setSourceFile(cut(product.getSourceFile(), 500));
|
product.setSourceFile(cut(product.getSourceFile(), 500));
|
||||||
|
product.setFilePath(cut(product.getFilePath(), 500));
|
||||||
product.setSupplierName(cut(product.getSupplierName(), 100));
|
product.setSupplierName(cut(product.getSupplierName(), 100));
|
||||||
product.setCurrency(cut(product.getCurrency(), 20));
|
product.setCurrency(cut(product.getCurrency(), 20));
|
||||||
product.setServiceNote(cut(product.getServiceNote(), 4000));
|
product.setServiceNote(cut(product.getServiceNote(), 4000));
|
||||||
@@ -675,6 +825,7 @@ public class PayProductServiceImpl implements IPayProductService
|
|||||||
fee.setZoneName(cut(fee.getZoneName(), 200));
|
fee.setZoneName(cut(fee.getZoneName(), 200));
|
||||||
fee.setChargeText(cut(fee.getChargeText(), 500));
|
fee.setChargeText(cut(fee.getChargeText(), 500));
|
||||||
fee.setCurrency(cut(fee.getCurrency(), 20));
|
fee.setCurrency(cut(fee.getCurrency(), 20));
|
||||||
|
fee.setPriceUnit(cut(fee.getPriceUnit(), 40));
|
||||||
fee.setFeeDesc(cut(fee.getFeeDesc(), 2000));
|
fee.setFeeDesc(cut(fee.getFeeDesc(), 2000));
|
||||||
fee.setPeriodText(cut(fee.getPeriodText(), 500));
|
fee.setPeriodText(cut(fee.getPeriodText(), 500));
|
||||||
fee.setChargeTiming(cut(fee.getChargeTiming(), 40));
|
fee.setChargeTiming(cut(fee.getChargeTiming(), 40));
|
||||||
@@ -1074,4 +1225,17 @@ public class PayProductServiceImpl implements IPayProductService
|
|||||||
{
|
{
|
||||||
return isBlank(value) ? fallback : value;
|
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 id="updateServiceChannel" parameterType="com.bms.masterdata.domain.ServiceChannel">
|
||||||
update srv_service_channel
|
update srv_service_channel
|
||||||
set channel_code = #{channelCode},
|
set channel_code = #{channelCode},
|
||||||
|
|
||||||
channel_name = #{channelName},
|
channel_name = #{channelName},
|
||||||
service_code = #{serviceCode},
|
service_code = #{serviceCode},
|
||||||
service_name = #{serviceName},
|
service_name = #{serviceName},
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<result property="sheetName" column="sheet_name"/>
|
<result property="sheetName" column="sheet_name"/>
|
||||||
<result property="relatedSheet" column="related_sheet"/>
|
<result property="relatedSheet" column="related_sheet"/>
|
||||||
<result property="sourceFile" column="source_file"/>
|
<result property="sourceFile" column="source_file"/>
|
||||||
|
<result property="filePath" column="file_path"/>
|
||||||
<result property="fileMd5" column="file_md5"/>
|
<result property="fileMd5" column="file_md5"/>
|
||||||
<result property="supplierName" column="supplier_name"/>
|
<result property="supplierName" column="supplier_name"/>
|
||||||
<result property="effectiveDate" column="effective_date"/>
|
<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,
|
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.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,
|
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.file_md5,
|
||||||
pay_product.supplier_name, pay_product.effective_date, ifnull(cur.currency, pay_product.currency) as currency,
|
pay_product.supplier_name, pay_product.effective_date, ifnull(cur.currency, pay_product.currency) as currency,
|
||||||
pay_product.service_note,
|
pay_product.service_note,
|
||||||
@@ -137,7 +139,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
|
|
||||||
<select id="selectPayProductById" parameterType="long" resultMap="PayProductResult">
|
<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,
|
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,
|
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
|
surcharge_count, extra_count, created_by, create_time, updated_by, update_time, remark
|
||||||
from pay_product
|
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 id="insertPayProduct" parameterType="com.bms.price.domain.PayProduct" useGeneratedKeys="true" keyProperty="productId">
|
||||||
insert into pay_product (
|
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,
|
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,
|
supplier_name, effective_date, currency, service_note, parse_status, weight_count, surcharge_count,
|
||||||
extra_count, created_by, create_time, remark
|
extra_count, created_by, create_time, remark
|
||||||
) values (
|
) values (
|
||||||
#{productName}, #{productType}, #{dictValue}, #{originId}, #{channelId}, ifnull(#{versionNo}, 1), #{countryScope}, #{chargeType}, #{extraPerKg},
|
#{productName}, #{productType}, #{dictValue}, #{originId}, #{channelId}, ifnull(#{versionNo}, 1), #{countryScope}, #{chargeType}, #{extraPerKg},
|
||||||
#{serviceType}, #{classification}, #{catalogRemark}, #{updateContent}, #{weightLimit}, #{sheetName},
|
#{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(),
|
#{serviceNote}, #{parseStatus}, #{weightCount}, #{surchargeCount}, #{extraCount}, #{createBy}, sysdate(),
|
||||||
#{remark}
|
#{remark}
|
||||||
)
|
)
|
||||||
@@ -184,6 +186,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
sheet_name = #{sheetName},
|
sheet_name = #{sheetName},
|
||||||
related_sheet = #{relatedSheet},
|
related_sheet = #{relatedSheet},
|
||||||
source_file = #{sourceFile},
|
source_file = #{sourceFile},
|
||||||
|
file_path = #{filePath},
|
||||||
file_md5 = #{fileMd5},
|
file_md5 = #{fileMd5},
|
||||||
supplier_name = #{supplierName},
|
supplier_name = #{supplierName},
|
||||||
effective_date = #{effectiveDate},
|
effective_date = #{effectiveDate},
|
||||||
@@ -246,12 +249,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
|
|
||||||
<insert id="insertSurcharges">
|
<insert id="insertSurcharges">
|
||||||
insert into pay_surcharge (
|
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
|
effective_start, effective_end, charge_timing, calc_rule, calc_param, sort_no
|
||||||
) values
|
) values
|
||||||
<foreach collection="list" item="row" separator=",">
|
<foreach collection="list" item="row" separator=",">
|
||||||
(#{row.productId}, ifnull(#{row.versionNo}, 1), #{row.sectionName}, #{row.feeName}, #{row.zoneName}, #{row.chargeText}, #{row.amount},
|
(#{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})
|
#{row.chargeTiming}, ifnull(#{row.calcRule}, 'MANUAL'), #{row.calcParam}, #{row.sortNo})
|
||||||
</foreach>
|
</foreach>
|
||||||
</insert>
|
</insert>
|
||||||
@@ -275,7 +278,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
|
|
||||||
<select id="selectSurcharges" resultType="com.bms.price.domain.PaySurcharge">
|
<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,
|
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,
|
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
|
charge_timing as chargeTiming, calc_rule as calcRule, calc_param as calcParam, sort_no as sortNo
|
||||||
from pay_surcharge
|
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,
|
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.effective_start as effectiveStart, t.effective_end as effectiveEnd, t.status,
|
||||||
t.created_by as createBy, t.create_time as createTime, t.remark,
|
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
|
p.surcharge_count as surchargeCount, p.currency
|
||||||
from pay_product_term t
|
from pay_product_term t
|
||||||
join pay_product p on p.product_id = t.product_id
|
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 id="insertTerm" parameterType="com.bms.price.domain.PayProductTerm" useGeneratedKeys="true" keyProperty="termId">
|
||||||
insert into pay_product_term (
|
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 (
|
) values (
|
||||||
#{dictValue}, #{productId}, ifnull(#{versionNo}, 1), #{effectiveStart}, #{effectiveEnd},
|
#{dictValue}, #{productId}, ifnull(#{versionNo}, 1), #{effectiveStart}, #{effectiveEnd}, #{filePath},
|
||||||
ifnull(#{status}, '0'), #{createBy}, sysdate(), #{remark}
|
ifnull(#{status}, '0'), #{createBy}, sysdate(), #{remark}
|
||||||
)
|
)
|
||||||
</insert>
|
</insert>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ CREATE TABLE IF NOT EXISTS `pay_product` (
|
|||||||
`sheet_name` varchar(200) NOT NULL COMMENT '报价工作表',
|
`sheet_name` varchar(200) NOT NULL COMMENT '报价工作表',
|
||||||
`related_sheet` varchar(500) DEFAULT NULL COMMENT '目录里一并指向的分区或附加表,不单独入库',
|
`related_sheet` varchar(500) DEFAULT NULL COMMENT '目录里一并指向的分区或附加表,不单独入库',
|
||||||
`source_file` varchar(500) NOT 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',
|
`file_md5` char(32) NOT NULL COMMENT '来源文件MD5',
|
||||||
`supplier_name` varchar(100) DEFAULT NULL COMMENT '价卡归属,如威狮、易德、GWE',
|
`supplier_name` varchar(100) DEFAULT NULL COMMENT '价卡归属,如威狮、易德、GWE',
|
||||||
`effective_date` date DEFAULT NULL COMMENT '价卡生效日',
|
`effective_date` date DEFAULT NULL COMMENT '价卡生效日',
|
||||||
@@ -65,6 +66,7 @@ CREATE TABLE IF NOT EXISTS `pay_surcharge` (
|
|||||||
`charge_text` varchar(500) DEFAULT NULL COMMENT '收费标准原文',
|
`charge_text` varchar(500) DEFAULT NULL COMMENT '收费标准原文',
|
||||||
`amount` decimal(12,4) DEFAULT NULL COMMENT '能解析出的金额或百分比',
|
`amount` decimal(12,4) DEFAULT NULL COMMENT '能解析出的金额或百分比',
|
||||||
`currency` varchar(20) 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 '费用说明',
|
`fee_desc` varchar(2000) DEFAULT NULL COMMENT '费用说明',
|
||||||
`period_text` varchar(500) DEFAULT NULL COMMENT '时效原文',
|
`period_text` varchar(500) DEFAULT NULL COMMENT '时效原文',
|
||||||
`effective_start` date DEFAULT NULL COMMENT '生效开始,空表示价卡期内一直有效',
|
`effective_start` date DEFAULT NULL COMMENT '生效开始,空表示价卡期内一直有效',
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -8,6 +8,7 @@ CREATE TABLE IF NOT EXISTS `pay_product_term` (
|
|||||||
`version_no` int(11) NOT NULL DEFAULT 1 COMMENT '版本号',
|
`version_no` int(11) NOT NULL DEFAULT 1 COMMENT '版本号',
|
||||||
`effective_start` date DEFAULT NULL COMMENT '生效开始',
|
`effective_start` date DEFAULT NULL COMMENT '生效开始',
|
||||||
`effective_end` 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失效',
|
`status` char(1) NOT NULL DEFAULT '0' COMMENT '0生效 1失效',
|
||||||
`created_by` varchar(64) DEFAULT '' COMMENT '创建者',
|
`created_by` varchar(64) DEFAULT '' COMMENT '创建者',
|
||||||
`create_time` datetime DEFAULT NULL 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">
|
<el-tab-pane :label="`附加费(${(pane.item.surcharges || []).length})`" name="fee">
|
||||||
<div class="pay-edit-bar">
|
<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>
|
<el-button v-if="editable" size="small" type="primary" plain @click="addFee(pane)">添加附加费</el-button>
|
||||||
</div>
|
</div>
|
||||||
<el-table :data="pageFees(pane)" border size="small" max-height="360" empty-text="没有附加费">
|
<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>
|
<el-table-column prop="zoneName" label="地区" min-width="160" show-overflow-tooltip>
|
||||||
<template #default="{ row }"><cell-text v-model="row.zoneName" :edit="editable" /></template>
|
<template #default="{ row }"><cell-text v-model="row.zoneName" :edit="editable" /></template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="chargeText" label="收费标准" min-width="180" show-overflow-tooltip>
|
<el-table-column label="价格" width="110" align="right">
|
||||||
<template #default="{ row }"><cell-text v-model="row.chargeText" :edit="editable" /></template>
|
<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>
|
||||||
<el-table-column prop="feeDesc" label="费用解释" min-width="240" show-overflow-tooltip>
|
<el-table-column prop="feeDesc" label="费用解释" min-width="240" show-overflow-tooltip>
|
||||||
<template #default="{ row }"><cell-text v-model="row.feeDesc" :edit="editable" /></template>
|
<template #default="{ row }"><cell-text v-model="row.feeDesc" :edit="editable" /></template>
|
||||||
@@ -214,7 +229,7 @@ function filteredBands(pane) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function filteredFees(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) {
|
function addBand(pane) {
|
||||||
@@ -225,7 +240,7 @@ function addBand(pane) {
|
|||||||
|
|
||||||
function addFee(pane) {
|
function addFee(pane) {
|
||||||
if (!pane.item.surcharges) pane.item.surcharges = []
|
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
|
pane.feePageNo = Math.ceil(pane.item.surcharges.length / pageSize) || 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,6 +305,19 @@ function money(value) {
|
|||||||
return Number.isNaN(num) ? value : String(num)
|
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) {
|
function periodLabel(row) {
|
||||||
if (row.effectiveStart || row.effectiveEnd) {
|
if (row.effectiveStart || row.effectiveEnd) {
|
||||||
return `${row.effectiveStart || '…'} 至 ${row.effectiveEnd || '…'}`
|
return `${row.effectiveStart || '…'} 至 ${row.effectiveEnd || '…'}`
|
||||||
|
|||||||
@@ -24,6 +24,11 @@
|
|||||||
</el-col>
|
</el-col>
|
||||||
</template>
|
</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 }">
|
<template #col-parseStatus="{ row }">
|
||||||
<el-tag :type="statusType(row.parseStatus)" size="small">{{ statusText(row.parseStatus) }}</el-tag>
|
<el-tag :type="statusType(row.parseStatus)" size="small">{{ statusText(row.parseStatus) }}</el-tag>
|
||||||
</template>
|
</template>
|
||||||
@@ -56,7 +61,7 @@
|
|||||||
|
|
||||||
<pay-import-dialog ref="importRef" @saved="tableRef.refresh()" />
|
<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">
|
<div class="history-bar">
|
||||||
<el-button type="primary" plain :disabled="historyPicked.length !== 2" @click="openCompare">比对所选版本</el-button>
|
<el-button type="primary" plain :disabled="historyPicked.length !== 2" @click="openCompare">比对所选版本</el-button>
|
||||||
<span>勾选两个版本后比对。历史订单按生效时间取价,不看当前开关。</span>
|
<span>勾选两个版本后比对。历史订单按生效时间取价,不看当前开关。</span>
|
||||||
@@ -64,7 +69,12 @@
|
|||||||
<el-table v-loading="historyLoading" :data="historyRows" border size="small" empty-text="还没有价格版本" @selection-change="onHistoryPick">
|
<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 type="selection" width="42" align="center" />
|
||||||
<el-table-column prop="versionNo" label="版本" width="70" 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="weightCount" label="重量段" width="80" align="center" />
|
||||||
<el-table-column prop="surchargeCount" label="附加费" width="80" align="center" />
|
<el-table-column prop="surchargeCount" label="附加费" width="80" align="center" />
|
||||||
<el-table-column label="生效开始" width="110" align="center">
|
<el-table-column label="生效开始" width="110" align="center">
|
||||||
@@ -88,8 +98,9 @@
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="150" align="center">
|
<el-table-column label="操作" width="210" align="center">
|
||||||
<template #default="{ row }">
|
<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="openTermDialog(row)" v-hasPermi="['price:pay:edit']">修改有效期</el-button>
|
||||||
<el-button link type="primary" @click="openDetail(row)" v-hasPermi="['price:pay:query']">查看</el-button>
|
<el-button link type="primary" @click="openDetail(row)" v-hasPermi="['price:pay:query']">查看</el-button>
|
||||||
</template>
|
</template>
|
||||||
@@ -126,6 +137,7 @@
|
|||||||
<el-drawer v-model="detailVisible" :title="detailTitle" size="78%" destroy-on-close>
|
<el-drawer v-model="detailVisible" :title="detailTitle" size="78%" destroy-on-close>
|
||||||
<pay-config-detail v-loading="detailLoading" :product="detail" :editable="editing" />
|
<pay-config-detail v-loading="detailLoading" :product="detail" :editable="editing" />
|
||||||
<template #footer>
|
<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-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 v-else type="primary" :loading="saving" @click="saveDetail">保存</el-button>
|
||||||
<el-button @click="detailVisible = false">关闭</el-button>
|
<el-button @click="detailVisible = false">关闭</el-button>
|
||||||
@@ -229,6 +241,18 @@ function currentQuery() {
|
|||||||
return query
|
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() {
|
function handleExport() {
|
||||||
proxy.download('/price/pay/export', currentQuery(), `应付配置_${Date.now()}.xlsx`)
|
proxy.download('/price/pay/export', currentQuery(), `应付配置_${Date.now()}.xlsx`)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user