Browse Source

上传供应商价格

master
常熟吴彦祖 3 weeks ago
parent
commit
eb76c2e10a
  1. 7
      src/main/java/com/xujie/modules/supplier/service/impl/SupplierPartMoqPriceServiceImpl.java
  2. 73
      src/main/java/com/xujie/modules/sys/controller/SysInitialImportController.java
  3. 38
      src/main/java/com/xujie/modules/sys/dao/SysInitialImportMapper.java
  4. 17
      src/main/java/com/xujie/modules/sys/service/SysInitialImportService.java
  5. 630
      src/main/java/com/xujie/modules/sys/service/impl/SysInitialImportServiceImpl.java
  6. 19
      src/main/java/com/xujie/modules/sys/vo/SupplierPriceInitialImportPreviewResultVo.java
  7. 35
      src/main/java/com/xujie/modules/sys/vo/SupplierPriceInitialImportRowVo.java
  8. 16
      src/main/java/com/xujie/modules/sys/vo/SupplierPriceInitialImportSaveVo.java
  9. 12
      src/main/resources/mapper/supplier/SupplierPartMoqPriceMapper.xml
  10. 85
      src/main/resources/mapper/sys/SysInitialImportMapper.xml
  11. 20
      src/main/resources/mapper/sys/SysMenuDao.xml

7
src/main/java/com/xujie/modules/supplier/service/impl/SupplierPartMoqPriceServiceImpl.java

@ -30,6 +30,7 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@ -479,9 +480,9 @@ public class SupplierPartMoqPriceServiceImpl implements ISupplierPartMoqPriceSer
private SupplierPartTierPriceItemVo normalizeTierRow(SupplierPartTierPriceItemVo row) {
SupplierPartTierPriceItemVo normalizedRow = new SupplierPartTierPriceItemVo();
normalizedRow.setQtyStart(row.getQtyStart());
normalizedRow.setQtyEnd(row.getQtyEnd());
normalizedRow.setUnitPrice(row.getUnitPrice());
normalizedRow.setQtyStart(row.getQtyStart().setScale(4, RoundingMode.HALF_UP));
normalizedRow.setQtyEnd(row.getQtyEnd().setScale(4, RoundingMode.HALF_UP));
normalizedRow.setUnitPrice(row.getUnitPrice().setScale(6, RoundingMode.HALF_UP));
normalizedRow.setRemark(StringUtils.hasText(row.getRemark()) ? row.getRemark().trim() : "");
return normalizedRow;
}

73
src/main/java/com/xujie/modules/sys/controller/SysInitialImportController.java

@ -0,0 +1,73 @@
package com.xujie.modules.sys.controller;
import com.xujie.common.utils.R;
import com.xujie.modules.sys.service.SysInitialImportService;
import com.xujie.modules.sys.vo.SupplierPriceInitialImportPreviewResultVo;
import com.xujie.modules.sys.vo.SupplierPriceInitialImportSaveVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
/**
* 系统管理 - 期初导入。
*/
@RestController
@RequestMapping("/sys/initialImport")
public class SysInitialImportController {
@Autowired
private SysInitialImportService initialImportService;
@GetMapping("/supplierPrice/template")
public ResponseEntity<Resource> downloadSupplierPriceTemplate() {
byte[] bytes = initialImportService.buildSupplierPriceTemplate();
String fileName = URLEncoder.encode("供应商价格期初导入模板.xlsx", StandardCharsets.UTF_8)
.replace("+", "%20");
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + fileName)
.header(HttpHeaders.CONTENT_LENGTH, String.valueOf(bytes.length))
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(new ByteArrayResource(bytes));
}
@PostMapping("/supplierPrice/preview")
public R previewSupplierPrice(@RequestParam("file") MultipartFile file,
@RequestParam("site") String site) {
try {
SupplierPriceInitialImportPreviewResultVo result = initialImportService.previewSupplierPrice(file, site);
return R.ok()
.put("data", result.getRows())
.put("total", result.getRows().size())
.put("missingItems", result.getMissingItems())
.put("missingSuppliers", result.getMissingSuppliers());
} catch (RuntimeException ex) {
return R.error(ex.getMessage());
}
}
@PostMapping("/supplierPrice/save")
public R saveSupplierPrice(@RequestBody SupplierPriceInitialImportSaveVo data) {
try {
int savedCount = initialImportService.saveSupplierPrice(data);
int totalCount = data == null || data.getRows() == null ? 0 : data.getRows().size();
return R.ok()
.put("savedCount", savedCount)
.put("skippedCount", totalCount - savedCount);
} catch (RuntimeException ex) {
return R.error(ex.getMessage());
}
}
}

38
src/main/java/com/xujie/modules/sys/dao/SysInitialImportMapper.java

