Merge branch 'dev' of github.com:LQYBill/wia_app into feat/clientInvoicingOptimization

pull/8040/head
Gauthier LO 2024-12-20 17:16:51 +01:00
commit 7dfeee0b6f
13 changed files with 336 additions and 21 deletions

View File

@ -1,9 +1,6 @@
package org.jeecg.modules.business.controller.admin;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -15,6 +12,7 @@ import org.jeecg.modules.business.entity.Sku;
import org.jeecg.modules.business.entity.SkuWeight;
import org.jeecg.modules.business.mongoService.SkuMongoService;
import org.jeecg.modules.business.service.ISecurityService;
import org.jeecg.modules.business.service.ISkuListMabangService;
import org.jeecg.modules.business.service.ISkuService;
import org.jeecg.modules.business.service.ISkuWeightService;
@ -24,6 +22,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.system.base.controller.JeecgController;
import org.jeecg.modules.business.vo.Responses;
import org.jeecg.modules.business.vo.SkuWeightParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
@ -45,6 +44,8 @@ import org.apache.shiro.authz.annotation.RequiresPermissions;
@RequestMapping("/skuWeight")
@Slf4j
public class SkuWeightController extends JeecgController<SkuWeight, ISkuWeightService> {
@Autowired
private ISkuListMabangService skuListMabangService;
@Autowired
private ISkuService skuService;
@Autowired
@ -164,7 +165,8 @@ public class SkuWeightController extends JeecgController<SkuWeight, ISkuWeightSe
}
/**
* Updating weight of multiple SKUs, creates new sku_weight entries with new effective_date and weight.
* /!\ Not maintained use updateBatch instead.
* Updating weight of a SKU, creates new sku_weight entry with new effective_date and weight.
* @param param
* @return
*/
@ -188,16 +190,23 @@ public class SkuWeightController extends JeecgController<SkuWeight, ISkuWeightSe
skuWeightService.save(skuWeight);
return Result.OK("data.invoice.effectiveDate");
}
/**
* Updating weight of multiple SKUs, creates new sku_weight entries with new effective_date and weight.
* Updates the weight in Mabang.
* Updates the weight in MongoDB.
* @param param
* @return
*/
@Transactional
@PostMapping(value = "/updateBatch")
public Result<String> updateBatch(@RequestBody SkuWeightParam param) {
public Result<?> updateBatch(@RequestBody SkuWeightParam param) {
boolean isEmployee = securityService.checkIsEmployee();
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
if(!isEmployee){
log.info("User {}, tried to access /skuWeight/updateBatch but is not authorized.", sysUser.getUsername());
return Result.error(403,"Forbidden.");
}
List<SkuWeight> skuWeights = new ArrayList<>();
Map<String, SkuWeight> skuWeightsMap = new HashMap<>();
for(String skuId : param.getIds()){
Sku sku = skuService.getById(skuId);
if(sku == null){
@ -208,10 +217,15 @@ public class SkuWeightController extends JeecgController<SkuWeight, ISkuWeightSe
skuWeight.setEffectiveDate(param.getEffectiveDate());
skuWeight.setSkuId(skuId);
skuWeight.setWeight(param.getWeight());
skuWeights.add(skuWeight);
skuMongoService.upsertSkuWeight(skuWeight);
skuWeightsMap.put(sku.getErpCode(), skuWeight);
}
List<SkuWeight> skuWeights = new ArrayList<>(skuWeightsMap.values());
Responses responses = skuListMabangService.mabangSkuWeightUpdate(skuWeights);
List<SkuWeight> skuWeightSuccesses = new ArrayList<>();
responses.getSuccesses().forEach(skuErpCode -> skuWeightSuccesses.add(skuWeightsMap.get(skuErpCode)));
skuWeightSuccesses.forEach(skuWeight -> skuMongoService.upsertSkuWeight(skuWeight));
skuWeightService.saveBatch(skuWeights);
return Result.OK("data.invoice.effectiveDate");
return Result.OK(responses);
}
}

View File

@ -75,7 +75,7 @@ public class SkuData {
@JSONField(name="height")
private String height;
@JSONField(name="weight")
private Double weight;
private Integer weight;
/**
* saleRemark contains the weight
*/

View File

@ -0,0 +1,33 @@
package org.jeecg.modules.business.domain.api.mabang.stockDoChangeStock;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.modules.business.domain.api.mabang.Request;
import org.springframework.http.ResponseEntity;
/**
* This class contains some key information and necessary procedures
* to send a request to mabang "get order list" API, for example: target URL,
* correspondent HTTP method, procedure to generate authorization.
* <p>
* One can use static method {@code sendRequest} to send request with body,
* and then get respective response. Or use instance of this class, see below.
* <p>
* Because data returned by target API is paginated. One can retrieve all data
* by calling next and hasNext.
*/
@Slf4j
public class SkuChangeRequest extends Request {
private final SkuChangeRequestBody body;
public SkuChangeRequest(SkuChangeRequestBody body) {
super(body);
this.body = body;
}
@Override
public SkuChangeResponse send() {
ResponseEntity<String> res = rawSend();
return SkuChangeResponse.parse(JSON.parseObject(res.getBody()), body.getStockSku());
}
}

View File

@ -0,0 +1,72 @@
package org.jeecg.modules.business.domain.api.mabang.stockDoChangeStock;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import lombok.Getter;
import lombok.Setter;
import org.jeecg.modules.business.domain.api.mabang.RequestBody;
import org.jeecg.modules.business.domain.api.mabang.doSearchSkuListNew.SkuData;
import java.math.BigDecimal;
import java.util.function.Function;
@Getter
@Setter
public class SkuChangeRequestBody implements RequestBody {
private String stockSku;
private String nameCn;
private String nameEn;
private Integer status;
private BigDecimal salePrice;
private String declareName;
private String declareEname;
private String warehouse;
private String remark;
private Integer weight;
private Integer isGift;
@Override
public String api() {
return "stock-do-change-stock";
}
@Override
public JSONObject parameters() {
JSONObject json = new JSONObject();
putNonNull(json, "stockSku", stockSku);
putNonNull(json, "nameCN", nameCn);
putNonNull(json, "nameEN", nameEn);
putNonNull(json, "status", status);
putNonNull(json, "salePrice", salePrice);
putNonNull(json, "declareName", declareName);
putNonNull(json, "declareEname", declareEname);
putNonNull(json, "weight", weight);
putNonNull(json, "saleRemark", remark);
putNonNull(json, "isGift", isGift);
return json;
}
public SkuChangeRequestBody(SkuData data) {
this.stockSku = data.getErpCode();
this.nameCn = data.getNameCN();
this.nameEn = data.getNameEN();
this.salePrice = data.getSalePrice();
this.declareName = data.getDeclareNameZh();
this.declareEname = data.getDeclareNameEn();
this.remark = data.getSaleRemark();
this.weight = data.getWeight();
this.isGift = data.getIsGift();
}
private <E> void putNonNull(JSONObject json, String key, E value) {
if (value != null) {
json.put(key, value);
}
}
private <E, T> void putNonNull(JSONObject json, String key, E value, Function<E, T> mapper) {
if (value != null) {
json.put(key, mapper.apply(value));
}
}
}

View File

@ -0,0 +1,11 @@
package org.jeecg.modules.business.domain.api.mabang.stockDoChangeStock;
/**
* This class represents error that is returned by target stock-do-change-stock API
* Message will contain error details.
*/
public class SkuChangeRequestErrorException extends RuntimeException {
public SkuChangeRequestErrorException(String msg) {
super(msg);
}
}

View File

@ -0,0 +1,63 @@
package org.jeecg.modules.business.domain.api.mabang.stockDoChangeStock;
import com.alibaba.fastjson.JSONObject;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.modules.business.domain.api.mabang.Response;
/**
* Immutable object
*/
@Slf4j
@Getter
public class SkuChangeResponse extends Response {
/**
* Current page data
*/
private final JSONObject data;
private final String stockId;
private final String stockSku;
public SkuChangeResponse(Code code, JSONObject data, String stockId, String stockSku) {
super(code);
this.data = data;
this.stockId = stockId;
this.stockSku = stockSku;
}
/**
* Make an instance by parsing json, it only checks validity of code.
* if json is not valid, return null
*
* @param json the json to parse
* @return Instance
* @throws SkuChangeRequestErrorException if response code represents error.
*/
public static SkuChangeResponse parse(JSONObject json, String erpCode) throws SkuChangeRequestErrorException {
log.debug("Constructing a response by json.");
String code = json.getString("code");
if (code.equals(Code.ERROR.value))
throw new SkuChangeRequestErrorException(json.getString("message"));
JSONObject data = json.getJSONObject("data");
String stockId = data.getString("stockId");
if(data != null) {
log.info("Constructed response: data contained {}", data);
}
else {
log.info("Data is null");
}
return new SkuChangeResponse(Code.SUCCESS, data, stockId, erpCode);
}
@Override
public String toString() {
return "SkuChangeResponse{" +
", data=" + data +
'}';
}
}

View File

@ -25,10 +25,11 @@ public class CreateFulfillmentRequest extends ShopifyRequest {
DHL_PACKET("https://www.dhl.de/en/privatkunden/pakete-empfangen/verfolgen.html?piececode=%s", "DHL Packet", "0034[0-9]{16}"),
GLS_NL("https://www.gls-info.nl/tracking", "GLS", "[0-9]{20}"),
GLS_NL_2("https://www.gls-info.nl/tracking", "GLS", "(1437|1000)[0-9]{10}"),
// USPS("https://tools.usps.com/go/TrackConfirmAction?qtc_tLabels1=%s", "USPS", "[0-9]{22,34}"),
USPS("https://tools.usps.com/go/TrackConfirmAction?tRef=fullpage&tLc=2&text28777=&tLabels=%s&tABt=false", "USPS", "926[0-9]{23}|420[0-9]{5}926[0-9]{23}"),
AUSTRIAN_POST("https://www.post.at/s/sendungsdetails?snr=%s", "Austrian Post", "15828030053[0-9]{13}"),
DHL_PACKET_WIA("https://www.dhl.de/en/privatkunden/pakete-empfangen/verfolgen.html?piececode=%s", "DHL Packet", "CD[0-9]{9}DE"),
DHL_PARCEL_WIA_NL("https://my.dhlparcel.nl/home/tracktrace/%s/%s?lang=nl_NL", "DHL Parcel", "3S[A-Z]{4}[0-9]{9}"),
DHL_PARCEL_NL("https://my.dhlecommerce.nl/home/tracktrace/%s/%s?lang=nl_NL", "DHL Parcel", "JVGL[0-9]{20}"),
GLS_IT("https://gls-group.com/IT/it/servizi-online/ricerca-spedizioni.html?match=%s&type=NAT", "GLS", "LT[0-9]{9}"),
COLIS_PRIVE_BE("https://colisprive.com/moncolis/pages/detailColis.aspx?numColis=%s&lang=fr", "Colis Privé", "Q[0-9]{11}[B][0-9]{4}|801000[0-9]{6}|R6700035[0-9]{4}B[0-9]{4}"),
COLIS_PRIVE_LU("https://colisprive.com/moncolis/pages/detailColis.aspx?numColis=%s", "Colis Privé", "Q[0-9]{11}[L][0-9]{4}"),
@ -49,6 +50,7 @@ public class CreateFulfillmentRequest extends ShopifyRequest {
QUICKPAC("https://quickpac.ch/de/tracking", "Quickpac", "44001091[0-9]{10}"),
CTT_EXPRESS("https://www.cttexpress.com/localizador-de-envios/", "CTT Express", "0082800082909[0-9]{9}"),
PDN_Express("https://pdn.express/nl/track/%s", "PDN Express", "PDN0003[0-9]{6}"),
UNI_EXPRESS("https://www.uniuni.com/tracking/#tracking-detail?no=%s", "Uni Express", "GV24CA[0-9A-Z]{12}"),
;
private final String trackingUrl;

View File

@ -55,6 +55,7 @@
ON s.owner_id = c.id
WHERE s.active = '1'
AND c.active = '1'
ORDER BY c.internal_code, s.erp_code
</select>
<select id="getIdByCode" resultType="java.lang.String">
SELECT id FROM shop

View File

@ -183,6 +183,7 @@
JOIN client c ON s.owner_id = c.id
WHERE c.id = #{clientId}
AND po.erp_status IN ('1','2')
AND po.can_send = 1
AND poc.erp_status IN ('1','2')
GROUP BY sku_id
)
@ -331,6 +332,7 @@
JOIN client c ON s.owner_id = c.id
WHERE c.id = #{clientId}
AND po.erp_status IN ('1','2')
AND po.can_send = 1
AND poc.erp_status IN ('1','2')
GROUP BY sku_id
)
@ -411,6 +413,7 @@
JOIN client c ON s.owner_id = c.id
WHERE c.id = #{clientId}
AND po.erp_status IN ('1','2')
AND po.can_send = 1
AND poc.erp_status IN ('1','2')
GROUP BY sku_id
)
@ -475,6 +478,7 @@
JOIN client c ON s.owner_id = c.id
WHERE c.id = #{clientId}
AND po.erp_status IN ('1','2')
AND po.can_send = 1
AND poc.erp_status IN ('1','2')
GROUP BY sku_id
)

