mirror of https://github.com/jeecgboot/jeecg-boot
Merge pull request #95 from LQYBill/feat/orderManagementTriggerSync
Feat/order management trigger syncpull/8040/head
commit
7a773aa3dc
|
@ -20,6 +20,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import freemarker.template.Template;
|
import freemarker.template.Template;
|
||||||
import freemarker.template.TemplateException;
|
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.ChangeOrderResponse;
|
||||||
import org.jeecg.modules.business.domain.api.mabang.dochangeorder.ChangeWarehouseRequest;
|
import org.jeecg.modules.business.domain.api.mabang.dochangeorder.ChangeWarehouseRequest;
|
||||||
import org.jeecg.modules.business.domain.api.mabang.dochangeorder.ChangeWarehouseRequestBody;
|
import org.jeecg.modules.business.domain.api.mabang.dochangeorder.ChangeWarehouseRequestBody;
|
||||||
|
@ -385,7 +386,7 @@ public class PlatformOrderController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/orderManagement")
|
@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();
|
boolean isEmployee = securityService.checkIsEmployee();
|
||||||
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
|
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
|
||||||
Client client;
|
Client client;
|
||||||
|
@ -469,6 +470,9 @@ public class PlatformOrderController {
|
||||||
}
|
}
|
||||||
List<Order> mabangOrders = platformOrderMabangService.getOrdersFromMabang(requests, executor);
|
List<Order> mabangOrders = platformOrderMabangService.getOrdersFromMabang(requests, executor);
|
||||||
for(Order mabangOrder : mabangOrders) {
|
for(Order mabangOrder : mabangOrders) {
|
||||||
|
if(mabangOrder.getTrackingNumber() == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if(!mabangOrder.getTrackingNumber().isEmpty()) {
|
if(!mabangOrder.getTrackingNumber().isEmpty()) {
|
||||||
ordersWithTrackingNumber.add(mabangOrder);
|
ordersWithTrackingNumber.add(mabangOrder);
|
||||||
}
|
}
|
||||||
|
@ -511,14 +515,17 @@ public class PlatformOrderController {
|
||||||
templateModel.put("lastname", client.getSurname());
|
templateModel.put("lastname", client.getSurname());
|
||||||
if(cancelCount > 0) {
|
if(cancelCount > 0) {
|
||||||
templateModel.put("cancelSuccessCount", cancelResponses.getSuccesses().size() + "/" + cancelCount);
|
templateModel.put("cancelSuccessCount", cancelResponses.getSuccesses().size() + "/" + cancelCount);
|
||||||
|
templateModel.put("cancelSuccesses", cancelResponses.getSuccesses());
|
||||||
templateModel.put("cancelFailures", cancelResponses.getFailures());
|
templateModel.put("cancelFailures", cancelResponses.getFailures());
|
||||||
}
|
}
|
||||||
if(suspendCount > 0) {
|
if(suspendCount > 0) {
|
||||||
templateModel.put("suspendSuccessCount", suspendResponses.getSuccesses().size() + "/" + suspendCount);
|
templateModel.put("suspendSuccessCount", suspendResponses.getSuccesses().size() + "/" + suspendCount);
|
||||||
|
templateModel.put("suspendSuccesses", suspendResponses.getSuccesses());
|
||||||
templateModel.put("suspendFailures", suspendResponses.getFailures());
|
templateModel.put("suspendFailures", suspendResponses.getFailures());
|
||||||
}
|
}
|
||||||
if(editCount > 0) {
|
if(editCount > 0) {
|
||||||
templateModel.put("editSuccessCount", editResponses.getSuccesses().size() + "/" + editCount);
|
templateModel.put("editSuccessCount", editResponses.getSuccesses().size() + "/" + editCount);
|
||||||
|
templateModel.put("editSuccesses", editResponses.getSuccesses());
|
||||||
templateModel.put("editFailures", editResponses.getFailures());
|
templateModel.put("editFailures", editResponses.getFailures());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -539,6 +546,13 @@ public class PlatformOrderController {
|
||||||
throw new RuntimeException(e);
|
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);
|
return Result.OK(result);
|
||||||
}
|
}
|
||||||
@GetMapping("/recipientInfo")
|
@GetMapping("/recipientInfo")
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
package org.jeecg.modules.business.domain.job;
|
package org.jeecg.modules.business.domain.job;
|
||||||
|
|
||||||
import com.google.common.collect.Lists;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.codehaus.jettison.json.JSONArray;
|
import org.codehaus.jettison.json.JSONArray;
|
||||||
import org.codehaus.jettison.json.JSONException;
|
import org.codehaus.jettison.json.JSONException;
|
||||||
import org.codehaus.jettison.json.JSONObject;
|
import org.codehaus.jettison.json.JSONObject;
|
||||||
import org.jeecg.common.api.dto.message.TemplateMessageDTO;
|
import org.jeecg.common.api.dto.message.TemplateMessageDTO;
|
||||||
import org.jeecg.common.system.api.ISysBaseAPI;
|
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.jeecg.modules.business.service.IPlatformOrderMabangService;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.quartz.Job;
|
import org.quartz.Job;
|
||||||
|
@ -17,10 +15,6 @@ import org.quartz.JobExecutionException;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class MabangOrderSyncJob implements Job {
|
public class MabangOrderSyncJob implements Job {
|
||||||
|
@ -29,7 +23,6 @@ public class MabangOrderSyncJob implements Job {
|
||||||
private IPlatformOrderMabangService platformOrderMabangService;
|
private IPlatformOrderMabangService platformOrderMabangService;
|
||||||
@Autowired
|
@Autowired
|
||||||
private ISysBaseAPI ISysBaseApi;
|
private ISysBaseAPI ISysBaseApi;
|
||||||
private static final Integer DEFAULT_NUMBER_OF_THREADS = 10;
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(JobExecutionContext context) throws JobExecutionException {
|
public void execute(JobExecutionContext context) throws JobExecutionException {
|
||||||
JobDataMap jobDataMap = context.getMergedJobDataMap();
|
JobDataMap jobDataMap = context.getMergedJobDataMap();
|
||||||
|
@ -54,53 +47,32 @@ public class MabangOrderSyncJob implements Job {
|
||||||
throw new RuntimeException("PlatformOrder ID list can't be empty !");
|
throw new RuntimeException("PlatformOrder ID list can't be empty !");
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("Syncing following orders {}", platformOrderIds);
|
try {
|
||||||
List<List<String>> platformOrderIdLists = Lists.partition(platformOrderIds, 10);
|
JSONObject res = platformOrderMabangService.syncOrdersFromMabang(platformOrderIds);
|
||||||
List<OrderListRequestBody> requests = new ArrayList<>();
|
String syncedOrderNumber = String.valueOf(res.getInt("synced_order_number"));
|
||||||
for (List<String> platformOrderIdList : platformOrderIdLists) {
|
List<String> syncedOrderIds = new ArrayList<>();
|
||||||
requests.add(new OrderListRequestBody().setPlatformOrderIds(platformOrderIdList));
|
JSONArray syncedOrderIdsArray = res.getJSONArray("synced_order_ids");
|
||||||
}
|
for (int i = 0; i < syncedOrderIdsArray.length(); i++) {
|
||||||
List<Order> mabangOrders = new ArrayList<>();
|
syncedOrderIds.add(syncedOrderIdsArray.getString(i));
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
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
|
@NotNull
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package org.jeecg.modules.business.service;
|
package org.jeecg.modules.business.service;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.extension.service.IService;
|
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.Order;
|
||||||
import org.jeecg.modules.business.domain.api.mabang.getorderlist.OrderListRequestBody;
|
import org.jeecg.modules.business.domain.api.mabang.getorderlist.OrderListRequestBody;
|
||||||
import org.jeecg.modules.business.vo.PlatformOrderOperation;
|
import org.jeecg.modules.business.vo.PlatformOrderOperation;
|
||||||
|
@ -40,4 +42,6 @@ public interface IPlatformOrderMabangService extends IService<Order> {
|
||||||
void clearLogisticChannel(List<Order> orders, ExecutorService executor);
|
void clearLogisticChannel(List<Order> orders, ExecutorService executor);
|
||||||
|
|
||||||
String stripAccents(String input);
|
String stripAccents(String input);
|
||||||
|
|
||||||
|
JSONObject syncOrdersFromMabang(List<String> platformOrderIds) throws JSONException;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
package org.jeecg.modules.business.service.impl;
|
package org.jeecg.modules.business.service.impl;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.shiro.SecurityUtils;
|
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.common.system.vo.LoginUser;
|
||||||
import org.jeecg.modules.business.domain.api.mabang.dochangeorder.*;
|
import org.jeecg.modules.business.domain.api.mabang.dochangeorder.*;
|
||||||
import org.jeecg.modules.business.domain.api.mabang.getorderlist.*;
|
import org.jeecg.modules.business.domain.api.mabang.getorderlist.*;
|
||||||
|
@ -23,6 +27,7 @@ import java.text.Normalizer;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
@ -40,6 +45,8 @@ import static java.util.stream.Collectors.toList;
|
||||||
public class PlatformOrderMabangServiceImpl extends ServiceImpl<PlatformOrderMabangMapper, Order> implements IPlatformOrderMabangService {
|
public class PlatformOrderMabangServiceImpl extends ServiceImpl<PlatformOrderMabangMapper, Order> implements IPlatformOrderMabangService {
|
||||||
@Autowired
|
@Autowired
|
||||||
private PlatformOrderMabangMapper platformOrderMabangMapper;
|
private PlatformOrderMabangMapper platformOrderMabangMapper;
|
||||||
|
@Autowired
|
||||||
|
private ISysBaseAPI ISysBaseApi;
|
||||||
|
|
||||||
private static final Integer DEFAULT_NUMBER_OF_THREADS = 2;
|
private static final Integer DEFAULT_NUMBER_OF_THREADS = 2;
|
||||||
private static final Integer MABANG_API_RATE_LIMIT_PER_MINUTE = 10;
|
private static final Integer MABANG_API_RATE_LIMIT_PER_MINUTE = 10;
|
||||||
|
@ -324,4 +331,46 @@ public class PlatformOrderMabangServiceImpl extends ServiceImpl<PlatformOrderMab
|
||||||
input = input.replaceAll("[^\\p{ASCII}]", "");
|
input = input.replaceAll("[^\\p{ASCII}]", "");
|
||||||
return input;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,13 @@
|
||||||
</tr>
|
</tr>
|
||||||
<#if cancelSuccessCount??>
|
<#if cancelSuccessCount??>
|
||||||
<tr>
|
<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>
|
</tr>
|
||||||
<#if cancelFailures?size gt 0 >
|
<#if cancelFailures?size gt 0 >
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -23,7 +29,13 @@
|
||||||
</#if>
|
</#if>
|
||||||
<#if suspendSuccessCount??>
|
<#if suspendSuccessCount??>
|
||||||
<tr>
|
<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>
|
</tr>
|
||||||
<#if suspendFailures?size gt 0 >
|
<#if suspendFailures?size gt 0 >
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -39,7 +51,13 @@
|
||||||
</#if>
|
</#if>
|
||||||
<#if editSuccessCount??>
|
<#if editSuccessCount??>
|
||||||
<tr>
|
<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>
|
</tr>
|
||||||
<#if editFailures?size gt 0 >
|
<#if editFailures?size gt 0 >
|
||||||
<tr>
|
<tr>
|
||||||
|
|
Loading…
Reference in New Issue