@ -0,0 +1,38 @@
package com.xujie.modules.sys.dao;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.math.BigDecimal;
import java.util.List;
/**
* 系统期初导入专用数据访问。
*/
@Mapper
public interface SysInitialImportMapper {
List<String> listExistingItems(@Param("site") String site, @Param("items") List<String> items);
List<String> listExistingSupplierShortNames(@Param("site") String site,
@Param("shortNames") List<String> shortNames);
List<String> listPartNosByItem(@Param("site") String site, @Param("item") String item);
List<String> listSupplierNosByShortNpc(@Param("site") String site,
@Param("shortNpc") String shortNpc);
Integer countSupplierPart(@Param("site") String site,
@Param("supplierNo") String supplierNo,
@Param("partNo") String partNo);
int updateSupplierPartMoq(@Param("site") String site,
@Param("supplierNo") String supplierNo,
@Param("partNo") String partNo,
@Param("minOrderQty") BigDecimal minOrderQty);
int insertSupplierPart(@Param("site") String site,
@Param("supplierNo") String supplierNo,
@Param("partNo") String partNo,
@Param("minOrderQty") BigDecimal minOrderQty);
}

17
src/main/java/com/xujie/modules/sys/service/SysInitialImportService.java

@ -0,0 +1,17 @@
package com.xujie.modules.sys.service;
import com.xujie.modules.sys.vo.SupplierPriceInitialImportPreviewResultVo;
import com.xujie.modules.sys.vo.SupplierPriceInitialImportSaveVo;
import org.springframework.web.multipart.MultipartFile;
/**
* 系统期初导入服务。
*/
public interface SysInitialImportService {
byte[] buildSupplierPriceTemplate();
SupplierPriceInitialImportPreviewResultVo previewSupplierPrice(MultipartFile file, String site);
int saveSupplierPrice(SupplierPriceInitialImportSaveVo data);
}

630
src/main/java/com/xujie/modules/sys/service/impl/SysInitialImportServiceImpl.java