View File

@ -3,6 +3,7 @@ package org.jeecg.modules.business.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.jeecg.modules.business.domain.api.mabang.doSearchSkuListNew.SkuData;
import org.jeecg.modules.business.entity.Sku;
import org.jeecg.modules.business.entity.SkuWeight;
import org.jeecg.modules.business.vo.Responses;
import org.jeecg.modules.business.vo.SkuOrderPage;
@ -38,4 +39,6 @@ public interface ISkuListMabangService extends IService<SkuData> {
void updateSkuId();
void mabangSkuStockUpdate(List<String> erpCodes);
Responses mabangSkuWeightUpdate(List<SkuWeight> skuWeights);
}

View File

@ -1,6 +1,7 @@
package org.jeecg.modules.business.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Lists;
import freemarker.template.Template;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.util.SpringContextUtils;
@ -9,10 +10,14 @@ import org.jeecg.modules.business.domain.api.mabang.doSearchSkuListNew.*;
import org.jeecg.modules.business.domain.api.mabang.stockDoAddStock.SkuAddRequest;
import org.jeecg.modules.business.domain.api.mabang.stockDoAddStock.SkuAddRequestBody;
import org.jeecg.modules.business.domain.api.mabang.stockDoAddStock.SkuAddResponse;
import org.jeecg.modules.business.domain.api.mabang.stockDoChangeStock.SkuChangeRequest;
import org.jeecg.modules.business.domain.api.mabang.stockDoChangeStock.SkuChangeRequestBody;
import org.jeecg.modules.business.domain.api.mabang.stockDoChangeStock.SkuChangeResponse;
import org.jeecg.modules.business.domain.api.mabang.stockGetStockQuantity.SkuStockData;
import org.jeecg.modules.business.domain.api.mabang.stockGetStockQuantity.SkuStockRawStream;
import org.jeecg.modules.business.domain.api.mabang.stockGetStockQuantity.SkuStockRequestBody;
import org.jeecg.modules.business.domain.api.mabang.stockGetStockQuantity.SkuStockStream;
import org.jeecg.modules.business.domain.job.ThrottlingExecutorService;
import org.jeecg.modules.business.entity.*;
import org.jeecg.modules.business.mapper.SkuListMabangMapper;
import org.jeecg.modules.business.mongoService.SkuMongoService;
@ -36,6 +41,7 @@ import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -73,6 +79,7 @@ public class SkuListMabangServiceImpl extends ServiceImpl<SkuListMabangMapper, S
Environment env;
private static final Integer DEFAULT_NUMBER_OF_THREADS = 10;
private static final Integer MABANG_API_RATE_LIMIT_PER_MINUTE = 10;
private final static String DEFAULT_WAREHOUSE_NAME = "SZBA宝安仓";
@ -258,11 +265,12 @@ public class SkuListMabangServiceImpl extends ServiceImpl<SkuListMabangMapper, S
skusToUpdate.add(s);
// update sku_weight
String remark = updateSkuWeight(s, skuData.getSaleRemark());
if(!remark.isEmpty())
updatedSkusRemarkMap.put(s, remark);
else
updatedSkusRemarkMap.put(s, "");
// String remark = updateSkuWeight(s, skuData.getSaleRemark());
// TODO : disabled temporarily because we are not updating the weight on Mabang, thus creating conflicts
// if(!remark.isEmpty())
// updatedSkusRemarkMap.put(s, remark);
// else
updatedSkusRemarkMap.put(s, "");
}
if(!skusToUpdate.isEmpty())
skuService.updateBatchById(skusToUpdate);
@ -625,4 +633,95 @@ public class SkuListMabangServiceImpl extends ServiceImpl<SkuListMabangMapper, S
skuMongoService.updateStock(sku);
}
}
@Override
public Responses mabangSkuWeightUpdate(List<SkuWeight> skuWeights) {
Responses responses = new Responses();
List<String> failures = new ArrayList<>();
List<Sku> skus = skuService.listByIds(skuWeights.stream()
.map(SkuWeight::getSkuId).collect(toList()));
Map<String, String> remarkMappedByErpCode = new HashMap<>();
List<List<String>> skusPartition = Lists.partition(skus.stream().map(Sku::getErpCode).collect(toList()), 50);
for(List<String> skuPartition : skusPartition) {
SkuListRequestBody body = new SkuListRequestBody();
body.setStockSkuList(String.join(",", skuPartition));
SkuListRawStream rawStream = new SkuListRawStream(body);
SkuUpdateListStream stream = new SkuUpdateListStream(rawStream);
List<SkuData> skusFromMabang = stream.all();
log.info("{} skus to be updated.", skusFromMabang.size());
if (skusFromMabang.isEmpty()) {
continue;
}
skusFromMabang.stream()
.filter(skuData -> skuData.getSaleRemark() != null)
.forEach(skuData -> {
String erpCode = skuData.getErpCode();
String remark = skuData.getSaleRemark();
remarkMappedByErpCode.put(erpCode, remark);
});
}
List<SkuData> skuDataList = skuWeights.stream()
.map(skuWeight -> {
Sku sku = skus.stream()
.filter(s -> s.getId().equals(skuWeight.getSkuId()))
.findFirst()
.orElse(null);
if(null == sku) {
log.error("Sku not found : {}", skuWeight.getSkuId());
failures.add(skuWeight.getSkuId());
return null;
}
SkuData skuData = new SkuData();
skuData.setErpCode(sku.getErpCode());
if(remarkMappedByErpCode.containsKey(sku.getErpCode())) {
StringBuilder remark = new StringBuilder();
remark.append(skuWeight.getWeight());
Matcher saleRemarkMatcher = saleRemarkPattern.matcher(remarkMappedByErpCode.get(sku.getErpCode()));
if(saleRemarkMatcher.matches() && !saleRemarkMatcher.group(2).isEmpty()) {
log.info("Sku {} has remark from Mabang : {}", sku.getErpCode(), saleRemarkMatcher.group(2));
String saleRemarkNotWeight = saleRemarkMatcher.group(2);
remark.append(saleRemarkNotWeight);
}
skuData.setSaleRemark(remark.toString());
}
else {
skuData.setSaleRemark(String.valueOf(skuWeight.getWeight()));
}
skuData.setWeight(skuWeight.getWeight());
return skuData;
})
.filter(Objects::nonNull)
.collect(toList());
ExecutorService executor = ThrottlingExecutorService.createExecutorService(DEFAULT_NUMBER_OF_THREADS, MABANG_API_RATE_LIMIT_PER_MINUTE, TimeUnit.MINUTES);
List<CompletableFuture<SkuChangeResponse>> futures = skuDataList.stream()
.map(skuData -> CompletableFuture.supplyAsync(() -> {
try {
SkuChangeRequestBody body = new SkuChangeRequestBody(skuData);
SkuChangeRequest request = new SkuChangeRequest(body);
return request.send();
} catch (Exception e) {
log.error("Error updating weight for sku {} : {}", skuData.getErpCode(), e.getMessage());
return new SkuChangeResponse(Response.Code.ERROR, null, null, skuData.getErpCode());
}
}, executor))
.collect(toList());
List<SkuChangeResponse> results = futures.stream().map(CompletableFuture::join).collect(toList());
long successCount = results.stream().filter(SkuChangeResponse::success).count();
log.info("{}/{} skus updated successfully.", successCount, skuDataList.size());
List<String> successes = results.stream()
.filter(SkuChangeResponse::success)
.map(SkuChangeResponse::getStockSku)
.collect(toList());
failures.addAll(results.stream()
.filter(response -> !response.success())
.map(SkuChangeResponse::getStockSku)
.collect(toList()));
responses.setSuccesses(successes);
responses.setFailures(failures);
return responses;
}
}

