修改导入问题
This commit is contained in:
+296
-83
@@ -23,7 +23,11 @@ import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.apache.poi.ss.usermodel.WorkbookFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.support.DefaultTransactionDefinition;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.common.utils.MessageUtils;
|
||||
@@ -73,6 +77,9 @@ public class BmsProductServiceImpl implements IBmsProductService
|
||||
@Autowired
|
||||
private IPrcSurchargeRelService surchargeRelService;
|
||||
|
||||
@Autowired
|
||||
private PlatformTransactionManager transactionManager;
|
||||
|
||||
@Override
|
||||
public BmsProduct selectBmsProductById(Long productId)
|
||||
{
|
||||
@@ -207,7 +214,6 @@ public class BmsProductServiceImpl implements IBmsProductService
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String importProductExcel(MultipartFile file, boolean updateSupport, String operName) throws Exception
|
||||
{
|
||||
if (file == null || file.isEmpty())
|
||||
@@ -234,12 +240,14 @@ public class BmsProductServiceImpl implements IBmsProductService
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.product.import.noData"));
|
||||
}
|
||||
products = dedupeCatalogProducts(products);
|
||||
|
||||
Map<String, BmsRegion> regionByNameCn = loadRegionByNameCn();
|
||||
if (regionByNameCn.isEmpty())
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.product.import.regionEmpty"));
|
||||
}
|
||||
List<BmsRegion> euRegions = loadEuRegions(regionByNameCn);
|
||||
|
||||
int successNum = 0;
|
||||
int skipNum = 0;
|
||||
@@ -249,61 +257,35 @@ public class BmsProductServiceImpl implements IBmsProductService
|
||||
|
||||
for (BmsProduct product : products)
|
||||
{
|
||||
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
|
||||
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
|
||||
TransactionStatus tx = transactionManager.getTransaction(def);
|
||||
try
|
||||
{
|
||||
product.setSourceSheet(product.getProductName());
|
||||
product.setCreateBy(operName);
|
||||
product.setUpdateBy(operName);
|
||||
|
||||
List<BmsProductCountry> countries = parseProductSheet(workbook, product, regionByNameCn, unmatchedCountries);
|
||||
// Sheet header date is the price version; catalog column is often stale
|
||||
Date sheetEff = resolveImportEffectiveDate(countries);
|
||||
Date newEff = toDateOnly(sheetEff != null ? sheetEff : product.getEffectiveDate());
|
||||
product.setEffectiveDate(newEff);
|
||||
if (newEff == null)
|
||||
{
|
||||
failureMsg.append("<br/>").append(product.getProductCode()).append("\uff1a")
|
||||
.append(MessageUtils.message("price.product.import.effEmpty"));
|
||||
continue;
|
||||
}
|
||||
BmsProduct existing = productMapper.selectBmsProductByCodeAndDate(
|
||||
product.getProductCode(), newEff);
|
||||
|
||||
if (existing != null)
|
||||
{
|
||||
skipNum++;
|
||||
}
|
||||
else
|
||||
{
|
||||
applyProductStatus(product);
|
||||
try
|
||||
{
|
||||
productMapper.insertBmsProduct(product);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (isProductCodeUniqueViolation(e))
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.product.import.needCodeEffUnique"));
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
productMapper.expireOpenByProductCode(product.getProductCode(), newEff, product.getProductId());
|
||||
productMapper.refreshStatusByDates();
|
||||
countryNum += saveCountriesVersioned(product, countries);
|
||||
successNum++;
|
||||
}
|
||||
int[] nums = importOneProduct(workbook, product, operName, regionByNameCn, euRegions,
|
||||
unmatchedCountries);
|
||||
transactionManager.commit(tx);
|
||||
successNum += nums[0];
|
||||
skipNum += nums[1];
|
||||
countryNum += nums[2];
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
failureMsg.append("<br/>").append(product.getProductCode()).append("\uff1a").append(e.getMessage());
|
||||
transactionManager.rollback(tx);
|
||||
String tip = e.getMessage();
|
||||
if (StringUtils.isEmpty(tip))
|
||||
{
|
||||
tip = e.getClass().getSimpleName();
|
||||
}
|
||||
failureMsg.append("<br/>").append(product.getProductCode()).append("\uff1a").append(tip);
|
||||
}
|
||||
}
|
||||
|
||||
if (failureMsg.length() > 0)
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.product.import.partialFail",
|
||||
successNum, skipNum, failureMsg.toString()));
|
||||
// Do not throw: each product already committed/rolled back independently
|
||||
return MessageUtils.message("price.product.import.partialFail",
|
||||
Integer.valueOf(successNum), Integer.valueOf(skipNum), failureMsg.toString());
|
||||
}
|
||||
String msg = MessageUtils.message("price.product.import.success", successNum, skipNum, countryNum)
|
||||
+ "<br/>" + MessageUtils.message("price.product.import.success.detail1")
|
||||
@@ -316,10 +298,68 @@ public class BmsProductServiceImpl implements IBmsProductService
|
||||
}
|
||||
}
|
||||
|
||||
/** @return int[]{added, skipped, countryRows} */
|
||||
private int[] importOneProduct(Workbook workbook, BmsProduct product, String operName,
|
||||
Map<String, BmsRegion> regionByNameCn, List<BmsRegion> euRegions, Set<String> unmatchedCountries)
|
||||
{
|
||||
product.setSourceSheet(product.getProductName());
|
||||
product.setCreateBy(operName);
|
||||
product.setUpdateBy(operName);
|
||||
|
||||
Sheet productSheet = findProductSheet(workbook, product);
|
||||
if (productSheet == null)
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.product.import.noSheet",
|
||||
product.getProductName()));
|
||||
}
|
||||
product.setSourceSheet(productSheet.getSheetName());
|
||||
List<BmsProductCountry> countries = parseProductSheet(productSheet, product,
|
||||
regionByNameCn, euRegions, unmatchedCountries);
|
||||
if (countries == null || countries.isEmpty())
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.product.import.noCountry",
|
||||
productSheet.getSheetName()));
|
||||
}
|
||||
Date sheetEff = resolveImportEffectiveDate(countries);
|
||||
Date newEff = toDateOnly(sheetEff != null ? sheetEff : product.getEffectiveDate());
|
||||
product.setEffectiveDate(newEff);
|
||||
if (newEff == null)
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.product.import.effEmpty"));
|
||||
}
|
||||
BmsProduct existing = productMapper.selectBmsProductByCodeAndDate(
|
||||
product.getProductCode(), newEff);
|
||||
|
||||
if (existing != null)
|
||||
{
|
||||
product.setProductId(existing.getProductId());
|
||||
int countryRows = saveCountriesVersioned(product, countries);
|
||||
return new int[] { 0, 1, countryRows };
|
||||
}
|
||||
|
||||
applyProductStatus(product);
|
||||
try
|
||||
{
|
||||
productMapper.insertBmsProduct(product);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (isProductCodeUniqueViolation(e))
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.product.import.needCodeEffUnique"));
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
productMapper.expireOpenByProductCode(product.getProductCode(), newEff, product.getProductId());
|
||||
productMapper.refreshStatusByDates();
|
||||
int countryRows = saveCountriesVersioned(product, countries);
|
||||
return new int[] { 1, 0, countryRows };
|
||||
}
|
||||
|
||||
/**
|
||||
* Versioned country save:
|
||||
* - newEff > last open: close old (expire_date = newEff), insert new (keep history)
|
||||
* - newEff equals last open: skip (never overwrite)
|
||||
* - newEff equals last open: replace open rows
|
||||
* - newEff < last open: reject
|
||||
*/
|
||||
private int saveCountriesVersioned(BmsProduct product, List<BmsProductCountry> countries)
|
||||
@@ -347,8 +387,8 @@ public class BmsProductServiceImpl implements IBmsProductService
|
||||
}
|
||||
else if (lastOpenEff != null)
|
||||
{
|
||||
// Same effective day: never overwrite existing country rows
|
||||
return 0;
|
||||
// Same effective day: replace open rows so re-import can fill/fix details
|
||||
countryMapper.deleteOpenByProductCodeAndEffectiveDate(productCode, newEff);
|
||||
}
|
||||
|
||||
for (BmsProductCountry c : countries)
|
||||
@@ -543,27 +583,141 @@ public class BmsProductServiceImpl implements IBmsProductService
|
||||
return list;
|
||||
}
|
||||
|
||||
private List<BmsProductCountry> parseProductSheet(Workbook workbook, BmsProduct product,
|
||||
Map<String, BmsRegion> regionByNameCn, Set<String> unmatchedCountries)
|
||||
/** Keep first row per product_code (catalog often repeats code across country rows). */
|
||||
private List<BmsProduct> dedupeCatalogProducts(List<BmsProduct> products)
|
||||
{
|
||||
String sheetName = product.getProductName();
|
||||
Sheet sheet = workbook.getSheet(sheetName);
|
||||
if (sheet == null)
|
||||
Map<String, BmsProduct> map = new HashMap<>();
|
||||
for (BmsProduct p : products)
|
||||
{
|
||||
for (int i = 0; i < workbook.getNumberOfSheets(); i++)
|
||||
if (p == null || StringUtils.isEmpty(p.getProductCode()))
|
||||
{
|
||||
String name = workbook.getSheetName(i);
|
||||
if (SKIP_SHEETS.contains(name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (name != null && name.trim().equals(sheetName.trim()))
|
||||
{
|
||||
sheet = workbook.getSheetAt(i);
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
String code = p.getProductCode().trim().toUpperCase(Locale.ROOT);
|
||||
if (!map.containsKey(code))
|
||||
{
|
||||
map.put(code, p);
|
||||
}
|
||||
}
|
||||
return new ArrayList<>(map.values());
|
||||
}
|
||||
|
||||
private Sheet findProductSheet(Workbook workbook, BmsProduct product)
|
||||
{
|
||||
if (workbook == null || product == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
String wantName = product.getProductName();
|
||||
String wantCode = product.getProductCode();
|
||||
if (StringUtils.isNotEmpty(wantName))
|
||||
{
|
||||
Sheet exact = workbook.getSheet(wantName.trim());
|
||||
if (exact != null)
|
||||
{
|
||||
return exact;
|
||||
}
|
||||
}
|
||||
String nameKey = normalizeSheetKey(wantName);
|
||||
String codeKey = normalizeSheetKey(wantCode);
|
||||
Sheet best = null;
|
||||
int bestScore = 0;
|
||||
for (int i = 0; i < workbook.getNumberOfSheets(); i++)
|
||||
{
|
||||
String name = workbook.getSheetName(i);
|
||||
if (name == null || SKIP_SHEETS.contains(name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
String key = normalizeSheetKey(name);
|
||||
if (StringUtils.isEmpty(key))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
int score = 0;
|
||||
if (StringUtils.isNotEmpty(nameKey) && key.equals(nameKey))
|
||||
{
|
||||
return workbook.getSheetAt(i);
|
||||
}
|
||||
if (StringUtils.isNotEmpty(nameKey) && key.contains(nameKey))
|
||||
{
|
||||
score = 100;
|
||||
}
|
||||
else if (StringUtils.isNotEmpty(nameKey) && nameKey.contains(key) && key.length() >= 4)
|
||||
{
|
||||
score = 80;
|
||||
}
|
||||
else if (StringUtils.isNotEmpty(codeKey) && key.contains(codeKey))
|
||||
{
|
||||
score = 60;
|
||||
}
|
||||
if (score > bestScore)
|
||||
{
|
||||
bestScore = score;
|
||||
best = workbook.getSheetAt(i);
|
||||
}
|
||||
}
|
||||
return bestScore >= 60 ? best : null;
|
||||
}
|
||||
|
||||
private static String normalizeSheetKey(String s)
|
||||
{
|
||||
if (s == null)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
String out = s.trim().toLowerCase(Locale.ROOT);
|
||||
// unify dash variants: - \u2013 \u2014 \uff0d
|
||||
out = out.replace('\u2013', '-').replace('\u2014', '-').replace('\uff0d', '-');
|
||||
out = out.replace(" ", "").replace("\u3000", "");
|
||||
return out;
|
||||
}
|
||||
|
||||
private List<BmsRegion> loadEuRegions(Map<String, BmsRegion> regionByNameCn)
|
||||
{
|
||||
List<BmsRegion> list = new ArrayList<>();
|
||||
if (regionByNameCn == null)
|
||||
{
|
||||
return list;
|
||||
}
|
||||
Set<String> seen = new HashSet<>();
|
||||
for (BmsRegion region : regionByNameCn.values())
|
||||
{
|
||||
if (region == null || !"1".equals(region.getIsEu()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (StringUtils.isEmpty(region.getCountryNameCn()) || StringUtils.isEmpty(region.getCountryCode2()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
String code = region.getCountryCode2().toUpperCase(Locale.ROOT);
|
||||
if (seen.contains(code))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
seen.add(code);
|
||||
list.add(region);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private static boolean isEuZoneName(String name)
|
||||
{
|
||||
if (StringUtils.isEmpty(name))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
String n = name.trim();
|
||||
return "\u6b27\u76df".equals(n)
|
||||
|| "EU".equalsIgnoreCase(n)
|
||||
|| "\u6b27\u6d32\u8054\u76df".equals(n)
|
||||
|| "\u6b27\u76df\u56fd\u5bb6".equals(n);
|
||||
}
|
||||
|
||||
private List<BmsProductCountry> parseProductSheet(Sheet sheet, BmsProduct product,
|
||||
Map<String, BmsRegion> regionByNameCn, List<BmsRegion> euRegions, Set<String> unmatchedCountries)
|
||||
{
|
||||
if (sheet == null)
|
||||
{
|
||||
return new ArrayList<>();
|
||||
@@ -631,7 +785,7 @@ public class BmsProductServiceImpl implements IBmsProductService
|
||||
}
|
||||
if (StringUtils.isNotEmpty(countryCell))
|
||||
{
|
||||
currentCountry = countryCell.trim();
|
||||
currentCountry = normalizeCountryName(countryCell);
|
||||
currentSize = emptyToNull(cellString(row.getCell(colSize)));
|
||||
currentLastMile = emptyToNull(cellString(row.getCell(colLastMile)));
|
||||
currentLeadTime = emptyToNull(cellString(row.getCell(colLeadTime)));
|
||||
@@ -645,32 +799,54 @@ public class BmsProductServiceImpl implements IBmsProductService
|
||||
{
|
||||
continue;
|
||||
}
|
||||
BigDecimal freight = cellDecimal(row.getCell(colFreight));
|
||||
BigDecimal registration = cellDecimal(row.getCell(colRegister));
|
||||
Date eff = sheetEffective != null ? sheetEffective : product.getEffectiveDate();
|
||||
|
||||
BmsRegion region = resolveRegion(currentCountry, regionByNameCn);
|
||||
BmsProductCountry c = new BmsProductCountry();
|
||||
if (region != null)
|
||||
{
|
||||
c.setCountryName(region.getCountryNameCn());
|
||||
c.setCountryCode(region.getCountryCode2());
|
||||
list.add(buildCountryRow(region.getCountryNameCn(), region.getCountryCode2(),
|
||||
range, freight, registration, currentSize, currentLastMile, currentLeadTime, eff));
|
||||
continue;
|
||||
}
|
||||
else
|
||||
if (isEuZoneName(currentCountry))
|
||||
{
|
||||
c.setCountryName(currentCountry);
|
||||
c.setCountryCode(null);
|
||||
unmatchedCountries.add(currentCountry);
|
||||
if (euRegions == null || euRegions.isEmpty())
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.product.import.euEmpty"));
|
||||
}
|
||||
for (BmsRegion eu : euRegions)
|
||||
{
|
||||
list.add(buildCountryRow(eu.getCountryNameCn(), eu.getCountryCode2(),
|
||||
range, freight, registration, currentSize, currentLastMile, currentLeadTime, eff));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
c.setWeightMin(range[0]);
|
||||
c.setWeightMax(range[1]);
|
||||
c.setFreightPrice(cellDecimal(row.getCell(colFreight)));
|
||||
c.setRegistrationFee(cellDecimal(row.getCell(colRegister)));
|
||||
c.setSizeLimit(currentSize);
|
||||
c.setLastMile(currentLastMile);
|
||||
c.setLeadTime(currentLeadTime);
|
||||
c.setEffectiveDate(sheetEffective != null ? sheetEffective : product.getEffectiveDate());
|
||||
list.add(c);
|
||||
unmatchedCountries.add(currentCountry);
|
||||
list.add(buildCountryRow(currentCountry, null,
|
||||
range, freight, registration, currentSize, currentLastMile, currentLeadTime, eff));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private BmsProductCountry buildCountryRow(String countryName, String countryCode, BigDecimal[] range,
|
||||
BigDecimal freight, BigDecimal registration, String sizeLimit, String lastMile, String leadTime, Date eff)
|
||||
{
|
||||
BmsProductCountry c = new BmsProductCountry();
|
||||
c.setCountryName(countryName);
|
||||
c.setCountryCode(countryCode);
|
||||
c.setWeightMin(range[0]);
|
||||
c.setWeightMax(range[1]);
|
||||
c.setFreightPrice(freight);
|
||||
c.setRegistrationFee(registration);
|
||||
c.setSizeLimit(sizeLimit);
|
||||
c.setLastMile(lastMile);
|
||||
c.setLeadTime(leadTime);
|
||||
c.setEffectiveDate(eff);
|
||||
return c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load bms_region map: country_name_cn -> region (for country_code_2).
|
||||
*/
|
||||
@@ -723,11 +899,48 @@ public class BmsProductServiceImpl implements IBmsProductService
|
||||
candidates.add(region);
|
||||
}
|
||||
}
|
||||
if (candidates.isEmpty())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (candidates.size() == 1)
|
||||
{
|
||||
return candidates.get(0);
|
||||
}
|
||||
return null;
|
||||
// Prefer longest Chinese name match (e.g. 捷克共和国 over 捷克)
|
||||
BmsRegion best = candidates.get(0);
|
||||
for (int i = 1; i < candidates.size(); i++)
|
||||
{
|
||||
String cn = candidates.get(i).getCountryNameCn();
|
||||
if (cn != null && cn.length() > best.getCountryNameCn().length())
|
||||
{
|
||||
best = candidates.get(i);
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
/** Strip Excel footnotes like "澳大利亚(1区)\\n分区点击" */
|
||||
private static String normalizeCountryName(String name)
|
||||
{
|
||||
if (name == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
String n = name.trim();
|
||||
int cut = n.length();
|
||||
int nl = n.indexOf('\n');
|
||||
if (nl >= 0 && nl < cut)
|
||||
{
|
||||
cut = nl;
|
||||
}
|
||||
int cr = n.indexOf('\r');
|
||||
if (cr >= 0 && cr < cut)
|
||||
{
|
||||
cut = cr;
|
||||
}
|
||||
n = n.substring(0, cut).trim();
|
||||
return n.isEmpty() ? null : n;
|
||||
}
|
||||
|
||||
private BigDecimal[] parseWeightRange(String label)
|
||||
|
||||
+1
-1
@@ -747,7 +747,7 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
|
||||
|
||||
private static String formatWeightLabel(BigDecimal min, BigDecimal max)
|
||||
{
|
||||
return stripTrailingZeros(min) + "\u2264W\u2264" + stripTrailingZeros(max);
|
||||
return stripTrailingZeros(min) + "<W\u2264" + stripTrailingZeros(max);
|
||||
}
|
||||
|
||||
private static String stripTrailingZeros(BigDecimal v)
|
||||
|
||||
+1
-1
@@ -897,7 +897,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
||||
|
||||
private static String formatWeightLabel(BigDecimal min, BigDecimal max)
|
||||
{
|
||||
return strip(min) + "\u2264W\u2264" + strip(max);
|
||||
return strip(min) + "<W\u2264" + strip(max);
|
||||
}
|
||||
|
||||
private static String strip(BigDecimal v)
|
||||
|
||||
Reference in New Issue
Block a user