@@ -1,6 +1,8 @@
package cn.lili.modules.goods.serviceimpl ;
import cn.hutool.core.convert.Convert ;
import cn.hutool.core.text.CharSequenceUtil ;
import cn.hutool.core.util.NumberUtil ;
import cn.hutool.json.JSONObject ;
import cn.hutool.json.JSONUtil ;
import cn.lili.cache.Cache ;
@@ -23,6 +25,7 @@ import cn.lili.modules.goods.entity.dto.GoodsSkuStockDTO;
import cn.lili.modules.goods.entity.enums.GoodsAuthEnum ;
import cn.lili.modules.goods.entity.enums.GoodsSalesModeEnum ;
import cn.lili.modules.goods.entity.enums.GoodsStatusEnum ;
import cn.lili.modules.goods.entity.enums.GoodsStockTypeEnum ;
import cn.lili.modules.goods.entity.vos.GoodsSkuSpecVO ;
import cn.lili.modules.goods.entity.vos.GoodsSkuVO ;
import cn.lili.modules.goods.entity.vos.GoodsVO ;
@@ -54,12 +57,22 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage ;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page ;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl ;
import org.apache.poi.ss.usermodel.* ;
import org.apache.poi.ss.util.CellRangeAddress ;
import org.apache.poi.ss.util.CellRangeAddressList ;
import org.apache.poi.xssf.usermodel.XSSFWorkbook ;
import org.apache.rocketmq.spring.core.RocketMQTemplate ;
import org.springframework.beans.factory.annotation.Autowired ;
import org.springframework.context.ApplicationEventPublisher ;
import org.springframework.stereotype.Service ;
import org.springframework.transaction.annotation.Transactional ;
import org.springframework.web.multipart.MultipartFile ;
import javax.servlet.ServletOutputStream ;
import javax.servlet.http.HttpServletResponse ;
import java.io.IOException ;
import java.io.InputStream ;
import java.net.URLEncoder ;
import java.util.* ;
import java.util.stream.Collectors ;
@@ -493,6 +506,115 @@ public class GoodsSkuServiceImpl extends ServiceImpl<GoodsSkuMapper, GoodsSku> i
return this . page ( PageUtil . initPage ( searchParams ) , searchParams . queryWrapper ( ) ) ;
}
@Override
public void queryExportStock ( HttpServletResponse response , GoodsSearchParams searchParams ) {
List < GoodsSkuStockDTO > goodsSkuStockDTOList = this . baseMapper . queryStocks ( searchParams . queryWrapper ( ) ) ;
XSSFWorkbook workbook = initStockExportData ( goodsSkuStockDTOList ) ;
try {
// 设置响应头
String fileName = URLEncoder . encode ( " 商品库存 " , " UTF-8 " ) ;
response . setContentType ( " application/vnd.ms-excel;charset=UTF-8 " ) ;
response . setHeader ( " Content-Disposition " , " attachment;filename= " + fileName + " .xlsx " ) ;
ServletOutputStream out = response . getOutputStream ( ) ;
workbook . write ( out ) ;
} catch ( Exception e ) {
e . printStackTrace ( ) ;
} finally {
try {
workbook . close ( ) ;
} catch ( Exception e ) {
e . printStackTrace ( ) ;
}
}
}
@Override
public void importStock ( String storeId , MultipartFile file ) {
List < GoodsSkuStockDTO > goodsSkuStockDTOList = new ArrayList < > ( ) ;
try ( InputStream inputStream = file . getInputStream ( ) ) {
// 使用 WorkbookFactory.create 方法读取 Excel 文件
Workbook workbook = WorkbookFactory . create ( inputStream ) ;
Sheet sheet = workbook . getSheetAt ( 0 ) ; // 我们只读取第一个sheet
// 检查第一个sheet的行数是否超过10002行
if ( sheet . getPhysicalNumberOfRows ( ) > 10002 ) {
throw new ServiceException ( ResultCode . GOODS_STOCK_IMPORT_ERROR , " Excel行数超过10002行 " ) ;
}
// 遍历行和单元格
Iterator < Row > rowIterator = sheet . rowIterator ( ) ;
int rowIndex = 0 ;
while ( rowIterator . hasNext ( ) ) {
Row row = rowIterator . next ( ) ;
rowIndex + + ;
// 跳过表头
if ( rowIndex < 3 ) {
continue ;
}
List < Object > objects = new ArrayList < > ( ) ;
for ( int i = 0 ; i < 4 ; i + + ) {
objects . add ( getCellValue ( row . getCell ( i ) ) ) ;
}
log . error ( getCellValue ( row . getCell ( 2 ) ) ) ;
log . error ( getCellValue ( row . getCell ( 3 ) ) ) ;
// 判断数据格式
if ( ! " 增 " . equals ( getCellValue ( row . getCell ( 2 ) ) ) & & ! " 减 " . equals ( getCellValue ( row . getCell ( 2 ) ) ) ) {
throw new ServiceException ( ResultCode . GOODS_STOCK_IMPORT_ERROR , " 库存修改方向列数据必须是“增”或“减” " ) ;
} else if ( ! NumberUtil . isInteger ( getCellValue ( row . getCell ( 3 ) ) ) | | Integer . parseInt ( getCellValue ( row . getCell ( 3 ) ) ) < 0 ) {
throw new ServiceException ( ResultCode . GOODS_STOCK_IMPORT_ERROR , " 库存必须是正整数 " ) ;
} else if ( this . count ( new LambdaQueryWrapper < GoodsSku > ( )
. eq ( GoodsSku : : getGoodsId , getCellValue ( row . getCell ( 0 ) ) )
. eq ( GoodsSku : : getId , getCellValue ( row . getCell ( 1 ) ) )
. eq ( GoodsSku : : getStoreId , storeId ) ) = = 0 ) {
throw new ServiceException ( ResultCode . GOODS_STOCK_IMPORT_ERROR , " 第 " + rowIndex + " 行商品不存在 " ) ;
}
GoodsSkuStockDTO goodsSkuStockDTO = new GoodsSkuStockDTO ( ) ;
goodsSkuStockDTO . setGoodsId ( getCellValue ( row . getCell ( 0 ) ) ) ;
goodsSkuStockDTO . setSkuId ( getCellValue ( row . getCell ( 1 ) ) ) ;
goodsSkuStockDTO . setType ( GoodsStockTypeEnum . fromDescription ( getCellValue ( row . getCell ( 2 ) ) ) . name ( ) ) ;
goodsSkuStockDTO . setQuantity ( Integer . parseInt ( getCellValue ( row . getCell ( 3 ) ) ) ) ;
goodsSkuStockDTOList . add ( goodsSkuStockDTO ) ;
}
} catch ( IOException e ) {
log . error ( " IOException occurred while processing the Excel file. " , e ) ;
throw new ServiceException ( ResultCode . GOODS_STOCK_IMPORT_ERROR , e . getMessage ( ) ) ;
}
// 批量修改商品库存
this . updateStocksByType ( goodsSkuStockDTOList ) ;
}
private String getCellValue ( Cell cell ) {
if ( cell = = null ) {
return " " ;
}
switch ( cell . getCellType ( ) ) {
case STRING :
return cell . getStringCellValue ( ) ;
case NUMERIC :
if ( DateUtil . isCellDateFormatted ( cell ) ) {
return cell . getDateCellValue ( ) . toString ( ) ;
} else {
// 将数值转换为整数以去掉小数点
double numericValue = cell . getNumericCellValue ( ) ;
if ( numericValue = = ( long ) numericValue ) {
return String . valueOf ( ( long ) numericValue ) ;
} else {
return String . valueOf ( numericValue ) ;
}
}
case BOOLEAN :
return String . valueOf ( cell . getBooleanCellValue ( ) ) ;
case FORMULA :
return cell . getCellFormula ( ) ;
default :
return " " ;
}
}
@Override
public IPage < GoodsSkuDTO > getGoodsSkuDTOByPage ( Page < GoodsSkuDTO > page , Wrapper < GoodsSkuDTO > queryWrapper ) {
return this . baseMapper . queryByParams ( page , queryWrapper ) ;
@@ -529,6 +651,27 @@ public class GoodsSkuServiceImpl extends ServiceImpl<GoodsSkuMapper, GoodsSku> i
}
}
@Override
@Transactional ( rollbackFor = Exception . class )
public void updateStocksByType ( List < GoodsSkuStockDTO > goodsSkuStockDTOS ) {
// 获取所有的goodsID, 并去除相同的goodsID
List < String > goodsIds = goodsSkuStockDTOS . stream ( )
. map ( GoodsSkuStockDTO : : getGoodsId )
. distinct ( )
. collect ( Collectors . toList ( ) ) ;
//更新SKU库存
for ( GoodsSkuStockDTO goodsSkuStockDTO : goodsSkuStockDTOS ) {
this . updateStock ( goodsSkuStockDTO . getSkuId ( ) , goodsSkuStockDTO . getQuantity ( ) , goodsSkuStockDTO . getType ( ) ) ;
}
//更新SPU库存
for ( String goodsId : goodsIds ) {
goodsService . updateStock ( goodsId ) ;
}
}
@Override
public void updateAlertQuantity ( GoodsSkuStockDTO goodsSkuStockDTO ) {
GoodsSku goodsSku = this . getById ( goodsSkuStockDTO . getSkuId ( ) ) ;
@@ -559,7 +702,6 @@ public class GoodsSkuServiceImpl extends ServiceImpl<GoodsSkuMapper, GoodsSku> i
this . updateBatchById ( goodsSkuList ) ;
}
@Override
@Transactional ( rollbackFor = Exception . class )
public void updateStock ( String skuId , Integer quantity ) {
@@ -591,6 +733,45 @@ public class GoodsSkuServiceImpl extends ServiceImpl<GoodsSkuMapper, GoodsSku> i
}
}
@Override
@Transactional ( rollbackFor = Exception . class )
public void updateStock ( String skuId , Integer quantity , String type ) {
GoodsSku goodsSku = getGoodsSkuByIdFromCache ( skuId ) ;
if ( goodsSku ! = null ) {
//计算修改库存
if ( type . equals ( GoodsStockTypeEnum . ADD . name ( ) ) ) {
quantity = Convert . toInt ( NumberUtil . add ( goodsSku . getQuantity ( ) , quantity ) ) ;
} else {
quantity = Convert . toInt ( NumberUtil . sub ( goodsSku . getQuantity ( ) , quantity ) ) ;
}
goodsSku . setQuantity ( quantity ) ;
//判断商品sku是否已经下架(修改商品库存为0时 会自动下架商品),再次更新商品库存时 需更新商品索引
boolean isFlag = goodsSku . getQuantity ( ) < = 0 ;
boolean update = this . update ( new LambdaUpdateWrapper < GoodsSku > ( ) . eq ( GoodsSku : : getId , skuId ) . set ( GoodsSku : : getQuantity , quantity ) ) ;
if ( update ) {
cache . remove ( CachePrefix . GOODS . getPrefix ( ) + goodsSku . getGoodsId ( ) ) ;
}
cache . put ( GoodsSkuService . getCacheKeys ( skuId ) , goodsSku ) ;
cache . put ( GoodsSkuService . getStockCacheKey ( skuId ) , quantity ) ;
this . promotionGoodsService . updatePromotionGoodsStock ( goodsSku . getId ( ) , quantity ) ;
//商品库存为0是删除商品索引
if ( quantity < = 0 ) {
goodsIndexService . deleteIndexById ( goodsSku . getId ( ) ) ;
}
//商品SKU库存为0并且商品sku状态为上架时更新商品库存
if ( isFlag & & CharSequenceUtil . equals ( goodsSku . getMarketEnable ( ) , GoodsStatusEnum . UPPER . name ( ) ) ) {
List < String > goodsIds = new ArrayList < > ( ) ;
goodsIds . add ( goodsSku . getGoodsId ( ) ) ;
applicationEventPublisher . publishEvent ( new TransactionCommitSendMQEvent ( " 更新商品 " , rocketmqCustomProperties . getGoodsTopic ( ) ,
GoodsTagsEnum . UPDATE_GOODS_INDEX . name ( ) , goodsIds ) ) ;
}
}
}
@Override
public Integer getStock ( String skuId ) {
String cacheKeys = GoodsSkuService . getStockCacheKey ( skuId ) ;
@@ -780,4 +961,150 @@ public class GoodsSkuServiceImpl extends ServiceImpl<GoodsSkuMapper, GoodsSku> i
return skuSpecVOList ;
}
/**
* 初始化填充商品库存导出数据
*
* @param goodsSkuStockDTOList 导出的库存数据
* @return 商品库存导出列表
*/
private XSSFWorkbook initStockExportData ( List < GoodsSkuStockDTO > goodsSkuStockDTOList ) {
XSSFWorkbook workbook = new XSSFWorkbook ( ) ;
// 创建模板
this . createTemplate ( workbook ) ;
// 创建sku库存列表
this . skuStockList ( workbook , goodsSkuStockDTOList ) ;
// 创建sku库存列表
this . skuList ( workbook , goodsSkuStockDTOList ) ;
return workbook ;
}
/**
* 创建模板
*
* @param workbook
*/
private void createTemplate ( XSSFWorkbook workbook ) {
Sheet templateSheet = workbook . createSheet ( " 商品库存编辑模板 " ) ;
// 创建表头
Row description = templateSheet . createRow ( 0 ) ;
description . setHeightInPoints ( 90 ) ;
Cell descriptionCell = description . createCell ( 0 ) ;
descriptionCell . setCellValue ( " 填写说明(请勿删除本说明): \ n " +
" 1.可批量设置多个商品的库存, 一次最多10000行 \ n " +
" 2.库存修改方向:选择库存方向后,会在原先库存基础上进行增加或者减少 \ n " +
" 3.库存变更数量: 需要为整数, 不能填写0和负数 " ) ;
// 合并描述行的单元格
templateSheet . addMergedRegion ( new CellRangeAddress ( 0 , 0 , 0 , 3 ) ) ;
// 设置描述行单元格样式(例如,自动换行)
CellStyle descriptionStyle = workbook . createCellStyle ( ) ;
descriptionStyle . setWrapText ( true ) ;
descriptionStyle . setAlignment ( HorizontalAlignment . LEFT ) ;
descriptionStyle . setVerticalAlignment ( VerticalAlignment . CENTER ) ;
descriptionCell . setCellStyle ( descriptionStyle ) ;
// 创建表头
Row header = templateSheet . createRow ( 1 ) ;
String [ ] headers = { " 商品ID( 必填) " , " skuID( 必填) " , " 库存修改方向(必填,填 增 或者 减) " , " 库存变更数量(必填) " } ;
CellStyle headerStyle = workbook . createCellStyle ( ) ;
Font headerFont = workbook . createFont ( ) ;
headerFont . setBold ( true ) ;
headerStyle . setFont ( headerFont ) ;
for ( int i = 0 ; i < headers . length ; i + + ) {
Cell cell = header . createCell ( i ) ;
cell . setCellValue ( headers [ i ] ) ;
cell . setCellStyle ( headerStyle ) ;
}
//修改列宽
templateSheet . setColumnWidth ( 0 , 30 * 256 ) ;
templateSheet . setColumnWidth ( 1 , 30 * 256 ) ;
templateSheet . setColumnWidth ( 2 , 40 * 256 ) ;
templateSheet . setColumnWidth ( 3 , 25 * 256 ) ;
// 设置下拉列表数据验证
DataValidationHelper validationHelper = templateSheet . getDataValidationHelper ( ) ;
DataValidationConstraint constraint = validationHelper . createExplicitListConstraint ( new String [ ] { " 增 " , " 减 " } ) ;
CellRangeAddressList addressList = new CellRangeAddressList ( 2 , 10001 , 2 , 2 ) ; // 从第3行到第10002行, 第3列
DataValidation validation = validationHelper . createValidation ( constraint , addressList ) ;
validation . setSuppressDropDownArrow ( true ) ;
validation . setShowErrorBox ( true ) ;
templateSheet . addValidationData ( validation ) ;
}
/**
* 创建sku库存列表
*
* @param workbook
*/
private void skuStockList ( XSSFWorkbook workbook , List < GoodsSkuStockDTO > goodsSkuStockDTOList ) {
Sheet skuListSheet = workbook . createSheet ( " 商品库存信息 " ) ;
// 创建表头
Row header = skuListSheet . createRow ( 0 ) ;
String [ ] headers = { " 商品ID " , " 商品名称 " , " 规格ID(SKUID) " , " 规格名称 " , " 货号 " , " 当前库存数量 " } ;
for ( int i = 0 ; i < headers . length ; i + + ) {
Cell cell = header . createCell ( i ) ;
cell . setCellValue ( headers [ i ] ) ;
}
// 填充数据
for ( int i = 0 ; i < goodsSkuStockDTOList . size ( ) ; i + + ) {
GoodsSkuStockDTO dto = goodsSkuStockDTOList . get ( i ) ;
Row row = skuListSheet . createRow ( i + 1 ) ;
row . createCell ( 0 ) . setCellValue ( dto . getGoodsId ( ) ) ;
row . createCell ( 1 ) . setCellValue ( dto . getGoodsName ( ) ) ;
row . createCell ( 2 ) . setCellValue ( dto . getSkuId ( ) ) ;
row . createCell ( 3 ) . setCellValue ( dto . getSimpleSpecs ( ) ) ;
row . createCell ( 4 ) . setCellValue ( dto . getSn ( ) ) ;
row . createCell ( 5 ) . setCellValue ( dto . getQuantity ( ) ) ;
}
//修改列宽
skuListSheet . setColumnWidth ( 0 , 30 * 256 ) ;
skuListSheet . setColumnWidth ( 1 , 30 * 256 ) ;
skuListSheet . setColumnWidth ( 2 , 30 * 256 ) ;
skuListSheet . setColumnWidth ( 3 , 30 * 256 ) ;
skuListSheet . setColumnWidth ( 4 , 30 * 256 ) ;
skuListSheet . setColumnWidth ( 5 , 15 * 256 ) ;
}
private void skuList ( XSSFWorkbook workbook , List < GoodsSkuStockDTO > goodsSkuStockDTOList ) {
Sheet skuListSheet = workbook . createSheet ( " 商品规格 " ) ;
// 创建表头
Row header = skuListSheet . createRow ( 0 ) ;
String [ ] headers = { " 商品ID " , " 商品名称 " , " 规格ID(SKUID) " , " 规格名称 " , " 货号 " } ;
for ( int i = 0 ; i < headers . length ; i + + ) {
Cell cell = header . createCell ( i ) ;
cell . setCellValue ( headers [ i ] ) ;
}
// 填充数据
for ( int i = 0 ; i < goodsSkuStockDTOList . size ( ) ; i + + ) {
GoodsSkuStockDTO dto = goodsSkuStockDTOList . get ( i ) ;
Row row = skuListSheet . createRow ( i + 1 ) ;
row . createCell ( 0 ) . setCellValue ( dto . getGoodsId ( ) ) ;
row . createCell ( 1 ) . setCellValue ( dto . getGoodsName ( ) ) ;
row . createCell ( 2 ) . setCellValue ( dto . getSkuId ( ) ) ;
row . createCell ( 3 ) . setCellValue ( dto . getSimpleSpecs ( ) ) ;
row . createCell ( 4 ) . setCellValue ( dto . getSn ( ) ) ;
}
//修改列宽
skuListSheet . setColumnWidth ( 0 , 30 * 256 ) ;
skuListSheet . setColumnWidth ( 1 , 30 * 256 ) ;
skuListSheet . setColumnWidth ( 2 , 30 * 256 ) ;
skuListSheet . setColumnWidth ( 3 , 30 * 256 ) ;
skuListSheet . setColumnWidth ( 4 , 30 * 256 ) ;
}
}