View File

@ -206,6 +206,7 @@ public class PurchaseOrderServiceImpl extends ServiceImpl<PurchaseOrderMapper, P
}
/**
* Used by clients only
* Generated a purchase order based on sku quantity, these sku are bought
* for some platform orders, their quantity may higher than those in platform
* orders. In case of higher, extra quantity are recorded to client's inventory.
@ -388,6 +389,7 @@ public class PurchaseOrderServiceImpl extends ServiceImpl<PurchaseOrderMapper, P
/**
* Used for generating purchase order from COMPLETE invoicing
* Generated a purchase order based on sku quantity, these sku are bought
* for some platform orders, their quantity may higher than those in platform
* orders. In case of higher, extra quantity are recorded to client's inventory.

View File

@ -176,6 +176,11 @@ spring:
host: 127.0.0.1
port: 6379
password: ''
#MongoDB 配置
data:
mongodb:
authentication-database: xxx
uri: xxx
#mybatis plus 设置
mybatis-plus:
mapper-locations: classpath*:org/jeecg/modules/**/xml/*Mapper.xml
@ -189,7 +194,7 @@ mybatis-plus:
table-underline: true
configuration:
# 这个配置会将执行的sql打印出来在开发或测试的时候可以用
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 返回类型为Map,显示null对应的字段
call-setters-on-nulls: true
#jeecg专用配置
@ -237,6 +242,9 @@ jeecg:
shippingInvoiceDetailDir: C://dev//invoices//shippingDetail
shippingInvoicePdfDir: C://dev//invoices//pdf//shipping
shippingInvoiceDetailPdfDir: C://dev//invoices//pdf//shippingDetail
invoiceDetailExportDir: xxx
# sku csv file for image search
skuCsvPath: xxx
#webapp文件路径
webapp: /opt/webapp
shiro:
@ -346,7 +354,10 @@ justauth:
timeout: 1h
mabang:
api:
appkey: "87a1a0c46df86eb2683e74776894dae9"
devid: "200809"
appkey: "xxx"
devid: "xxx"
company:
orgName: "WIA"
orgName: "xxx"
jessy:
name: "xxx"
email: "xxx"