Implement product Excel file import functionality, including new service methods for handling Excel attachments. Update the ProductController to list imported Excel files by product code. Enhance BmsProduct and related entities to store references to the imported Excel files. Update front-end components to support displaying and managing Excel file attachments for products.

This commit is contained in:
wuweihua
2026-10-09 16:46:52 +08:00
parent 853187ad58
commit 533d360cdc
21 changed files with 589 additions and 31 deletions
@@ -33,6 +33,8 @@ public class BmsProduct extends BaseEntity
public static final String STATUS_EXPIRED = "2";
private String status;
private String sourceSheet;
/** FK prc_price_excel_file.pef_id */
private Long pefId;
private Long tmsId;
private Integer countryCount;
private Integer surchargeCount;
@@ -0,0 +1,35 @@
package com.bms.price.domain;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.bms.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* Import quote Excel attachment: prc_price_excel_file
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class PrcPriceExcelFile extends BaseEntity
{
private static final long serialVersionUID = 1L;
public static final String TYPE_PRODUCT = "PRODUCT";
public static final String TYPE_REDLINE = "REDLINE";
public static final String TYPE_SALES = "SALES";
private Long pefId;
/** PRODUCT / REDLINE / SALES */
private String excelType;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date effectiveTime;
private String fileName;
private String filePath;
private Long fileSize;
private String contentType;
private String fileExtension;
private String ossId;
/** Comma-separated sheet names (optional) */
private String sheetNames;
}
@@ -48,4 +48,9 @@ public class PrcPriceSet extends BaseEntity
private String pullFromLibrary;
/** Y: even if version already has details, delete+re-pull from library */
private String forcePullLibrary;
/**
* Request-only: stamp this Excel attachment id onto prc_price_set_product.pef_id
* when saving IMPORT details (not a column on prc_price_set).
*/
private Long pefId;
}
@@ -37,6 +37,8 @@ public class PrcPriceSetProduct implements Serializable
private Long sourceProductId;
/** 来源Excel sheet */
private String sourceSheet;
/** 关联导入价表Excel附件 prc_price_excel_file.pef_id */
private Long pefId;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@@ -0,0 +1,24 @@
package com.bms.price.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.bms.price.domain.PrcPriceExcelFile;
/**
* Import quote Excel attachment mapper
*/
public interface PrcPriceExcelFileMapper
{
public PrcPriceExcelFile selectPrcPriceExcelFileById(Long pefId);
public List<PrcPriceExcelFile> selectPrcPriceExcelFileList(PrcPriceExcelFile query);
/** Distinct PRODUCT excel files linked by product.pef_id for this product_code */
public List<PrcPriceExcelFile> selectByProductCode(@Param("productCode") String productCode);
public int insertPrcPriceExcelFile(PrcPriceExcelFile row);
public int updatePrcPriceExcelFile(PrcPriceExcelFile row);
public int deletePrcPriceExcelFileByIds(Long[] pefIds);
}
@@ -34,4 +34,8 @@ public interface PrcPriceSetProductMapper
/** Bind detail.psp_id from set product by psv + product_code. */
public int bindDetailPspByPsvId(@Param("psvId") Long psvId);
/** Stamp Excel attachment id on set products under this version (optional code filter). */
public int updatePefIdByPsvId(@Param("psvId") Long psvId, @Param("pefId") Long pefId,
@Param("productCodes") List<String> productCodes);
}
@@ -0,0 +1,25 @@
package com.bms.price.service;
import java.util.Date;
import java.util.List;
import com.bms.price.domain.PrcPriceExcelFile;
/**
* Import quote Excel attachment service
*/
public interface IPrcPriceExcelFileService
{
public PrcPriceExcelFile selectPrcPriceExcelFileById(Long pefId);
/** Import Excel attachments linked to this product_code via product.pef_id */
public List<PrcPriceExcelFile> selectByProductCode(String productCode);
/**
* Persist uploaded Excel under upload/price/excel and insert metadata row.
*
* @param excelType PRODUCT / REDLINE / SALES
* @return inserted row with pefId
*/
public PrcPriceExcelFile saveImportExcel(String excelType, Date effectiveTime, byte[] bytes,
String filename, String operName) throws Exception;
}
@@ -41,7 +41,9 @@ import com.bms.price.domain.PrcZone;
import com.bms.price.mapper.BmsProductCountryMapper;
import com.bms.price.mapper.BmsProductMapper;
import com.bms.price.mapper.PrcZoneMapper;
import com.bms.price.domain.PrcPriceExcelFile;
import com.bms.price.service.IBmsProductService;
import com.bms.price.service.IPrcPriceExcelFileService;
import com.bms.price.service.IPrcSurchargeRelService;
import com.bms.price.task.BmsProductImportAsyncRunner;
import com.bms.price.util.ZoneCodeAbbrevs;
@@ -94,6 +96,9 @@ public class BmsProductServiceImpl implements IBmsProductService
@Autowired
private BmsProductImportAsyncRunner importAsyncRunner;
@Autowired
private IPrcPriceExcelFileService priceExcelFileService;
@Override
public BmsProduct selectBmsProductById(Long productId)
{
@@ -313,6 +318,10 @@ public class BmsProductServiceImpl implements IBmsProductService
assertImportProductsNoDupCountryWeight(workbook, products, regionByNameCn, euRegions,
zoneByCountryAndCode);
PrcPriceExcelFile excelFile = priceExcelFileService.saveImportExcel(
PrcPriceExcelFile.TYPE_PRODUCT, forcedEff, bytes, filename, operName);
Long pefId = excelFile != null ? excelFile.getPefId() : null;
int successNum = 0;
int skipNum = 0;
int countryNum = 0;
@@ -331,6 +340,7 @@ public class BmsProductServiceImpl implements IBmsProductService
try
{
product.setEffectiveDate(forcedEff);
product.setPefId(pefId);
int[] nums = importOneProduct(workbook, product, operName, regionByNameCn, euRegions,
zoneByCountryAndCode, unmatchedCountries, forcedEff);
transactionManager.commit(tx);
@@ -431,6 +441,10 @@ public class BmsProductServiceImpl implements IBmsProductService
// Pre-check all products: reject whole import before any write if any is earlier than prev version
assertImportProductsEffectiveNotBeforePrev(products, forcedEff);
PrcPriceExcelFile excelFile = priceExcelFileService.saveImportExcel(
PrcPriceExcelFile.TYPE_PRODUCT, forcedEff, bytes, filename, operName);
Long pefId = excelFile != null ? excelFile.getPefId() : null;
int successNum = 0;
int skipNum = 0;
int countryNum = 0;
@@ -458,6 +472,7 @@ public class BmsProductServiceImpl implements IBmsProductService
product.setDestRegion(countryLabel);
product.setSourceSheet(block.sheetName);
product.setEffectiveDate(forcedEff);
product.setPefId(pefId);
if (StringUtils.isEmpty(product.getTaxMode()))
{
product.setTaxMode(resolveTaxMode(product.getProductName()));
@@ -649,6 +664,7 @@ public class BmsProductServiceImpl implements IBmsProductService
if (existing != null)
{
product.setProductId(existing.getProductId());
patchProductPefId(product, operName);
int countryRows = saveCountries(product, countries);
return new int[] { 0, 1, countryRows };
}
@@ -672,6 +688,19 @@ public class BmsProductServiceImpl implements IBmsProductService
return new int[] { 1, 0, countryRows };
}
private void patchProductPefId(BmsProduct product, String operName)
{
if (product == null || product.getProductId() == null || product.getPefId() == null)
{
return;
}
BmsProduct pefPatch = new BmsProduct();
pefPatch.setProductId(product.getProductId());
pefPatch.setPefId(product.getPefId());
pefPatch.setUpdateBy(operName);
productMapper.updateBmsProduct(pefPatch);
}
/**
* Save country rows by effective_date.
@@ -2159,6 +2188,7 @@ public class BmsProductServiceImpl implements IBmsProductService
if (existing != null)
{
product.setProductId(existing.getProductId());
patchProductPefId(product, operName);
int countryRows = saveCountries(product, countries);
return new int[] { 0, 1, countryRows };
}
@@ -0,0 +1,115 @@
package com.bms.price.service.impl;
import java.io.File;
import java.io.FileOutputStream;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.bms.common.config.BmsConfig;
import com.bms.common.exception.ServiceException;
import com.bms.common.utils.DateUtils;
import com.bms.common.utils.StringUtils;
import com.bms.common.utils.file.FileUploadUtils;
import com.bms.common.utils.uuid.IdUtils;
import com.bms.price.domain.PrcPriceExcelFile;
import com.bms.price.mapper.PrcPriceExcelFileMapper;
import com.bms.price.service.IPrcPriceExcelFileService;
/**
* Persist import Excel bytes and metadata
*/
@Service
public class PrcPriceExcelFileServiceImpl implements IPrcPriceExcelFileService
{
private static final String SUB_DIR = "price/excel";
@Autowired
private PrcPriceExcelFileMapper excelFileMapper;
@Override
public PrcPriceExcelFile selectPrcPriceExcelFileById(Long pefId)
{
return excelFileMapper.selectPrcPriceExcelFileById(pefId);
}
@Override
public List<PrcPriceExcelFile> selectByProductCode(String productCode)
{
if (StringUtils.isEmpty(productCode))
{
return Collections.emptyList();
}
List<PrcPriceExcelFile> list = excelFileMapper.selectByProductCode(productCode.trim());
return list == null ? Collections.emptyList() : list;
}
@Override
public PrcPriceExcelFile saveImportExcel(String excelType, Date effectiveTime, byte[] bytes,
String filename, String operName) throws Exception
{
if (StringUtils.isEmpty(excelType))
{
throw new ServiceException("excelType required");
}
if (bytes == null || bytes.length == 0)
{
throw new ServiceException("excel empty");
}
if (effectiveTime == null)
{
effectiveTime = new Date();
}
String originalName = StringUtils.isNotEmpty(filename) ? filename : "import.xlsx";
String ext = FilenameUtils.getExtension(originalName);
if (StringUtils.isEmpty(ext))
{
ext = "xlsx";
}
ext = ext.toLowerCase(Locale.ROOT);
String uploadDir = BmsConfig.getUploadPath() + File.separator + SUB_DIR;
String pathName = DateUtils.datePath() + "/" + IdUtils.fastSimpleUUID() + "." + ext;
File dest = FileUploadUtils.getAbsoluteFile(uploadDir, pathName);
FileOutputStream fos = null;
try
{
fos = new FileOutputStream(dest);
fos.write(bytes);
}
finally
{
IOUtils.close(fos);
}
String storedPath = FileUploadUtils.getPathFileName(uploadDir, pathName);
PrcPriceExcelFile row = new PrcPriceExcelFile();
row.setExcelType(excelType.trim().toUpperCase(Locale.ROOT));
row.setEffectiveTime(effectiveTime);
row.setFileName(originalName);
row.setFilePath(storedPath);
row.setFileSize(Long.valueOf(bytes.length));
row.setContentType(resolveContentType(ext));
row.setFileExtension(ext);
row.setCreateBy(operName);
excelFileMapper.insertPrcPriceExcelFile(row);
return row;
}
private static String resolveContentType(String ext)
{
if ("xls".equals(ext))
{
return "application/vnd.ms-excel";
}
if ("xlsx".equals(ext))
{
return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
}
return "";
}
}
@@ -22,10 +22,12 @@ import com.bms.common.exception.ServiceException;
import com.bms.common.utils.DateUtils;
import com.bms.common.utils.MessageUtils;
import com.bms.common.utils.StringUtils;
import com.bms.common.utils.SecurityUtils;
import com.bms.common.utils.uuid.IdUtils;
import com.bms.price.domain.BmsProduct;
import com.bms.price.domain.BmsProductCountry;
import com.bms.price.domain.PrcCustomerPriceSet;
import com.bms.price.domain.PrcPriceExcelFile;
import com.bms.price.domain.PrcPriceSet;
import com.bms.price.domain.PrcPriceSetCheckItem;
import com.bms.price.domain.PrcPriceSetDetail;
@@ -46,6 +48,7 @@ import com.bms.price.mapper.PrcPriceSetVersionSurchargeMapper;
import com.bms.price.mapper.PrcSurchargeMapper;
import com.bms.price.mapper.PrcSurchargeRelMapper;
import com.bms.price.service.IBmsProductService;
import com.bms.price.service.IPrcPriceExcelFileService;
import com.bms.price.service.IPrcPriceSetService;
import com.bms.price.task.PrcPriceSetSaveAsyncRunner;
@@ -117,6 +120,9 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
@Autowired
private PrcPriceSetSaveAsyncRunner saveAsyncRunner;
@Autowired
private IPrcPriceExcelFileService priceExcelFileService;
@Override
public PrcPriceSet selectPrcPriceSetById(Long priceSetId)
{
@@ -1071,6 +1077,18 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
{
assertUpdateOnlyWeightBandsMatch(byProduct, detailIndex);
}
String operName = null;
try
{
operName = SecurityUtils.getUsername();
}
catch (Exception ignored)
{
operName = null;
}
PrcPriceExcelFile excelFile = priceExcelFileService.saveImportExcel(
PrcPriceExcelFile.TYPE_SALES, new Date(), bytes, filename, operName);
Long pefId = excelFile != null ? excelFile.getPefId() : null;
Map<String, PrcPriceSetProduct> productMap = new LinkedHashMap<String, PrcPriceSetProduct>();
int updated = 0;
@@ -1172,9 +1190,20 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
throw new ServiceException(MessageUtils.message("price.set.import.noDetailWritten"));
}
if (pefId != null)
{
for (PrcPriceSetProduct sp : productMap.values())
{
if (sp != null)
{
sp.setPefId(pefId);
}
}
}
Map<String, Object> result = new HashMap<String, Object>();
result.put("products", new ArrayList<PrcPriceSetProduct>(productMap.values()));
result.put("details", details);
result.put("pefId", pefId);
if (updateOnly)
{
result.put("message", MessageUtils.message("price.set.import.updateSuccess",
@@ -1291,6 +1320,12 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
{
assertUpdateOnlyWeightBandsMatch(byProduct, detailIndex);
}
PrcPriceSetVersion verForFile = versionMapper.selectPrcPriceSetVersionById(psvId);
Date excelEff = verForFile != null && verForFile.getEffectiveTime() != null
? verForFile.getEffectiveTime() : new Date();
PrcPriceExcelFile excelFile = priceExcelFileService.saveImportExcel(
PrcPriceExcelFile.TYPE_SALES, excelEff, bytes, filename, operName);
Long pefId = excelFile != null ? excelFile.getPefId() : null;
int updated = 0;
int inserted = 0;
@@ -1321,7 +1356,7 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
Long pspId = null;
if (!updateOnly)
{
pspId = ensureSetProduct(priceSetId, psvId, productCode, productName);
pspId = ensureSetProduct(priceSetId, psvId, productCode, productName, pefId);
}
for (BmsProductCountry row : e.getValue())
{
@@ -1385,6 +1420,11 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
{
setProductMapper.bindDetailPspByPsvId(psvId);
}
if (pefId != null && (inserted > 0 || updated > 0))
{
List<String> codes = new ArrayList<String>(byProduct.keySet());
setProductMapper.updatePefIdByPsvId(psvId, pefId, codes);
}
reportImportProgress(taskId, priceSetId, 95, "finishing");
if (inserted == 0 && updated == 0)
{
@@ -2935,7 +2975,7 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
d.setPriceSetId(set.getPriceSetId());
d.setPsvId(version.getPsvId());
Long pspId = ensureSetProduct(set.getPriceSetId(), version.getPsvId(),
d.getProductCode(), d.getProductName());
d.getProductCode(), d.getProductName(), set.getPefId());
if (pspId != null)
{
d.setPspId(pspId);
@@ -3059,6 +3099,12 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
* Uses request-scoped cache during bulk upsert to avoid N selects on same code.
*/
private Long ensureSetProduct(Long priceSetId, Long psvId, String productCode, String productNameHint)
{
return ensureSetProduct(priceSetId, psvId, productCode, productNameHint, null);
}
private Long ensureSetProduct(Long priceSetId, Long psvId, String productCode, String productNameHint,
Long pefId)
{
if (psvId == null || StringUtils.isEmpty(productCode))
{
@@ -3069,15 +3115,32 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
Map<String, Long> cache = setProductCacheTl.get();
if (cache != null && cache.containsKey(key))
{
if (pefId != null)
{
PrcPriceSetProduct pefPatch = new PrcPriceSetProduct();
pefPatch.setPspId(cache.get(key));
pefPatch.setPefId(pefId);
setProductMapper.updatePrcPriceSetProduct(pefPatch);
}
return cache.get(key);
}
PrcPriceSetProduct existing = setProductMapper.selectByPsvIdAndCode(psvId, code);
if (existing != null)
{
boolean needUpdate = false;
if (StringUtils.isEmpty(existing.getProductName()) && StringUtils.isNotEmpty(productNameHint)
&& !code.equals(productNameHint))
{
existing.setProductName(productNameHint);
needUpdate = true;
}
if (pefId != null && (existing.getPefId() == null || !pefId.equals(existing.getPefId())))
{
existing.setPefId(pefId);
needUpdate = true;
}
if (needUpdate)
{
setProductMapper.updatePrcPriceSetProduct(existing);
}
if (cache != null)
@@ -3090,6 +3153,7 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
row.setPriceSetId(priceSetId);
row.setPsvId(psvId);
row.setProductCode(code);
row.setPefId(pefId);
BmsProduct lib = null;
try
{