@ -0,0 +1,630 @@
package com.xujie.modules.sys.service.impl;
import com.xujie.modules.supplier.data.SupplierPartTierPriceHistoryMainVo;
import com.xujie.modules.supplier.data.SupplierPartTierPriceItemVo;
import com.xujie.modules.supplier.data.SupplierPartTierPriceQueryVo;
import com.xujie.modules.supplier.mapper.SupplierPartMoqPriceMapper;
import com.xujie.modules.sys.dao.SysInitialImportMapper;
import com.xujie.modules.sys.entity.SysUserEntity;
import com.xujie.modules.sys.service.SysInitialImportService;
import com.xujie.modules.sys.vo.SupplierPriceInitialImportPreviewResultVo;
import com.xujie.modules.sys.vo.SupplierPriceInitialImportRowVo;
import com.xujie.modules.sys.vo.SupplierPriceInitialImportSaveVo;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.DataFormatter;
import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.FormulaEvaluator;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.shiro.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.ByteArrayOutputStream;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.UUID;
@Service
public class SysInitialImportServiceImpl implements SysInitialImportService {
private static final int MAX_IMPORT_ROWS = 1000;
private static final String SHEET_NAME = "price list";
private static final List<String> HEADERS = Arrays.asList(
"RAW CODE", "SUPPLIER", "MOQ/COLOR", "START DATE",
"Tier_QTY_1", "Tier_QTY_2", "Tier_QTY_3", "Tier_QTY_4",
"Tier_QTY_5", "Tier_QTY_6", "Tier_QTY_7", "Tier_QTY_8",
"Price_Tier_1", "Price_Tier_2", "Price_Tier_3", "Price_Tier_4",
"Price_Tier_5", "Price_Tier_6", "Price_Tier_7", "Price_Tier_8"
);
private static final List<DateTimeFormatter> DATE_FORMATTERS = Arrays.asList(
DateTimeFormatter.ofPattern("yyyy-M-d", Locale.ENGLISH),
DateTimeFormatter.ofPattern("yyyy/M/d", Locale.ENGLISH),
DateTimeFormatter.ofPattern("M/d/yyyy", Locale.ENGLISH),
DateTimeFormatter.ofPattern("d/M/yyyy", Locale.ENGLISH),
DateTimeFormatter.ofPattern("d-MMM-yy", Locale.ENGLISH),
DateTimeFormatter.ofPattern("d-MMM-yyyy", Locale.ENGLISH),
DateTimeFormatter.ofPattern("MMM d, yyyy", Locale.ENGLISH),
DateTimeFormatter.ofPattern("yyyy年M月d日", Locale.CHINA)
);
private static final List<DateTimeFormatter> DATE_TIME_FORMATTERS = Arrays.asList(
DateTimeFormatter.ofPattern("yyyy-M-d H:m:s", Locale.ENGLISH),
DateTimeFormatter.ofPattern("yyyy/M/d H:m:s", Locale.ENGLISH),
DateTimeFormatter.ofPattern("yyyy-M-d H:m", Locale.ENGLISH),
DateTimeFormatter.ofPattern("yyyy/M/d H:m", Locale.ENGLISH),
DateTimeFormatter.ofPattern("M/d/yyyy H:m:s", Locale.ENGLISH),
DateTimeFormatter.ofPattern("M/d/yyyy H:m", Locale.ENGLISH)
);
@Autowired
private SysInitialImportMapper initialImportMapper;
@Autowired
private SupplierPartMoqPriceMapper supplierPartMoqPriceMapper;
@Override
public byte[] buildSupplierPriceTemplate() {
try (Workbook workbook = new XSSFWorkbook(); ByteArrayOutputStream output = new ByteArrayOutputStream()) {
Sheet sheet = workbook.createSheet(SHEET_NAME);
Row headerRow = sheet.createRow(0);
CellStyle headerStyle = createHeaderStyle(workbook);
for (int i = 0; i < HEADERS.size(); i++) {
Cell cell = headerRow.createCell(i);
cell.setCellValue(HEADERS.get(i));
cell.setCellStyle(headerStyle);
sheet.setColumnWidth(i, resolveColumnWidth(i) * 256);
}
sheet.createFreezePane(0, 1);
workbook.write(output);
return output.toByteArray();
} catch (Exception ex) {
throw new RuntimeException("生成供应商价格导入模板失败", ex);
}
}
@Override
public SupplierPriceInitialImportPreviewResultVo previewSupplierPrice(MultipartFile file, String site) {
validateFile(file);
if (!StringUtils.hasText(site)) {
throw new RuntimeException("登录Site不能为空");
}
try (Workbook workbook = WorkbookFactory.create(file.getInputStream())) {
if (workbook.getNumberOfSheets() == 0) {
throw new RuntimeException("Excel中没有工作表");
}
Sheet sheet = workbook.getSheetAt(0);
DataFormatter formatter = new DataFormatter(Locale.ENGLISH);
FormulaEvaluator evaluator = workbook.getCreationHelper().createFormulaEvaluator();
validateHeader(sheet.getRow(0), formatter, evaluator);
List<SupplierPriceInitialImportRowVo> result = new ArrayList<SupplierPriceInitialImportRowVo>();
for (int rowIndex = 1; rowIndex <= sheet.getLastRowNum(); rowIndex++) {
Row row = sheet.getRow(rowIndex);
if (isEmptyRow(row, formatter, evaluator)) {
continue;
}
if (result.size() >= MAX_IMPORT_ROWS) {
throw new RuntimeException("单次导入上限是1000行,请分批导入");
}
result.add(parseRow(row, rowIndex + 1, formatter, evaluator));
}
if (result.isEmpty()) {
throw new RuntimeException("Excel中没有可预览的数据");
}
return validatePreviewMappings(site.trim(), result);
} catch (RuntimeException ex) {
throw ex;
} catch (Exception ex) {
throw new RuntimeException("读取Excel失败,请检查文件格式", ex);
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public int saveSupplierPrice(SupplierPriceInitialImportSaveVo data) {
if (data == null || !StringUtils.hasText(data.getSite())) {
throw new RuntimeException("登录Site不能为空");
}
if (data.getRows() == null || data.getRows().isEmpty()) {
throw new RuntimeException("没有可保存的导入数据");
}
if (data.getRows().size() > MAX_IMPORT_ROWS) {
throw new RuntimeException("单次导入上限是1000行,请分批导入");
}
String site = data.getSite().trim();
String submitBy = resolveSubmitBy();
int savedCount = 0;
for (int index = 0; index < data.getRows().size(); index++) {
try {
if (saveSupplierPriceRow(site, submitBy, data.getRows().get(index))) {
savedCount++;
}
} catch (RuntimeException ex) {
throw new RuntimeException("Excel第" + (index + 2) + "行:" + ex.getMessage(), ex);
}
}
return savedCount;
}
private boolean saveSupplierPriceRow(String site, String submitBy, SupplierPriceInitialImportRowVo row) {
if (row == null) {
throw new RuntimeException("数据不能为空");
}
String item = trimToEmpty(row.getRawCode());
String shortNpc = trimToEmpty(row.getSupplier());
if (!StringUtils.hasText(item) || !StringUtils.hasText(shortNpc)) {
return false;
}
List<String> partNos = initialImportMapper.listPartNosByItem(site, item);
List<String> supplierNos = initialImportMapper.listSupplierNosByShortNpc(site, shortNpc);
if (partNos == null || partNos.isEmpty() || supplierNos == null || supplierNos.isEmpty()) {
return false;
}
if (row.getMoqColor() != null && row.getMoqColor().compareTo(BigDecimal.ZERO) < 0) {
throw new RuntimeException("MOQ/COLOR不能小于0");
}
String partNo = resolveUniqueCode(partNos,
"Item未找到对应物料:" + item,
"Item对应多个物料,请先处理重复数据:" + item);
String supplierNo = resolveUniqueCode(supplierNos,
"Short_NPC未找到对应供应商:" + shortNpc,
"Short_NPC对应多个供应商,请先处理重复数据:" + shortNpc);
Integer relationCount = initialImportMapper.countSupplierPart(site, supplierNo, partNo);
if (relationCount != null && relationCount > 0) {
initialImportMapper.updateSupplierPartMoq(site, supplierNo, partNo, row.getMoqColor());
} else {
initialImportMapper.insertSupplierPart(site, supplierNo, partNo, row.getMoqColor());
}
List<SupplierPartTierPriceItemVo> newTierPrices = buildTierPriceList(row);
if (newTierPrices.isEmpty()) {
return true;
}
LocalDate newPriceDate = parseImportDate(row.getStartDate());
SupplierPartTierPriceQueryVo query = new SupplierPartTierPriceQueryVo();
query.setSite(site);
query.setSupplierCode(supplierNo);
query.setPartNo(partNo);
List<SupplierPartTierPriceItemVo> currentTierPrices = supplierPartMoqPriceMapper.listCurrentTierPrice(query);
if (currentTierPrices != null && !currentTierPrices.isEmpty()) {
LocalDate currentPriceDate = resolveCurrentPriceDate(currentTierPrices);
if (!currentPriceDate.equals(newPriceDate)) {
archiveCurrentTierPrice(site, supplierNo, partNo, submitBy, currentPriceDate, currentTierPrices);
}
supplierPartMoqPriceMapper.deleteCurrentTierPrice(site, supplierNo, partNo);
}
supplierPartMoqPriceMapper.insertCurrentTierPriceBatch(
site,
supplierNo,
partNo,
newPriceDate.toString(),
"",
submitBy,
newTierPrices
);
return true;
}
private SupplierPriceInitialImportPreviewResultVo validatePreviewMappings(
String site, List<SupplierPriceInitialImportRowVo> rows) {
Set<String> requestedItems = new LinkedHashSet<>();
Set<String> requestedSupplierShortNames = new LinkedHashSet<>();
for (SupplierPriceInitialImportRowVo row : rows) {
if (row != null && StringUtils.hasText(row.getRawCode())) {
requestedItems.add(normalizeLookupValue(row.getRawCode()));
}
if (row != null && StringUtils.hasText(row.getSupplier())) {
requestedSupplierShortNames.add(normalizeLookupValue(row.getSupplier()));
}
}
Set<String> existingItems = normalizeLookupValues(requestedItems.isEmpty()
? new ArrayList<>()
: initialImportMapper.listExistingItems(site, new ArrayList<>(requestedItems)));
Set<String> existingSupplierShortNames = normalizeLookupValues(requestedSupplierShortNames.isEmpty()
? new ArrayList<>()
: initialImportMapper.listExistingSupplierShortNames(
site, new ArrayList<>(requestedSupplierShortNames)));
Set<String> missingItems = new LinkedHashSet<>();
Set<String> missingSuppliers = new LinkedHashSet<>();
for (SupplierPriceInitialImportRowVo row : rows) {
String item = row == null ? "" : trimToEmpty(row.getRawCode());
String supplierShortName = row == null ? "" : trimToEmpty(row.getSupplier());
boolean itemExists = StringUtils.hasText(item)
&& existingItems.contains(normalizeLookupValue(item));
boolean supplierExists = StringUtils.hasText(supplierShortName)
&& existingSupplierShortNames.contains(normalizeLookupValue(supplierShortName));
if (!itemExists) {
missingItems.add(StringUtils.hasText(item) ? item : "(空)");
}
if (!supplierExists) {
missingSuppliers.add(StringUtils.hasText(supplierShortName) ? supplierShortName : "(空)");
}
if (row != null) {
row.setValid(itemExists && supplierExists);
row.setValidationMessage(buildValidationMessage(itemExists, supplierExists));
}
}
SupplierPriceInitialImportPreviewResultVo result = new SupplierPriceInitialImportPreviewResultVo();
result.setRows(rows);
result.setMissingItems(new ArrayList<>(missingItems));
result.setMissingSuppliers(new ArrayList<>(missingSuppliers));
return result;
}
private Set<String> normalizeLookupValues(List<String> values) {
Set<String> result = new LinkedHashSet<>();
if (values == null) {
return result;
}
for (String value : values) {
if (StringUtils.hasText(value)) {
result.add(normalizeLookupValue(value));
}
}
return result;
}
private String normalizeLookupValue(String value) {
return trimToEmpty(value).toUpperCase(Locale.ENGLISH);
}
private String buildValidationMessage(boolean itemExists, boolean supplierExists) {
if (!itemExists && !supplierExists) {
return "Item和供应商简称不存在";
}
if (!itemExists) {
return "Item不存在";
}
if (!supplierExists) {
return "供应商简称不存在";
}
return "";
}
List<SupplierPartTierPriceItemVo> buildTierPriceList(SupplierPriceInitialImportRowVo row) {
BigDecimal[] quantities = {
row.getTierQty1(), row.getTierQty2(), row.getTierQty3(), row.getTierQty4(),
row.getTierQty5(), row.getTierQty6(), row.getTierQty7(), row.getTierQty8()
};
BigDecimal[] prices = {
row.getPriceTier1(), row.getPriceTier2(), row.getPriceTier3(), row.getPriceTier4(),
row.getPriceTier5(), row.getPriceTier6(), row.getPriceTier7(), row.getPriceTier8()
};
List<SupplierPartTierPriceItemVo> result = new ArrayList<>();
BigDecimal quantityStart = BigDecimal.ZERO;
boolean tierEnded = false;
for (int index = 0; index < quantities.length; index++) {
BigDecimal quantityEnd = quantities[index];
BigDecimal unitPrice = prices[index];
if (quantityEnd == null && unitPrice == null) {
tierEnded = true;
continue;
}
if (tierEnded) {
throw new RuntimeException("阶梯数量和价格不能在空档后继续填写");
}
if (quantityEnd == null || unitPrice == null) {
throw new RuntimeException("第" + (index + 1) + "档的Tier_QTY和Price_Tier必须同时填写");
}
if (quantityEnd.compareTo(quantityStart) < 0) {
throw new RuntimeException("第" + (index + 1) + "档数量上限必须大于上一档数量上限");
}
if (unitPrice.compareTo(BigDecimal.ZERO) < 0) {
throw new RuntimeException("第" + (index + 1) + "档价格不能小于0");
}
SupplierPartTierPriceItemVo tierPrice = new SupplierPartTierPriceItemVo();
// Keep every row at the target SQL Server scale. Mixed BigDecimal scales in a
// multi-row VALUES statement can otherwise be inferred as the lowest scale.
tierPrice.setQtyStart(quantityStart.setScale(4, RoundingMode.HALF_UP));
tierPrice.setQtyEnd(quantityEnd.setScale(4, RoundingMode.HALF_UP));
tierPrice.setUnitPrice(unitPrice.setScale(6, RoundingMode.HALF_UP));
tierPrice.setRemark("");
result.add(tierPrice);
quantityStart = quantityEnd.add(BigDecimal.ONE);
}
return result;
}
private void archiveCurrentTierPrice(String site,
String supplierNo,
String partNo,
String submitBy,
LocalDate priceDate,
List<SupplierPartTierPriceItemVo> currentTierPrices) {
Date submitDate = new Date();
String versionNo = generateInitialImportVersionNo();
String attachmentRefs = resolveAttachmentRefs(currentTierPrices);
SupplierPartTierPriceHistoryMainVo historyMain = new SupplierPartTierPriceHistoryMainVo();
historyMain.setSite(site);
historyMain.setSupplierCode(supplierNo);
historyMain.setPartNo(partNo);
historyMain.setVersionNo(versionNo);
historyMain.setSubmitDate(submitDate);
historyMain.setPriceDate(java.sql.Date.valueOf(priceDate));
historyMain.setSubmitBy(submitBy);
historyMain.setCurrency("USD");
historyMain.setAttachmentRefs(attachmentRefs);
supplierPartMoqPriceMapper.insertHistoryTierPriceMain(historyMain);
if (historyMain.getId() == null) {
throw new RuntimeException("当前价格归档失败");
}
supplierPartMoqPriceMapper.insertHistoryTierPriceBatch(
site,
historyMain.getId(),
supplierNo,
partNo,
versionNo,
submitDate,
priceDate.toString(),
attachmentRefs,
submitBy,
currentTierPrices
);
}
private LocalDate resolveCurrentPriceDate(List<SupplierPartTierPriceItemVo> rows) {
LocalDate priceDate = null;
for (SupplierPartTierPriceItemVo row : rows) {
if (row == null || row.getPriceDate() == null) {
throw new RuntimeException("当前价格记录的价格日期为空");
}
LocalDate rowPriceDate = toLocalDate(row.getPriceDate());
if (priceDate == null) {
priceDate = rowPriceDate;
} else if (!priceDate.equals(rowPriceDate)) {
throw new RuntimeException("当前价格记录存在多个价格日期,请先处理异常数据");
}
}
return priceDate;
}
private LocalDate toLocalDate(Date value) {
if (value instanceof java.sql.Date) {
return ((java.sql.Date) value).toLocalDate();
}
return value.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
}
private LocalDate parseImportDate(String value) {
if (!StringUtils.hasText(value)) {
throw new RuntimeException("START DATE不能为空");
}
try {
return LocalDate.parse(value.trim(), DateTimeFormatter.ISO_LOCAL_DATE);
} catch (DateTimeParseException ex) {
throw new RuntimeException("START DATE格式错误,应为yyyy-MM-dd");
}
}
private String resolveUniqueCode(List<String> values, String emptyMessage, String duplicateMessage) {
if (values == null || values.isEmpty()) {
throw new RuntimeException(emptyMessage);
}
if (values.size() > 1) {
throw new RuntimeException(duplicateMessage + "(" + String.join("、", values) + ")");
}
return values.get(0);
}
private String trimToEmpty(String value) {
return StringUtils.hasText(value) ? value.trim() : "";
}
private String resolveSubmitBy() {
Object principal = SecurityUtils.getSubject().getPrincipal();
if (principal instanceof SysUserEntity && StringUtils.hasText(((SysUserEntity) principal).getUsername())) {
return ((SysUserEntity) principal).getUsername().trim();
}
return "system";
}
private String resolveAttachmentRefs(List<SupplierPartTierPriceItemVo> rows) {
for (SupplierPartTierPriceItemVo row : rows) {
if (row != null && StringUtils.hasText(row.getAttachmentRefs())) {
return row.getAttachmentRefs().trim();
}
}
return "";
}
private String generateInitialImportVersionNo() {
String time = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS").format(LocalDateTime.now());
String random = UUID.randomUUID().toString().substring(0, 8).toUpperCase(Locale.ENGLISH);
return "INIT-" + time + "-" + random;
}
private SupplierPriceInitialImportRowVo parseRow(Row row, int excelRowNo,
DataFormatter formatter, FormulaEvaluator evaluator) {
SupplierPriceInitialImportRowVo value = new SupplierPriceInitialImportRowVo();
value.setRawCode(readText(row.getCell(0), formatter, evaluator));
value.setSupplier(readText(row.getCell(1), formatter, evaluator));
value.setMoqColor(readDecimal(row.getCell(2), excelRowNo, HEADERS.get(2), formatter, evaluator));
value.setStartDate(readDate(row.getCell(3), excelRowNo, formatter, evaluator));
value.setTierQty1(readDecimal(row.getCell(4), excelRowNo, HEADERS.get(4), formatter, evaluator));
value.setTierQty2(readDecimal(row.getCell(5), excelRowNo, HEADERS.get(5), formatter, evaluator));
value.setTierQty3(readDecimal(row.getCell(6), excelRowNo, HEADERS.get(6), formatter, evaluator));
value.setTierQty4(readDecimal(row.getCell(7), excelRowNo, HEADERS.get(7), formatter, evaluator));
value.setTierQty5(readDecimal(row.getCell(8), excelRowNo, HEADERS.get(8), formatter, evaluator));
value.setTierQty6(readDecimal(row.getCell(9), excelRowNo, HEADERS.get(9), formatter, evaluator));
value.setTierQty7(readDecimal(row.getCell(10), excelRowNo, HEADERS.get(10), formatter, evaluator));
value.setTierQty8(readDecimal(row.getCell(11), excelRowNo, HEADERS.get(11), formatter, evaluator));
value.setPriceTier1(readDecimal(row.getCell(12), excelRowNo, HEADERS.get(12), formatter, evaluator));
value.setPriceTier2(readDecimal(row.getCell(13), excelRowNo, HEADERS.get(13), formatter, evaluator));
value.setPriceTier3(readDecimal(row.getCell(14), excelRowNo, HEADERS.get(14), formatter, evaluator));
value.setPriceTier4(readDecimal(row.getCell(15), excelRowNo, HEADERS.get(15), formatter, evaluator));
value.setPriceTier5(readDecimal(row.getCell(16), excelRowNo, HEADERS.get(16), formatter, evaluator));
value.setPriceTier6(readDecimal(row.getCell(17), excelRowNo, HEADERS.get(17), formatter, evaluator));
value.setPriceTier7(readDecimal(row.getCell(18), excelRowNo, HEADERS.get(18), formatter, evaluator));
value.setPriceTier8(readDecimal(row.getCell(19), excelRowNo, HEADERS.get(19), formatter, evaluator));
return value;
}
private void validateHeader(Row headerRow, DataFormatter formatter, FormulaEvaluator evaluator) {
if (headerRow == null) {
throw new RuntimeException("Excel第一行必须是模板表头");
}
for (int i = 0; i < HEADERS.size(); i++) {
String actual = normalizeHeader(readText(headerRow.getCell(i), formatter, evaluator));
String expected = normalizeHeader(HEADERS.get(i));
if (!expected.equals(actual)) {
throw new RuntimeException("Excel第" + (i + 1) + "列表头应为 " + HEADERS.get(i));
}
}
short lastCellNum = headerRow.getLastCellNum();
for (int i = HEADERS.size(); i < lastCellNum; i++) {
if (StringUtils.hasText(readText(headerRow.getCell(i), formatter, evaluator))) {
throw new RuntimeException("Excel表头必须与下载模板一致,不能增加额外栏位");
}
}
}
private String readText(Cell cell, DataFormatter formatter, FormulaEvaluator evaluator) {
if (cell == null) {
return null;
}
String text = formatter.formatCellValue(cell, evaluator);
return StringUtils.hasText(text) ? text.trim() : null;
}
private BigDecimal readDecimal(Cell cell, int rowNo, String columnName,
DataFormatter formatter, FormulaEvaluator evaluator) {
String text = readText(cell, formatter, evaluator);
if (!StringUtils.hasText(text)) {
return null;
}
try {
String normalized = text.replace('\u00A0', ' ').trim();
boolean negativeByParentheses = normalized.startsWith("(") && normalized.endsWith(")");
normalized = normalized.replaceAll("[\\p{Sc}\\p{L}\\s]", "")
.replace(",", "")
.replace("(", "")
.replace(")", "");
if (!normalized.matches("[+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][+-]?\\d+)?")) {
throw new NumberFormatException();
}
BigDecimal number = new BigDecimal(normalized);
return negativeByParentheses ? number.negate() : number;
} catch (NumberFormatException ex) {
throw new RuntimeException("第" + rowNo + "行 " + columnName + " 必须是数字");
}
}
private String readDate(Cell cell, int rowNo, DataFormatter formatter, FormulaEvaluator evaluator) {
String text = readText(cell, formatter, evaluator);
if (!StringUtils.hasText(text)) {
return null;
}
if ((cell.getCellType() == CellType.NUMERIC || cell.getCellType() == CellType.FORMULA)
&& DateUtil.isCellDateFormatted(cell)) {
return DateUtil.getLocalDateTime(cell.getNumericCellValue()).toLocalDate().toString();
}
for (DateTimeFormatter dateFormatter : DATE_FORMATTERS) {
try {
return LocalDate.parse(text, dateFormatter).toString();
} catch (DateTimeParseException ignored) {
// 继续尝试下一种格式。
}
}
for (DateTimeFormatter dateTimeFormatter : DATE_TIME_FORMATTERS) {
try {
return LocalDateTime.parse(text, dateTimeFormatter).toLocalDate().toString();
} catch (DateTimeParseException ignored) {
// 继续尝试下一种格式。
}
}
throw new RuntimeException("第" + rowNo + "行 START DATE 日期格式无法识别");
}
private boolean isEmptyRow(Row row, DataFormatter formatter, FormulaEvaluator evaluator) {
if (row == null) {
return true;
}
for (int i = 0; i < HEADERS.size(); i++) {
if (StringUtils.hasText(readText(row.getCell(i), formatter, evaluator))) {
return false;
}
}
return true;
}
private void validateFile(MultipartFile file) {
if (file == null || file.isEmpty()) {
throw new RuntimeException("请选择Excel文件");
}
String fileName = file.getOriginalFilename();
String lowerName = fileName == null ? "" : fileName.toLowerCase(Locale.ENGLISH);
if (!lowerName.endsWith(".xlsx") && !lowerName.endsWith(".xls")) {
throw new RuntimeException("仅支持.xls或.xlsx文件");
}
}
private CellStyle createHeaderStyle(Workbook workbook) {
CellStyle style = workbook.createCellStyle();
Font font = workbook.createFont();
font.setBold(true);
style.setFont(font);
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
style.setBorderTop(BorderStyle.THIN);
style.setBorderRight(BorderStyle.THIN);
style.setBorderBottom(BorderStyle.THIN);
style.setBorderLeft(BorderStyle.THIN);
return style;
}
private int resolveColumnWidth(int columnIndex) {
if (columnIndex == 0 || columnIndex == 1) {
return 20;
}
if (columnIndex == 3) {
return 15;
}
return 14;
}
private String normalizeHeader(String value) {
return value == null ? "" : value.replace("\uFEFF", "").trim();
}
}

19
src/main/java/com/xujie/modules/sys/vo/SupplierPriceInitialImportPreviewResultVo.java

@ -0,0 +1,19 @@
package com.xujie.modules.sys.vo;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* 供应商价格期初导入预览结果。
*/
@Data
public class SupplierPriceInitialImportPreviewResultVo {
private List<SupplierPriceInitialImportRowVo> rows = new ArrayList<>();
private List<String> missingItems = new ArrayList<>();
private List<String> missingSuppliers = new ArrayList<>();
}

35
src/main/java/com/xujie/modules/sys/vo/SupplierPriceInitialImportRowVo.java

@ -0,0 +1,35 @@
package com.xujie.modules.sys.vo;
import lombok.Data;
import java.math.BigDecimal;
/**
* 供应商价格期初导入预览行。
*/
@Data
public class SupplierPriceInitialImportRowVo {
private String rawCode;
private String supplier;
private BigDecimal moqColor;
private String startDate;
private BigDecimal tierQty1;
private BigDecimal tierQty2;
private BigDecimal tierQty3;
private BigDecimal tierQty4;
private BigDecimal tierQty5;
private BigDecimal tierQty6;
private BigDecimal tierQty7;
private BigDecimal tierQty8;
private BigDecimal priceTier1;
private BigDecimal priceTier2;
private BigDecimal priceTier3;
private BigDecimal priceTier4;
private BigDecimal priceTier5;
private BigDecimal priceTier6;
private BigDecimal priceTier7;
private BigDecimal priceTier8;
private Boolean valid;
private String validationMessage;
}

16
src/main/java/com/xujie/modules/sys/vo/SupplierPriceInitialImportSaveVo.java

@ -0,0 +1,16 @@
package com.xujie.modules.sys.vo;
import lombok.Data;
import java.util.List;
/**
* 供应商价格期初导入保存参数。
*/
@Data
public class SupplierPriceInitialImportSaveVo {
private String site;
private List<SupplierPriceInitialImportRowVo> rows;
}

12
src/main/resources/mapper/supplier/SupplierPartMoqPriceMapper.xml

@ -292,9 +292,9 @@
#{site},
#{supplierCode},
#{partNo},
#{item.qtyStart},
#{item.qtyEnd},
#{item.unitPrice},
CAST(#{item.qtyStart,jdbcType=DECIMAL} AS DECIMAL(18,4)),
CAST(#{item.qtyEnd,jdbcType=DECIMAL} AS DECIMAL(18,4)),
CAST(#{item.unitPrice,jdbcType=DECIMAL} AS DECIMAL(18,6)),
CAST(#{priceDate} AS DATETIME),
#{attachmentRefs},
#{item.remark},
@ -368,9 +368,9 @@
#{versionNo},
#{submitDate},
#{submitBy},
#{item.qtyStart},
#{item.qtyEnd},
#{item.unitPrice},
CAST(#{item.qtyStart,jdbcType=DECIMAL} AS DECIMAL(18,4)),
CAST(#{item.qtyEnd,jdbcType=DECIMAL} AS DECIMAL(18,4)),
CAST(#{item.unitPrice,jdbcType=DECIMAL} AS DECIMAL(18,6)),
CAST(#{priceDate} AS DATETIME),
#{attachmentRefs},
#{item.remark},

85
src/main/resources/mapper/sys/SysInitialImportMapper.xml

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xujie.modules.sys.dao.SysInitialImportMapper">
<select id="listExistingItems" resultType="java.lang.String">
SELECT DISTINCT LTRIM(RTRIM(item))
FROM part
WHERE site = #{site}
AND UPPER(LTRIM(RTRIM(item))) IN
<foreach collection="items" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</select>
<select id="listExistingSupplierShortNames" resultType="java.lang.String">
SELECT DISTINCT LTRIM(RTRIM(c_short_npc))
FROM srm_supplier
WHERE site = #{site}
AND UPPER(LTRIM(RTRIM(c_short_npc))) IN
<foreach collection="shortNames" item="shortName" open="(" separator="," close=")">
#{shortName}
</foreach>
</select>
<select id="listPartNosByItem" resultType="java.lang.String">
SELECT DISTINCT part_no
FROM part
WHERE site = #{site}
AND UPPER(LTRIM(RTRIM(item))) = UPPER(#{item})
ORDER BY part_no
</select>
<select id="listSupplierNosByShortNpc" resultType="java.lang.String">
SELECT DISTINCT supplier_no
FROM srm_supplier
WHERE site = #{site}
AND UPPER(LTRIM(RTRIM(c_short_npc))) = UPPER(#{shortNpc})
ORDER BY supplier_no
</select>
<select id="countSupplierPart" resultType="java.lang.Integer">
SELECT COUNT(1)
FROM SupplierforPart
WHERE site = #{site}
AND supplier_no = #{supplierNo}
AND part_no = #{partNo}
</select>
<update id="updateSupplierPartMoq">
UPDATE SupplierforPart
SET [minOrderQty] = #{minOrderQty}
WHERE site = #{site}
AND supplier_no = #{supplierNo}
AND part_no = #{partNo}
</update>
<insert id="insertSupplierPart">
INSERT INTO SupplierforPart
(
site,
supplier_no,
part_no,
active,
currency,
[minOrderQty],
new_product_audited,
need_new_product_audit,
need_submit_test
)
VALUES
(
#{site},
#{supplierNo},
#{partNo},
'Y',
'USD',
#{minOrderQty},
'Y',
'N',
'N'
)
</insert>
</mapper>

20
src/main/resources/mapper/sys/SysMenuDao.xml

@ -108,17 +108,15 @@
sm.url, sm.perms, sm.type, sm.icon, sm.order_num
FROM sys_menu sm
LEFT JOIN sys_object_language_menu sml ON sm.menu_id = sml.menu_id and sml.language_code = #{language}
LEFT JOIN sys_role_menu srm ON srm.menu_id = sm.menu_id
LEFT JOIN sys_user_role sur ON sur.role_id = srm.role_id
LEFT JOIN sys_user su ON su.user_id = sur.user_id
<where>
srm.menu_id = sm.menu_id AND sur.role_id = srm.role_id AND su.user_id = sur.user_id
AND sm.menu_type = #{menuType}
<if test="userId != null and userId != '' ">
AND su.user_id = #{userId}
</if>
</where>
WHERE sm.menu_type = #{menuType}
AND EXISTS (
SELECT 1
FROM sys_user_role sur
INNER JOIN sys_role_menu srm ON srm.role_id = sur.role_id
WHERE sur.user_id = #{userId}
AND srm.menu_id = sm.menu_id
)
ORDER BY sm.order_num ASC
</select>
</mapper>
</mapper>
Loading…
Cancel
Save