Merge pull request #98 from LQYBill/dev

Prepare release 2.6.0
pull/8040/head
Qiuyi LI 2024-07-31 10:32:51 +02:00 committed by GitHub
commit 47b8740a21
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 552 additions and 82 deletions

View File

@ -20,6 +20,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.google.common.collect.Lists;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.codehaus.jettison.json.JSONException;
import org.jeecg.modules.business.domain.api.mabang.dochangeorder.ChangeOrderResponse;
import org.jeecg.modules.business.domain.api.mabang.dochangeorder.ChangeWarehouseRequest;
import org.jeecg.modules.business.domain.api.mabang.dochangeorder.ChangeWarehouseRequestBody;
@ -385,7 +386,7 @@ public class PlatformOrderController {
}
@PostMapping("/orderManagement")
public Result<?> orderManagement(@RequestBody List<PlatformOrderOperation> orderOperations) throws IOException {
public Result<?> orderManagement(@RequestBody List<PlatformOrderOperation> orderOperations) throws IOException, JSONException {
boolean isEmployee = securityService.checkIsEmployee();
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
Client client;
@ -469,6 +470,9 @@ public class PlatformOrderController {
}
List<Order> mabangOrders = platformOrderMabangService.getOrdersFromMabang(requests, executor);
for(Order mabangOrder : mabangOrders) {
if(mabangOrder.getTrackingNumber() == null) {
continue;
}
if(!mabangOrder.getTrackingNumber().isEmpty()) {
ordersWithTrackingNumber.add(mabangOrder);
}
@ -511,14 +515,17 @@ public class PlatformOrderController {
templateModel.put("lastname", client.getSurname());
if(cancelCount > 0) {
templateModel.put("cancelSuccessCount", cancelResponses.getSuccesses().size() + "/" + cancelCount);
templateModel.put("cancelSuccesses", cancelResponses.getSuccesses());
templateModel.put("cancelFailures", cancelResponses.getFailures());
}
if(suspendCount > 0) {
templateModel.put("suspendSuccessCount", suspendResponses.getSuccesses().size() + "/" + suspendCount);
templateModel.put("suspendSuccesses", suspendResponses.getSuccesses());
templateModel.put("suspendFailures", suspendResponses.getFailures());
}
if(editCount > 0) {
templateModel.put("editSuccessCount", editResponses.getSuccesses().size() + "/" + editCount);
templateModel.put("editSuccesses", editResponses.getSuccesses());
templateModel.put("editFailures", editResponses.getFailures());
}
@ -539,6 +546,13 @@ public class PlatformOrderController {
throw new RuntimeException(e);
}
// sync orders from Mabang
List<String> poIdsSuccesses = new ArrayList<>();
poIdsSuccesses.addAll(cancelResponses.getSuccesses());
poIdsSuccesses.addAll(suspendResponses.getSuccesses());
poIdsSuccesses.addAll(editResponses.getSuccesses());
platformOrderMabangService.syncOrdersFromMabang(poIdsSuccesses);
return Result.OK(result);
}
@GetMapping("/recipientInfo")

View File

@ -90,6 +90,9 @@ public class SkuData {
public SkuStatus getStatus() {
return SkuStatus.fromCode(this.status);
}
public int getStatusValue() {
return this.status;
}
public String toString() {
return "ID : " + this.id +
"\nStockSkuId : " + this.stockSkuId +

View File

@ -18,7 +18,7 @@ public class SkuListRequestBody implements RequestBody {
private String stockSku = null;
// 50 skus max
private String stockSkuList = null;
private DateType datetimeType;
private DateType datetimeType = DateType.CREATE;
private LocalDateTime startDate;
private LocalDateTime endDate;
private Integer page = 1;

View File

@ -0,0 +1,122 @@
package org.jeecg.modules.business.domain.api.mabang.doSearchSkuListNew;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.modules.business.domain.api.mabang.getorderlist.NetworkDataStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.NoSuchElementException;
/**
* This class provide stream of order.
*/
@Slf4j
public class SkuUpdateListStream implements NetworkDataStream<SkuData> {
private final NetworkDataStream<SkuListResponse> rawStream;
private List<SkuData> skus;
private int index;
private boolean began;
/**
* Flag of current data is already empty,
* either currentOrders is null or currentIndex arrives at the end.
* In both case, we should call next() of the rawStream.
*/
private boolean empty;
public SkuUpdateListStream(NetworkDataStream<SkuListResponse> rawStream) {
this.rawStream = rawStream;
skus = null;
this.index = 0;
this.empty = true;
this.began = false;
}
@Override
public List<SkuData> all() {
SkuData firstElement = attempt();
if (firstElement == null) {
return Collections.emptyList();
}
ArrayList<SkuData> res = new ArrayList<>();
if (firstElement.getStatus().equals(SkuStatus.Normal) || firstElement.getStatus().equals(SkuStatus.StoppedSelling)) {
res.add(firstElement);
}
while (hasNext()) {
SkuData nextSku = next();
if(nextSku.getStatus().equals(SkuStatus.Normal) || firstElement.getStatus().equals(SkuStatus.StoppedSelling)) {
res.add(nextSku);
}
}
return res;
}
@Override
public SkuData attempt() {
began = true;
log.info("Attempting for the first request");
SkuListResponse response = rawStream.attempt();
if (response == null) {
log.info("No response");
return null;
}
if (response.getData().isEmpty()) {
log.info("Response with empty data");
return null;
}
skus = response.getData().toJavaList(SkuData.class);
index = 1;
log.info("Returned the first element");
empty = index >= skus.size();
return skus.get(0);
}
@Override
public boolean hasNext() {
// the first time
if (!began) {
throw new IllegalStateException("Calling hasNext before begin");
}
// Current data is not yet empty
if (index < skus.size()) {
log.debug("Current order list is not empty yet");
return true;
}
/* Current data is empty */
this.empty = true;
log.debug("Current order list is already empty,");
// and raw stream is empty too.
if (!rawStream.hasNext()) {
log.debug("and source stream is empty too, hasNext: false");
return false;
}
// but raw stream not empty.
else {
log.debug("but source stream still has data, hasNext: true");
return true;
}
}
@Override
public SkuData next() {
if (!hasNext()) {
throw new NoSuchElementException("Stream is empty!");
}
if (empty) {
skus = this.rawStream.next().getData().toJavaList(SkuData.class);
empty = false;
index = 0;
}
log.debug("Return data at {}", index);
SkuData res = skus.get(index);
index++;
return res;
}
}

View File

@ -7,6 +7,7 @@ import org.codehaus.jettison.json.JSONObject;
import org.jeecg.modules.business.domain.api.mabang.dochangeorder.ArchiveOrderRequest;
import org.jeecg.modules.business.domain.api.mabang.dochangeorder.ArchiveOrderRequestBody;
import org.jeecg.modules.business.domain.api.mabang.dochangeorder.ChangeOrderResponse;
import org.jeecg.modules.business.service.IPlatformOrderMabangService;
import org.jeecg.modules.business.service.IPlatformOrderService;
import org.quartz.Job;
import org.quartz.JobDataMap;
@ -36,6 +37,8 @@ public class ArchiveOrderJob implements Job {
@Autowired
private IPlatformOrderService platformOrderService;
@Autowired
private IPlatformOrderMabangService platformOrderMabangService;
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
@ -86,7 +89,7 @@ public class ArchiveOrderJob implements Job {
platformOrderIds.forEach(s -> archiveOrderRequestBodies.add(new ArchiveOrderRequestBody(s)));
log.info("{} order archiving requests to be sent to MabangAPI", archiveOrderRequestBodies.size());
List<String> platformOrderIdsArchived = new ArrayList<>();
List<CompletableFuture<Boolean>> changeOrderFutures = archiveOrderRequestBodies.stream()
.map(archiveOrderRequestBody -> CompletableFuture.supplyAsync(() -> {
boolean success = false;
@ -94,7 +97,9 @@ public class ArchiveOrderJob implements Job {
ArchiveOrderRequest archiveOrderRequest = new ArchiveOrderRequest(archiveOrderRequestBody);
ChangeOrderResponse response = archiveOrderRequest.send();
success = response.success();
} catch (RuntimeException e) {
if(success)
platformOrderIdsArchived.add(archiveOrderRequestBody.getPlatformOrderId());
} catch (RuntimeException e) {
log.error("Error communicating with MabangAPI", e);
}
return success;
@ -103,5 +108,11 @@ public class ArchiveOrderJob implements Job {
List<Boolean> results = changeOrderFutures.stream().map(CompletableFuture::join).collect(Collectors.toList());
long nbSuccesses = results.stream().filter(b -> b).count();
log.info("{}/{} order archiving requests have succeeded.", nbSuccesses, archiveOrderRequestBodies.size());
try {
platformOrderMabangService.syncOrdersFromMabang(platformOrderIdsArchived);
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -1,13 +1,11 @@
package org.jeecg.modules.business.domain.job;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import org.jeecg.common.api.dto.message.TemplateMessageDTO;
import org.jeecg.common.system.api.ISysBaseAPI;
import org.jeecg.modules.business.domain.api.mabang.getorderlist.*;
import org.jeecg.modules.business.service.IPlatformOrderMabangService;
import org.jetbrains.annotations.NotNull;
import org.quartz.Job;
@ -17,10 +15,6 @@ import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
@Slf4j
public class MabangOrderSyncJob implements Job {
@ -29,7 +23,6 @@ public class MabangOrderSyncJob implements Job {
private IPlatformOrderMabangService platformOrderMabangService;
@Autowired
private ISysBaseAPI ISysBaseApi;
private static final Integer DEFAULT_NUMBER_OF_THREADS = 10;
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
JobDataMap jobDataMap = context.getMergedJobDataMap();
@ -54,53 +47,32 @@ public class MabangOrderSyncJob implements Job {
throw new RuntimeException("PlatformOrder ID list can't be empty !");
}
log.info("Syncing following orders {}", platformOrderIds);
List<List<String>> platformOrderIdLists = Lists.partition(platformOrderIds, 10);
List<OrderListRequestBody> requests = new ArrayList<>();
for (List<String> platformOrderIdList : platformOrderIdLists) {
requests.add(new OrderListRequestBody().setPlatformOrderIds(platformOrderIdList));
}
List<Order> mabangOrders = new ArrayList<>();
ExecutorService executor = Executors.newFixedThreadPool(DEFAULT_NUMBER_OF_THREADS);
List<CompletableFuture<Boolean>> futures = requests.stream()
.map(request -> CompletableFuture.supplyAsync(() -> {
boolean success = false;
try {
OrderListRawStream rawStream = new OrderListRawStream(request);
OrderListStream stream = new OrderListStream(rawStream);
List<Order> orders = stream.all();
mabangOrders.addAll(orders);
success = !orders.isEmpty();
} catch (RuntimeException e) {
log.error("Error communicating with MabangAPI", e);
}
return success;
}, executor))
.collect(Collectors.toList());
List<Boolean> results = futures.stream().map(CompletableFuture::join).collect(Collectors.toList());
long nbSuccesses = results.stream().filter(b -> b).count();
log.info("{}/{} requests have succeeded.", nbSuccesses, requests.size());
int syncedOrderNumber = mabangOrders.size();
List<String> syncedOrderIds = mabangOrders.stream().map(Order::getPlatformOrderId).collect(Collectors.toList());
log.info("{}/{} mabang orders have been retrieved.", syncedOrderNumber, platformOrderIds.size());
log.info("{} orders to be updated.", syncedOrderNumber);
platformOrderMabangService.saveOrderFromMabang(mabangOrders);
Map<String, String> param = new HashMap<>();
param.put("requested_order_number", String.valueOf(platformOrderIds.size()));
param.put("synced_order_number", String.valueOf(syncedOrderNumber));
param.put("requested_order_ids", getHtmlListFromStringList(platformOrderIds));
List<String> failedToSyncOrderIds = new ArrayList<>();
for (String platformOrderId : platformOrderIds) {
if (!syncedOrderIds.contains(platformOrderId)) {
failedToSyncOrderIds.add(platformOrderId);
try {
JSONObject res = platformOrderMabangService.syncOrdersFromMabang(platformOrderIds);
String syncedOrderNumber = String.valueOf(res.getInt("synced_order_number"));
List<String> syncedOrderIds = new ArrayList<>();
JSONArray syncedOrderIdsArray = res.getJSONArray("synced_order_ids");
for (int i = 0; i < syncedOrderIdsArray.length(); i++) {
syncedOrderIds.add(syncedOrderIdsArray.getString(i));
}
Map<String, String> param = new HashMap<>();
param.put("requested_order_number", String.valueOf(platformOrderIds.size()));
param.put("synced_order_number", syncedOrderNumber);
param.put("requested_order_ids", getHtmlListFromStringList(platformOrderIds));
List<String> failedToSyncOrderIds = new ArrayList<>();
for (String platformOrderId : platformOrderIds) {
if (!syncedOrderIds.contains(platformOrderId)) {
failedToSyncOrderIds.add(platformOrderId);
}
}
param.put("failed_to_sync_order_ids", getHtmlListFromStringList(failedToSyncOrderIds));
TemplateMessageDTO message = new TemplateMessageDTO("admin", username == null ? "admin" : username, "马帮订单同步任务", param, "mabang_order_sync_job_result");
ISysBaseApi.sendTemplateAnnouncement(message);
log.info("Order sync job recap message sent");
} catch (JSONException e) {
throw new RuntimeException(e);
}
param.put("failed_to_sync_order_ids", getHtmlListFromStringList(failedToSyncOrderIds));
TemplateMessageDTO message = new TemplateMessageDTO("admin", username == null ? "admin" : username, "马帮订单同步任务", param, "mabang_order_sync_job_result");
ISysBaseApi.sendTemplateAnnouncement(message);
log.info("Order sync job recap message sent");
}
@NotNull

View File

@ -0,0 +1,113 @@
package org.jeecg.modules.business.domain.job;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import org.jeecg.modules.business.domain.api.mabang.doSearchSkuListNew.*;
import org.jeecg.modules.business.service.ISkuListMabangService;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
/**
* A Job that retrieves all Sku from Mabang
* if the sku is of status 3 (normal) and not in DB, then we insert it in DB
*/
@Slf4j
@Component
public class MabangSkuSyncJob implements Job {
@Autowired
private ISkuListMabangService skuListMabangService;
private static final Integer DEFAULT_NUMBER_OF_DAYS = 5;
private static final DateType DEFAULT_DATE_TYPE = DateType.UPDATE;
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
LocalDateTime endDateTime = LocalDateTime.now(ZoneId.of(ZoneId.SHORT_IDS.get("CTT")));
LocalDateTime startDateTime = endDateTime.minusDays(DEFAULT_NUMBER_OF_DAYS);
List<String> skus = new ArrayList<>();
DateType dateType = DEFAULT_DATE_TYPE;
JobDataMap jobDataMap = context.getMergedJobDataMap();
String parameter = ((String) jobDataMap.get("parameter"));
if (parameter != null) {
try {
JSONObject jsonObject = new JSONObject(parameter);
if (!jsonObject.isNull("startDateTime")) {
String startDateStr = jsonObject.getString("startDateTime");
startDateTime = LocalDateTime.parse(startDateStr);
}
if (!jsonObject.isNull("endDateTime")) {
String endDateStr = jsonObject.getString("endDateTime");
endDateTime = LocalDateTime.parse(endDateStr);
}
if (!jsonObject.isNull("dateType")) {
dateType = DateType.fromCode(jsonObject.getInt("dateType"));
}
if (!jsonObject.isNull("skus")) {
JSONArray array = jsonObject.getJSONArray("skus");
for(int i = 0; i < array.length(); i++) {
skus.add(array.getString(i));
}
}
} catch (JSONException e) {
log.error("Error while parsing parameter as JSON, falling back to default parameters.");
}
}
if (!endDateTime.isAfter(startDateTime)) {
throw new RuntimeException("EndDateTime must be strictly greater than StartDateTime !");
}
try {
if(skus.isEmpty()) {
log.info("Updating skus by date");
while (startDateTime.until(endDateTime, ChronoUnit.HOURS) > 0) {
LocalDateTime dayBeforeEndDateTime = endDateTime.minusDays(1);
SkuListRequestBody body = SkuListRequestBodys.allSkuOfDateType(dayBeforeEndDateTime, endDateTime, dateType);
SkuListRawStream rawStream = new SkuListRawStream(body);
SkuUpdateListStream stream = new SkuUpdateListStream(rawStream);
// the status is directly filtered in all() method
List<SkuData> skusFromMabang = stream.all();
log.info("{} skus from {} to {} ({})to be updated.", skusFromMabang.size(),
dayBeforeEndDateTime, endDateTime, dateType);
if (!skusFromMabang.isEmpty()) {
// we save the skuDatas in DB
skuListMabangService.updateSkusFromMabang(skusFromMabang);
}
endDateTime = dayBeforeEndDateTime;
}
}
else {
log.info("Updating skus by erpCode : {}", skus);
List<List<String>> skusPartition = Lists.partition(skus, 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()) {
// we save the skuDatas in DB
skuListMabangService.updateSkusFromMabang(skusFromMabang);
}
}
}
} catch (SkuListRequestErrorException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -32,9 +32,19 @@ public class CostTrialCalculation {
private final BigDecimal additionalCost;
private final BigDecimal previousUnitPrice;
private CostTrialCalculation(String countryCode, String logisticsChannelName, String logisticChannelCode, BigDecimal unitPrice, BigDecimal shippingCost,
BigDecimal registrationCost, BigDecimal additionalCost, Date effectiveDate) {
private final BigDecimal previousShippingCost;
private final BigDecimal previousRegistrationCost;
private final BigDecimal previousAdditionalCost;
private CostTrialCalculation(String countryCode, String logisticsChannelName, String logisticChannelCode,
BigDecimal unitPrice, BigDecimal shippingCost, BigDecimal registrationCost, BigDecimal additionalCost, Date effectiveDate,
BigDecimal previousUnitPrice,BigDecimal previousShippingCost, BigDecimal previousRegistrationCost, BigDecimal previousAdditionalCost) {
this.countryCode = countryCode;
this.logisticsChannelName = logisticsChannelName;
this.logisticChannelCode = logisticChannelCode;
@ -43,15 +53,28 @@ public class CostTrialCalculation {
this.registrationCost = registrationCost;
this.additionalCost = additionalCost;
this.effectiveDate = effectiveDate;
this.previousUnitPrice = previousUnitPrice;
this.previousShippingCost = previousShippingCost;
this.previousRegistrationCost = previousRegistrationCost;
this.previousAdditionalCost = previousAdditionalCost;
}
public CostTrialCalculation(LogisticChannelPrice price, int weight, String logisticsChannelName, String code) {
public CostTrialCalculation(LogisticChannelPrice price, LogisticChannelPrice previousPrice,int weight, String logisticsChannelName, String code) {
this(price.getEffectiveCountry(), logisticsChannelName, code, price.getCalUnitPrice(), price.calculateShippingPrice(BigDecimal.valueOf(weight)),
price.getRegistrationFee(), price.getAdditionalCost(), price.getEffectiveDate());
price.getRegistrationFee(), price.getAdditionalCost(), price.getEffectiveDate(),
previousPrice.getCalUnitPrice(), previousPrice.calculateShippingPrice(BigDecimal.valueOf(weight)), previousPrice.getRegistrationFee(), previousPrice.getAdditionalCost()
);
}
@JsonProperty("TotalCost")
public double getTotalCost() {
return shippingCost.add(registrationCost).add(additionalCost).setScale(2, RoundingMode.CEILING).doubleValue();
}
@JsonProperty("CostDifference")
public double getCostDifference() {
double previousCost = previousShippingCost.add(previousRegistrationCost).add(previousAdditionalCost).setScale(2, RoundingMode.CEILING).doubleValue();
BigDecimal diff = BigDecimal.valueOf((getTotalCost() - previousCost) / previousCost * 100);
return diff.setScale(2, RoundingMode.CEILING).doubleValue();
}
}

View File

@ -98,4 +98,12 @@ public class Sku implements Serializable {
@Excel(name = "服务费", width = 15)
@ApiModelProperty(value = "服务费")
private java.math.BigDecimal serviceFee;
/**
* Status
* 1:;2:;3:;4:;5:"
* default : 3
*/
@Excel(name = "Status", width = 15)
@ApiModelProperty(value = "Status")
private java.lang.Integer status;
}

View File

@ -34,7 +34,7 @@ public interface LogisticChannelPriceMapper extends BaseMapper<LogisticChannelPr
* @param countryList the country, represented by 2 letters code
* @return one propre price
*/
LogisticChannelPrice findBy(
List<LogisticChannelPrice> findBy(
@Param("channelName") String channelName,
@Param("date") Date shippingTime,
@Param("trueWeight") BigDecimal weight,

View File

@ -33,7 +33,7 @@
AND effective_date &lt;= #{date}
AND active = 1
ORDER BY effective_date DESC
LIMIT 1 </select>
LIMIT 2 </select>
<select id="findPricesBy" resultType="org.jeecg.modules.business.entity.LogisticChannelPrice">
SELECT lcp.id, lcp.create_by, lcp.create_time, lcp.update_by, lcp.update_time,

View File

@ -209,6 +209,7 @@
LEFT JOIN sales_7 s7 ON s.id = s7.sku_id
LEFT JOIN qtyInOrdersNotShipped ON s.id = qtyInOrdersNotShipped.ID
WHERE client_sku.client_id = #{clientId}
AND s.status = 3
ORDER BY ${column} ${order}
<if test="size != -1">
LIMIT #{offset}, #{size}
@ -272,6 +273,7 @@
LEFT JOIN sales_7 s7 ON s.id = s7.sku_id
LEFT JOIN qtyInOrdersNotShippedCTE ON s.id = qtyInOrdersNotShippedCTE.ID
WHERE client_sku.client_id = #{clientId}
AND s.status = 3
AND (
<if test="erpCodes != ''">
s.erp_code REGEXP #{erpCodes}

View File

@ -47,7 +47,7 @@ public interface ILogisticChannelService extends IService<LogisticChannel> {
* @param country 2 letters code of the destination country
* @return one suitable logistic channel price
*/
LogisticChannelPrice findLogisticsChannelPrice(String channelName, Date date, int trueWeight, List<String> country);
List<LogisticChannelPrice> findLogisticsChannelPrice(String channelName, Date date, int trueWeight, List<String> country);
List<CostTrialCalculation> logisticChannelTrial(int weight, int volume, List<String> countryList);

View File

@ -1,6 +1,8 @@
package org.jeecg.modules.business.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import org.jeecg.modules.business.domain.api.mabang.getorderlist.Order;
import org.jeecg.modules.business.domain.api.mabang.getorderlist.OrderListRequestBody;
import org.jeecg.modules.business.vo.PlatformOrderOperation;
@ -40,4 +42,6 @@ public interface IPlatformOrderMabangService extends IService<Order> {
void clearLogisticChannel(List<Order> orders, ExecutorService executor);
String stripAccents(String input);
JSONObject syncOrdersFromMabang(List<String> platformOrderIds) throws JSONException;
}

View File

@ -15,6 +15,7 @@ public interface ISkuListMabangService extends IService<SkuData> {
* @param skuDataList skus to save.
*/
Map<Sku, String> saveSkuFromMabang(List<SkuData> skuDataList);
void updateSkusFromMabang(List<SkuData> skuDataList);
/**
* Save products to DB from mabang api.

View File

@ -90,13 +90,16 @@ public class LogisticChannelPriceServiceImpl extends ServiceImpl<LogisticChannel
String countryCode = countryService.findByEnName(order.getCountry()).getCode();
LogisticChannelPrice price = logisticChannelPriceMapper.findBy(
List<LogisticChannelPrice> priceList = logisticChannelPriceMapper.findBy(
logisticChannelName,
order.getShippingTime(),
weight,
Collections.singletonList(countryCode)
);
// find the one with latest effective date
LogisticChannelPrice price = priceList.stream()
.max(Comparator.comparing(LogisticChannelPrice::getEffectiveDate))
.orElse(null);
if (price == null) {
throw new UserException("Can't find price for channel {}, shipped at {}, weight {}, country {}",
logisticChannelName,

View File

@ -78,7 +78,7 @@ public class LogisticChannelServiceImpl extends ServiceImpl<LogisticChannelMappe
}
@Override
public LogisticChannelPrice findLogisticsChannelPrice(String channelName, Date date, int trueWeight, List<String> countryList) {
public List<LogisticChannelPrice> findLogisticsChannelPrice(String channelName, Date date, int trueWeight, List<String> countryList) {
return logisticChannelPriceMapper.findBy(channelName, new java.util.Date(), BigDecimal.valueOf(trueWeight), countryList);
}
@ -98,9 +98,15 @@ public class LogisticChannelServiceImpl extends ServiceImpl<LogisticChannelMappe
} else {
trueWeight = weight;
}
LogisticChannelPrice price = findLogisticsChannelPrice(channelName, new Date(), trueWeight, countryList);
List<LogisticChannelPrice> priceList = findLogisticsChannelPrice(channelName, new Date(), trueWeight, countryList);
LogisticChannelPrice price = priceList.stream()
.max(Comparator.comparing(LogisticChannelPrice::getEffectiveDate))
.orElse(null);
LogisticChannelPrice previousPrice = priceList.stream()
.min(Comparator.comparing(LogisticChannelPrice::getEffectiveDate))
.orElse(null);
if (price != null) {
return new CostTrialCalculation(price, trueWeight, internalName, code);
return new CostTrialCalculation(price, previousPrice, trueWeight, internalName, code);
} else {
return null;
}

View File

@ -1,8 +1,12 @@
package org.jeecg.modules.business.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import org.jeecg.common.system.api.ISysBaseAPI;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.modules.business.domain.api.mabang.dochangeorder.*;
import org.jeecg.modules.business.domain.api.mabang.getorderlist.*;
@ -13,6 +17,7 @@ import org.jeecg.modules.business.domain.job.ThrottlingExecutorService;
import org.jeecg.modules.business.entity.PlatformOrder;
import org.jeecg.modules.business.mapper.PlatformOrderMabangMapper;
import org.jeecg.modules.business.service.IPlatformOrderMabangService;
import org.jeecg.modules.business.service.IPlatformOrderService;
import org.jeecg.modules.business.vo.PlatformOrderOperation;
import org.jeecg.modules.business.vo.Responses;
import org.springframework.beans.factory.annotation.Autowired;
@ -23,6 +28,7 @@ import java.text.Normalizer;
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.stream.Collectors;
@ -40,6 +46,10 @@ import static java.util.stream.Collectors.toList;
public class PlatformOrderMabangServiceImpl extends ServiceImpl<PlatformOrderMabangMapper, Order> implements IPlatformOrderMabangService {
@Autowired
private PlatformOrderMabangMapper platformOrderMabangMapper;
@Autowired
private IPlatformOrderService orderservice;
@Autowired
private ISysBaseAPI ISysBaseApi;
private static final Integer DEFAULT_NUMBER_OF_THREADS = 2;
private static final Integer MABANG_API_RATE_LIMIT_PER_MINUTE = 10;
@ -92,6 +102,9 @@ public class PlatformOrderMabangServiceImpl extends ServiceImpl<PlatformOrderMab
) {
// If order wasn't invoiced pre-shipping, we can remove and re-insert contents
if (orderInDatabase.getShippingInvoiceNumber() == null) {
boolean hasInvoiceNumber = orderservice.getById(orderInDatabase.getId()).getShippingInvoiceNumber() != null;
if(hasInvoiceNumber)
continue;
oldOrders.add(retrievedOrder);
} else {
invoicedShippedOrders.add(retrievedOrder);
@ -116,11 +129,11 @@ public class PlatformOrderMabangServiceImpl extends ServiceImpl<PlatformOrderMab
/* for new orders, insert them to DB and their children */
List<OrderItem> allNewItems = prepareItems(newOrders);
try {
if (newOrders.size() != 0) {
if (!newOrders.isEmpty()) {
log.info("{} orders to be inserted/updated.", newOrders.size());
platformOrderMabangMapper.insertOrdersFromMabang(newOrders);
}
if (allNewItems.size() != 0) {
if (!allNewItems.isEmpty()) {
platformOrderMabangMapper.insertOrderItemsFromMabang(allNewItems);
log.info("{} order items to be inserted/updated.", allNewItems.size());
}
@ -131,12 +144,12 @@ public class PlatformOrderMabangServiceImpl extends ServiceImpl<PlatformOrderMab
// for old orders, update themselves and delete and reinsert their content.
List<OrderItem> allNewItemsOfOldItems = prepareItems(oldOrders);
try {
if (oldOrders.size() != 0) {
if (!oldOrders.isEmpty()) {
log.info("{} orders to be inserted/updated.", oldOrders.size());
platformOrderMabangMapper.batchUpdateById(oldOrders);
platformOrderMabangMapper.batchDeleteByMainID(oldOrders.stream().map(Order::getId).collect(toList()));
}
if (ordersFromShippedToCompleted.size() != 0) {
if (!ordersFromShippedToCompleted.isEmpty()) {
log.info("{} orders to be updated from Shipped to Completed.", ordersFromShippedToCompleted.size());
platformOrderMabangMapper.batchUpdateById(ordersFromShippedToCompleted);
log.info("Contents of {} orders to be updated from Shipped to Completed.", ordersFromShippedToCompleted.size());
@ -144,7 +157,7 @@ public class PlatformOrderMabangServiceImpl extends ServiceImpl<PlatformOrderMab
ordersFromShippedToCompleted.stream().map(Order::getId).collect(toList()),
OrderStatus.Completed.getCode());
}
if (invoicedShippedOrders.size() != 0) {
if (!invoicedShippedOrders.isEmpty()) {
log.info("{} orders to be updated from Pending/Preparing to Shipped.", invoicedShippedOrders.size());
platformOrderMabangMapper.batchUpdateById(invoicedShippedOrders);
log.info("Contents of {} orders to be updated from Pending/Preparing to Shipped.", invoicedShippedOrders.size());
@ -152,7 +165,7 @@ public class PlatformOrderMabangServiceImpl extends ServiceImpl<PlatformOrderMab
invoicedShippedOrders.stream().map(Order::getId).collect(toList()),
OrderStatus.Shipped.getCode());
}
if (obsoleteOrders.size() != 0) {
if (!obsoleteOrders.isEmpty()) {
log.info("{} orders to become obsolete.", obsoleteOrders.size());
platformOrderMabangMapper.batchUpdateById(obsoleteOrders);
log.info("Contents of {} orders to be updated to Obsolete.", obsoleteOrders.size());
@ -160,7 +173,7 @@ public class PlatformOrderMabangServiceImpl extends ServiceImpl<PlatformOrderMab
obsoleteOrders.stream().map(Order::getId).collect(toList()),
OrderStatus.Obsolete.getCode());
}
if (allNewItemsOfOldItems.size() != 0) {
if (!allNewItemsOfOldItems.isEmpty()) {
log.info("{} order items to be inserted/updated.", allNewItemsOfOldItems.size());
platformOrderMabangMapper.insertOrderItemsFromMabang(allNewItemsOfOldItems);
}
@ -324,4 +337,46 @@ public class PlatformOrderMabangServiceImpl extends ServiceImpl<PlatformOrderMab
input = input.replaceAll("[^\\p{ASCII}]", "");
return input;
}
@Transactional
@Override
public JSONObject syncOrdersFromMabang(List<String> platformOrderIds) throws JSONException {
log.info("Syncing following orders {}", platformOrderIds);
List<List<String>> platformOrderIdLists = Lists.partition(platformOrderIds, 10);
List<OrderListRequestBody> requests = new ArrayList<>();
for (List<String> platformOrderIdList : platformOrderIdLists) {
requests.add(new OrderListRequestBody().setPlatformOrderIds(platformOrderIdList));
}
List<Order> mabangOrders = new ArrayList<>();
ExecutorService executor = Executors.newFixedThreadPool(10);
List<CompletableFuture<Boolean>> futures = requests.stream()
.map(request -> CompletableFuture.supplyAsync(() -> {
boolean success = false;
try {
OrderListRawStream rawStream = new OrderListRawStream(request);
OrderListStream stream = new OrderListStream(rawStream);
List<Order> orders = stream.all();
mabangOrders.addAll(orders);
success = !orders.isEmpty();
} catch (RuntimeException e) {
log.error("Error communicating with MabangAPI", e);
}
return success;
}, executor))
.collect(Collectors.toList());
List<Boolean> results = futures.stream().map(CompletableFuture::join).collect(Collectors.toList());
long nbSuccesses = results.stream().filter(b -> b).count();
log.info("{}/{} requests have succeeded.", nbSuccesses, requests.size());
int syncedOrderNumber = mabangOrders.size();
List<String> syncedOrderIds = mabangOrders.stream().map(Order::getPlatformOrderId).collect(Collectors.toList());
log.info("{}/{} mabang orders have been retrieved.", syncedOrderNumber, platformOrderIds.size());
log.info("{} orders to be updated.", syncedOrderNumber);
saveOrderFromMabang(mabangOrders);
JSONObject res = new JSONObject();
res.put("synced_order_number", syncedOrderNumber);
res.put("synced_order_ids", syncedOrderIds);
return res;
}
}

View File

@ -54,7 +54,10 @@ public class SkuListMabangServiceImpl extends ServiceImpl<SkuListMabangMapper, S
@Autowired
Environment env;
// In NameCN field on top of the product name we also get the customer code in the beginning of the string : "XX Description of the product"
final Pattern cnNamePattern = Pattern.compile("^([a-zA-Z]{2,5})\\s(.*)$");
// In NameEN field on top of the product name we also get the customer code in the beginning of the string : "XX-En name of product"
final Pattern enNamePattern = Pattern.compile("^([a-zA-Z]{2,5})-(.*)$");
/**
* Save skus to DB from mabang api.
*
@ -94,7 +97,7 @@ public class SkuListMabangServiceImpl extends ServiceImpl<SkuListMabangMapper, S
/* for new skuDatas, insert them to DB */
try {
if (newSkuDatas.size() != 0) {
if (!newSkuDatas.isEmpty()) {
// we need to check if the product associated with the sku exists, for that we are going to parse the Sku erpCode into product code
// check if the product code exists in DB, if not we create a new entry in DB and fill all the infos.
// then we can finally add the new Sku, product has to be created first if it doesn't exist, since we need to fill productID in Sku table
@ -160,6 +163,63 @@ public class SkuListMabangServiceImpl extends ServiceImpl<SkuListMabangMapper, S
return newSkusMap;
}
@Override
@Transactional
public void updateSkusFromMabang(List<SkuData> skuDataList) {
if (skuDataList.isEmpty()) {
return;
}
// we collect all erpCode
List<String> allSkuErpCode = skuDataList.stream()
.map(SkuData::getErpCode)
.collect(toList());
// find Skus that already exist in DB
List<Sku> existingSkuList = skuListMabangMapper.searchExistence(allSkuErpCode);
// We map all existing Skus in DB with erpCode as key
Map<String, Sku> existingSkusIDMap = existingSkuList.stream()
.collect(
Collectors.toMap(
Sku::getErpCode, Function.identity()
)
);
ArrayList<SkuData> existingSkuDatas = new ArrayList<>();
for (SkuData retrievedSkuData : skuDataList) {
Sku skuInDatabase = existingSkusIDMap.get(retrievedSkuData.getErpCode());
// the current SkuData's erpCode is in DB, so we add it to the list of existingSkuDatas
if (skuInDatabase != null) {
existingSkuDatas.add(retrievedSkuData);
}
}
/* for skuDatas to update, update product names and sku status them to DB */
try {
if (!existingSkuDatas.isEmpty()) {
// we need to check if the product associated with the sku exists, for that we are going to parse the Sku erpCode into product code
// check if the product code exists in DB, if not we create a new entry in DB and fill all the infos.
// then we can finally add the new Sku, product has to be created first if it doesn't exist, since we need to fill productID in Sku table
// we can now proceed to create new sku_declare_value associated with the new Sku and also sku_price
updateProductFromMabang(existingSkuDatas);
log.info("{} skus to be updated.", existingSkuDatas.size());
//update status of existing skus
List<Sku> skusToUpdate = new ArrayList<>();
for(SkuData skuData: existingSkuDatas) {
Sku s = new Sku();
s.setId(existingSkusIDMap.get(skuData.getErpCode()).getId());
s.setUpdateBy("mabang api");
s.setUpdateTime(new Date());
s.setStatus(skuData.getStatusValue());
skusToUpdate.add(s);
}
skuService.updateBatchById(skusToUpdate);
log.info("Updated {} skus : {}.", skusToUpdate.size(), existingSkuDatas.stream().map(SkuData::getErpCode).collect(toList()));
}
} catch (RuntimeException e) {
log.error(e.getLocalizedMessage());
}
}
/**
* Save products to DB from mabang api.
*
@ -197,6 +257,63 @@ public class SkuListMabangServiceImpl extends ServiceImpl<SkuListMabangMapper, S
}
return productsWithInfoMap;
}
/**
* Update products enName and zhName to DB from mabang api.
*
* @param skuDataList we update the product enName and zhName of the skus
*/
public void updateProductFromMabang(List<SkuData> skuDataList) {
List<String> allProductCodes = parseSkuListToProductCodeList(skuDataList);
List< Product> existingProduct = skuListMabangMapper.searchProductExistence(allProductCodes);
Map<String, Product> existingProductsIDMap = existingProduct.stream()
.collect(
Collectors.toMap(
Product::getCode, Function.identity()
)
);
List<SkuData> skuDatasToUpdate = new ArrayList<>();
// we ignore the new products and only update the existing ones
for(SkuData skuData : skuDataList) {
Product productInDB = existingProductsIDMap.get(parseSkuToProduct(skuData.getErpCode()));
// the current product code is in DB, so we add it to the list of newProducts
if (productInDB != null) {
skuDatasToUpdate.add(skuData);
}
}
List<Product> productsToUpdate = new ArrayList<>();
for(SkuData skuData: skuDatasToUpdate) {
Product p = new Product();
p.setId(existingProductsIDMap.get(parseSkuToProduct(skuData.getErpCode())).getId());
p.setUpdateBy("mabang api");
p.setUpdateTime(new Date());
// Removing the customer code from the product CN name
if (!skuData.getNameEN().isEmpty()) {
Matcher enNameMatcher = enNamePattern.matcher(skuData.getNameEN());
if (enNameMatcher.matches() && !enNameMatcher.group(2).isEmpty()) {
p.setEnName(enNameMatcher.group(2));
}
else {
p.setEnName(skuData.getNameEN());
}
}
// Removing the customer code from the product CN name
if (!skuData.getNameCN().isEmpty()) {
Matcher cnNameMatcher = cnNamePattern.matcher(skuData.getNameCN());
if (cnNameMatcher.matches() && !cnNameMatcher.group(2).isEmpty()) {
p.setZhName(cnNameMatcher.group(2));
}
else {
p.setZhName(skuData.getNameCN());
}
}
productsToUpdate.add(p);
}
if(!productsToUpdate.isEmpty()) {
productService.updateBatchById(productsToUpdate);
}
log.info("Updated {} products : {}.", productsToUpdate.size(), skuDatasToUpdate.stream().map(SkuData::getErpCode).collect(toList()));
}
public void saveSkuPrices(List<SkuData> newSkus) {
List<SkuPrice> l = new ArrayList<>();
@ -290,8 +407,6 @@ public class SkuListMabangServiceImpl extends ServiceImpl<SkuListMabangMapper, S
final String electroMagSensitiveAttributeId = skuListMabangMapper.searchSensitiveAttributeId("Electro-magnetic");
final String electricSensitiveAttributeId = skuListMabangMapper.searchSensitiveAttributeId("Electronic/Electric");
final String normalSensitiveAttributeId = skuListMabangMapper.searchSensitiveAttributeId("Normal goods");
// In NameCN field on top of the product name we also get the customer code in the beginning of the string : "XX Description of the product"
final Pattern cnNamePattern = Pattern.compile("^([a-zA-Z]{2,5})\\s(.*)$");
// IN saleRemark sometimes not only the product weight provided, we can get extra information such as service_fee (eg : "15每件服务费0.2")
final Pattern saleRemarkPattern = Pattern.compile("^([0-9]*)(.*)$");
// we are stocking product codes, so we don't get duplicates which causes error

View File

@ -7,7 +7,13 @@
</tr>
<#if cancelSuccessCount??>
<tr>
<td style="padding:0 0 35px 0;">Demandes d'annulations de commande : <b>${cancelSuccessCount}</b> réussie(s)</td>
<td style="padding:0 0 35px 0;">Demandes d'annulations de commande : <b>${cancelSuccessCount}</b> réussie(s) :
<ul>
<#list cancelSuccesses as success>
<li>${success}</li>
</#list>
</ul>
</td>
</tr>
<#if cancelFailures?size gt 0 >
<tr>
@ -23,7 +29,13 @@
</#if>
<#if suspendSuccessCount??>
<tr>
<td style="padding:0 0 35px 0;">Demandes de suspension de commande : <b>${suspendSuccessCount}</b> réussie(s)</td>
<td style="padding:0 0 35px 0;">Demandes de suspension de commande : <b>${suspendSuccessCount}</b> réussie(s) :
<ul>
<#list suspendSuccesses as success>
<li>${success}</li>
</#list>
</ul>
</td>
</tr>
<#if suspendFailures?size gt 0 >
<tr>
@ -39,7 +51,13 @@
</#if>
<#if editSuccessCount??>
<tr>
<td style="padding:0 0 35px 0;">Demandes de modification d'informations de commande : <b>${editSuccessCount}</b> réussie(s)</td>
<td style="padding:0 0 35px 0;">Demandes de modification d'informations de commande : <b>${editSuccessCount}</b> réussie(s) :
<ul>
<#list editSuccesses as success>
<li>${success}</li>
</#list>
</ul>
</td>
</tr>
<#if editFailures?size gt 0 >
<tr>