feature : confirmed clients invoicing job

pull/6221/head
Gauthier LO 2023-11-15 15:31:26 +01:00
parent 6978554075
commit b718a23b9c
50 changed files with 1330 additions and 582 deletions

View File

@ -13,6 +13,7 @@ import org.jeecg.common.aspect.annotation.AutoLog;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.business.controller.UserException;
import org.jeecg.modules.business.domain.purchase.invoice.InvoiceData;
import org.jeecg.modules.business.entity.*;
import org.jeecg.modules.business.service.*;
@ -421,7 +422,7 @@ public class PurchaseOrderController {
*/
@AutoLog(value = "商品采购清单-通过客户id查询")
@RequestMapping(value = "/admin/loadInventory")
public Result<?> loadInventory(@RequestParam(name = "id") String clientId) {
public Result<?> loadInventory(@RequestParam(name = "id") String clientId) throws UserException {
Client client = clientService.getById(clientId);
ClientInfo clientInfo = new ClientInfo(client);
List<ImportedInventory> importedInventories = importedInventoryService.selectByClientId(clientId);

View File

@ -35,7 +35,8 @@ public class UserClientController {
public Result<?> getClientByUserId() {
LoginUser loginUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
String userId = loginUser.getId();
userId = "1708866308713140225";
// userId = "1708866308713140225"; //EP
// userId = "1721929497801580546"; //ND
Client client = userClientService.getClientByUserId(userId);
if(client == null) {
List<SysRole> sysRoles = sysUserRoleService.getUserRole(userId);

View File

@ -31,6 +31,7 @@ import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import javax.mail.Authenticator;
import javax.mail.MessagingException;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.servlet.http.HttpServletRequest;
@ -235,6 +236,8 @@ public class InvoiceController {
} catch (IOException | ParseException e) {
log.error(e.getMessage());
return Result.error("Sorry, server error, please try later");
} catch (MessagingException e) {
throw new RuntimeException(e);
}
}
@ -280,6 +283,8 @@ public class InvoiceController {
} catch (IOException | ParseException e) {
log.error(e.getMessage());
return Result.error("Sorry, server error, please try later");
} catch (MessagingException e) {
throw new RuntimeException(e);
}
}
@ -411,7 +416,7 @@ public class InvoiceController {
clientId = clientIDCodeMap.get(estimation.getCode());
}
else {
clientId = clientService.getClientByInternalCode(estimation.getCode());
clientId = clientService.getClientIdByCode(estimation.getCode());
clientIDCodeMap.put(estimation.getCode(), clientId);
}
if (estimation.getIsCompleteInvoice().equals("1")) {
@ -453,7 +458,7 @@ public class InvoiceController {
});
for(Map.Entry<String, List<ShippingFeesEstimation>> entry : estimationClientMap.entrySet()) {
String code = entry.getKey();
String clientId = clientService.getClientByInternalCode(code);
String clientId = clientService.getClientIdByCode(code);
List<String> shops = new ArrayList<>();
int ordersToProcess = 0;
int processedOrders = 0;

View File

@ -75,6 +75,8 @@ public class ShippingInvoiceController {
@Autowired
private IPlatformOrderService platformOrderService;
@Autowired
private IPurchaseOrderService purchaseOrderService;
@Autowired
private ISavRefundService savRefundService;
@Autowired
private IShippingInvoiceService shippingInvoiceService;
@ -510,6 +512,7 @@ public class ShippingInvoiceController {
log.info("Cancelling invoice number : {}", invoiceNumber);
platformOrderContentService.cancelInvoice(invoiceNumber);
platformOrderService.cancelInvoice(invoiceNumber);
purchaseOrderService.cancelInvoice(invoiceNumber);
savRefundService.cancelInvoice(invoiceNumber);
shippingInvoiceService.delMain(id);
log.info("Updating balance ...");
@ -571,6 +574,7 @@ public class ShippingInvoiceController {
public Result<?> cancelBatchInvoice(@RequestParam("ids") List<String> ids, @RequestParam("invoiceNumbers") List<String> invoiceNumbers, @RequestParam("clientIds") List<String> clientIds) {
log.info("Cancelling invoices : {}", invoiceNumbers);
purchaseOrderService.cancelBatchInvoice(invoiceNumbers);
platformOrderContentService.cancelBatchInvoice(invoiceNumbers);
platformOrderService.cancelBatchInvoice(invoiceNumbers);
savRefundService.cancelBatchInvoice(invoiceNumbers);

View File

@ -12,6 +12,7 @@ import org.jeecg.common.aspect.annotation.AutoLog;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.business.controller.UserException;
import org.jeecg.modules.business.entity.ClientPlatformOrderContent;
import org.jeecg.modules.business.entity.PlatformOrder;
import org.jeecg.modules.business.entity.PlatformOrderContent;
@ -192,7 +193,7 @@ public class ClientPlatformOrderController {
notes = "Compute order statistic data of platform orders indicated by its identifier."
)
@PostMapping(value = "/computeInfo", consumes = "application/json", produces = "application/json")
public Result<OrdersStatisticData> queryOrdersStatisticInfo(@RequestBody List<String> orderIds) {
public Result<OrdersStatisticData> queryOrdersStatisticInfo(@RequestBody List<String> orderIds) throws UserException {
log.info("Calculating statistic information for orders: {}", orderIds);
OrdersStatisticData ordersData = platformOrderService.getPlatformOrdersStatisticData(orderIds);
log.info("Got statistic information: {}", ordersData);
@ -212,7 +213,7 @@ public class ClientPlatformOrderController {
"client confirm information."
)
@PostMapping(value = "/placeOrder", consumes = "application/json", produces = "application/json")
public Result<PurchaseConfirmation> placeOrder(@RequestBody List<String> orderIds) {
public Result<PurchaseConfirmation> placeOrder(@RequestBody List<String> orderIds) throws UserException {
log.info("One client place a purchase order");
PurchaseConfirmation d = platformOrderService.confirmPurchaseByPlatformOrder(orderIds);
log.info(d.toString());
@ -226,7 +227,7 @@ public class ClientPlatformOrderController {
* @return confirmation.
*/
@PostMapping(value = "/adjustOrder", consumes = "application/json", produces = "application/json")
public Result<PurchaseConfirmation> adjustOrder(@RequestBody List<SkuQuantity> skuQuantities) {
public Result<PurchaseConfirmation> adjustOrder(@RequestBody List<SkuQuantity> skuQuantities) throws UserException {
log.info("One client adjust its purchase order");
log.info("Content: {}", skuQuantities);
PurchaseConfirmation d = platformOrderService.confirmPurchaseBySkuQuantity(skuQuantities);

View File

@ -6,6 +6,7 @@ import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.aspect.annotation.AutoLog;
import org.jeecg.modules.business.controller.UserException;
import org.jeecg.modules.business.controller.client.requestParams.PurchaseRequest;
import org.jeecg.modules.business.entity.PurchaseOrder;
import org.jeecg.modules.business.service.IPurchaseOrderService;
@ -64,7 +65,7 @@ public class ClientPurchaseController {
@AutoLog(value = "商品采购订单-添加")
@ApiOperation(value = "商品采购订单-添加", notes = "商品采购订单-添加")
@PostMapping(value = "/add")
public Result<String> addPurchaseOrder(@RequestBody PurchaseRequest purchaseRequest) {
public Result<String> addPurchaseOrder(@RequestBody PurchaseRequest purchaseRequest) throws UserException {
String id = purchaseOrderService.addPurchase(
purchaseRequest.getSkuQuantity(),
purchaseRequest.getPlatformOrderIDList()

View File

@ -3,7 +3,9 @@ package org.jeecg.modules.business.controller.client;
import cn.hutool.core.date.DateTime;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.api.dto.message.TemplateMessageDTO;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.api.ISysBaseAPI;
import org.jeecg.modules.business.domain.shippingInvoice.ShippingInvoiceFactory;
import org.jeecg.modules.business.entity.PlatformOrder;
import org.jeecg.modules.business.entity.PlatformOrderContent;
@ -13,6 +15,7 @@ import org.jeecg.modules.business.service.*;
import org.jeecg.modules.business.vo.Estimation;
import org.jeecg.modules.business.vo.ShippingFeesEstimation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@ -28,6 +31,8 @@ import java.util.stream.Collectors;
@RequestMapping("/transaction")
@Slf4j
public class TransactionController {
@Autowired
private EmailService emailService;
@Autowired
private TransactionMapper transactionMapper;
@Autowired
@ -68,6 +73,13 @@ public class TransactionController {
ISavRefundWithDetailService savRefundWithDetailService;
@Autowired
ISavRefundService savRefundService;
@Autowired
private ISysBaseAPI ISysBaseApi;
@Autowired
Environment env;
private final String SECTION_START = "<section><ul>";
private final String SECTION_END = "</ul></section>";
@GetMapping(value="/list")
public Result<?> list() {
return Result.ok(transactionMapper.list());
@ -91,7 +103,7 @@ public class TransactionController {
ShippingInvoiceFactory factory = new ShippingInvoiceFactory(
platformOrderService, clientMapper, shopMapper, logisticChannelMapper, logisticChannelPriceMapper,
platformOrderContentService, skuDeclaredValueService, countryService, exchangeRatesMapper,
purchaseOrderService, purchaseOrderContentMapper, skuPromotionHistoryMapper, savRefundService, savRefundWithDetailService);
purchaseOrderService, purchaseOrderContentMapper, skuPromotionHistoryMapper, savRefundService, savRefundWithDetailService, emailService, env);
List<ShippingFeesEstimation> shippingFeesEstimations = factory.getEstimations(clientId, orderIds, errorMessages);
if(shippingFeesEstimations.isEmpty())
return Result.OK("No estimation found.");
@ -131,6 +143,28 @@ public class TransactionController {
System.out.println("Purchase Fee " + currency + " : " + purchaseEstimation);
System.out.println("Shipping Fee " + currency + " : " + shippingFeesEstimation);
}
// system notification
String errors = SECTION_START;
int max_entries = 100;
int current_page = 0;
int total_page = (int) Math.ceil((double) errorMessages.size() /max_entries);
for(int i = 1; i <= errorMessages.size(); i++) {
if(i%max_entries == 1) {
errors = SECTION_START;
current_page++;
}
errors = errors.concat("<li>" + i + " : " + errorMessages.get(i-1) +"</li>");
if(i%max_entries==0 || i == errorMessages.size()) {
errors = errors.concat(SECTION_END);
Map<String, String> param = new HashMap<>();
param.put("nb_entries", String.valueOf(errorMessages.size()));
param.put("errors", errors);
param.put("current_page", String.valueOf(current_page));
param.put("total_page", String.valueOf(total_page));
TemplateMessageDTO message = new TemplateMessageDTO("admin", "Gauthier", "Expenses Overview Errors", param, "expenses_overview_errors");
ISysBaseApi.sendTemplateAnnouncement(message);
}
}
return Result.ok(new Estimation(shippingFeesEstimation, purchaseEstimation, currency, errorMessages, shopIds, new DateTime(startDate).toString(), new DateTime(endDate).toString(), isCompleteInvoiceReady));
}
}

View File

@ -0,0 +1,161 @@
package org.jeecg.modules.business.domain.job;
import freemarker.template.Template;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.modules.business.entity.Client;
import org.jeecg.modules.business.entity.SavRefundWithDetail;
import org.jeecg.modules.business.service.*;
import org.jeecg.modules.business.vo.BalanceData;
import org.jeecg.modules.business.vo.FactureDetail;
import org.jeecg.modules.business.vo.InvoiceMetaData;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import javax.mail.Authenticator;
import javax.mail.MessagingException;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import java.io.IOException;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.*;
import java.util.stream.Collectors;
@Slf4j
public class ConfirmedClientsInvoicingJob implements Job {
@Autowired
private IBalanceService balanceService;
@Autowired
private IClientService clientService;
@Autowired
private EmailService emailService;
@Autowired
private PlatformOrderShippingInvoiceService platformOrderShippingInvoiceService;
@Autowired
private ISavRefundWithDetailService savRefundWithDetailService;
@Autowired
Environment env;
@Autowired
private FreeMarkerConfigurer freemarkerConfigurer;
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
log.info("Confirmed clients Invoicing Job started ...");
List<Client> confirmedClients = clientService.getClientsByType("confirmed");
List<Client> clients = new ArrayList<>();
List<BalanceData> balanceDataList = new ArrayList<>();
List<BalanceData> shippingBalanceDataList = new ArrayList<>();
List<BalanceData> completeBalanceDataList = new ArrayList<>();
for(Client client : confirmedClients) {
BigDecimal balance = balanceService.getBalanceByClientIdAndCurrency(client.getId(), client.getCurrency());
if(balance.compareTo(BigDecimal.ZERO) > 0) {
clients.add(client);
balanceDataList.add(new BalanceData(client, client.getCurrency(), balance));
if(client.getIsCompleteInvoice().equals("0")) {
shippingBalanceDataList.add(new BalanceData(client, client.getCurrency(), balance));
}
else {
completeBalanceDataList.add(new BalanceData(client, client.getCurrency(), balance));
}
}
}
log.info("shipping clients list size : " + shippingBalanceDataList.size());
log.info("complete clients list size : " + completeBalanceDataList.size());
List<InvoiceMetaData> invoiceList = new ArrayList<>();
// we need to make sure that the client has enough balance to be invoiced
// step 1 : get the list of clients that have enough positive balance
// step 2 : calculer au pro rata le montant de la facture pour qu'elle ne dépasse pas le solde du client
// step 3 : update balance
// step 4 : generate invoice
// step 5 : send mail to client if balance is low
if(!shippingBalanceDataList.isEmpty()) {
log.info("Making shipping invoice for clients : {}", shippingBalanceDataList);
invoiceList = new ArrayList<>(platformOrderShippingInvoiceService.breakdownInvoiceClientByTypeAndBalance(shippingBalanceDataList, 0));
}
if(!completeBalanceDataList.isEmpty()) {
log.info("Making complete shipping invoice for clients : {}", completeBalanceDataList);
invoiceList.addAll(platformOrderShippingInvoiceService.breakdownInvoiceClientByTypeAndBalance(completeBalanceDataList, 1));
}
if(invoiceList.isEmpty()) {
log.info("Nothing to invoice.");
return;
}
List<InvoiceMetaData> metaDataErrorList = new ArrayList<>();
List<InvoiceMetaData> invoicedMetaDataList = new ArrayList<>(); // list of invoice metadata that has been invoiced
log.info("Generating detail files ...0/{}", invoiceList.size());
int cpt = 1;
for(InvoiceMetaData metaData: invoiceList){
if(metaData.getInvoiceCode().equals("error")) {
metaDataErrorList.add(metaData);
}
else {
invoicedMetaDataList.add(metaData);
List<FactureDetail> factureDetails = platformOrderShippingInvoiceService.getInvoiceDetail(metaData.getInvoiceCode());
List<SavRefundWithDetail> refunds = savRefundWithDetailService.getRefundsByInvoiceNumber(metaData.getInvoiceCode());
try {
platformOrderShippingInvoiceService.exportToExcel(factureDetails, refunds, metaData.getInvoiceCode(), metaData.getInvoiceEntity(), metaData.getInternalCode());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
log.info("Generating detail files ...{}/{}", cpt++, invoiceList.size());
}
if(!metaDataErrorList.isEmpty()) {
String subject = "[" + LocalDate.now() + "] Confirmed clients invoicing job report";
String destEmail = env.getProperty("spring.mail.username");
String templateName = "vipInvoicingJobReport.ftl";
Map<String, Object> templateModel = new HashMap<>();
templateModel.put("job", "Confirmed clients");
templateModel.put("errors", metaDataErrorList);
try {
emailService.newSendSimpleMessage(destEmail, subject, templateName, templateModel);
} catch (MessagingException e) {
throw new RuntimeException(e);
}
}
// emailing for low balance clients
List<BalanceData> lowBalanceDataList = balanceService.getLowBalanceClients(invoicedMetaDataList);
//todo : send mail to clients in prod
if(!lowBalanceDataList.isEmpty()) {
for(BalanceData data : lowBalanceDataList) {
log.info("Low balance client : {}", data.getClient().getInternalCode());
String subject = "[" + LocalDate.now() + "] WIA App low balance notification";
String destEmail = env.getProperty("spring.mail.username");
Properties prop = emailService.getMailSender();
Map<String, Object> templateModel = new HashMap<>();
templateModel.put("firstname", data.getClient().getFirstName());
templateModel.put("lastname", data.getClient().getSurname());
templateModel.put("balance", data.getBalance());
templateModel.put("currency", data.getCurrency());
templateModel.put("clientCategory", "confirmed");
Session session = Session.getInstance(prop, new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(env.getProperty("spring.mail.username"), env.getProperty("spring.mail.password"));
}
});
try {
freemarkerConfigurer = emailService.freemarkerClassLoaderConfig();
Template freemarkerTemplate = freemarkerConfigurer.getConfiguration()
.getTemplate("client/lowBalanceNotification.ftl");
String htmlBody = FreeMarkerTemplateUtils.processTemplateIntoString(freemarkerTemplate, templateModel);
emailService.sendSimpleMessage(destEmail, subject, htmlBody, session);
//todo : update balance_notification date and reason
log.info("Mail sent successfully !");
} catch (Exception e) {
log.error("Error while sending low balance notification mail in VipInvoicingJob : ", e);
e.printStackTrace();
}
}
}
log.info("Confirmed clients invoicing job finished.");
}
}

View File

@ -2,9 +2,9 @@ package org.jeecg.modules.business.domain.job;
import freemarker.template.Template;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
import org.jeecg.modules.business.entity.*;
import org.jeecg.modules.business.service.*;
import org.jeecg.modules.business.vo.BalanceData;
import org.jeecg.modules.business.vo.FactureDetail;
import org.jeecg.modules.business.vo.InvoiceMetaData;
import org.quartz.Job;
@ -19,12 +19,15 @@ import javax.mail.Authenticator;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import java.io.IOException;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.*;
import java.util.stream.Collectors;
@Slf4j
public class VipInvoicingJob implements Job {
@Autowired
private IBalanceService balanceService;
@Autowired
private IClientService clientService;
@Autowired
@ -40,7 +43,7 @@ public class VipInvoicingJob implements Job {
private FreeMarkerConfigurer freemarkerConfigurer;
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
log.info("VIP Invoicing Job executed.");
log.info("VIP Invoicing Job started ...");
List<Client> clients = clientService.getClientsByType("vip");
List<String> shippingClientIds = clients.stream().filter(client -> client.getIsCompleteInvoice().equals("0")).map(Client::getId).collect(Collectors.toList());
List<String> completeClientIds = clients.stream().filter(client -> client.getIsCompleteInvoice().equals("1")).map(Client::getId).collect(Collectors.toList());
@ -62,6 +65,7 @@ public class VipInvoicingJob implements Job {
}
List<InvoiceMetaData> metaDataErrorList = new ArrayList<>();
List<InvoiceMetaData> invoicedMetaDataList = new ArrayList<>(); // list of invoice meta data that has been invoiced
log.info("Generating detail files ...0/{}", invoiceList.size());
int cpt = 1;
for(InvoiceMetaData metaData: invoiceList){
@ -69,6 +73,7 @@ public class VipInvoicingJob implements Job {
metaDataErrorList.add(metaData);
}
else {
invoicedMetaDataList.add(metaData);
List<FactureDetail> factureDetails = platformOrderShippingInvoiceService.getInvoiceDetail(metaData.getInvoiceCode());
List<SavRefundWithDetail> refunds = savRefundWithDetailService.getRefundsByInvoiceNumber(metaData.getInvoiceCode());
try {
@ -84,6 +89,7 @@ public class VipInvoicingJob implements Job {
String destEmail = env.getProperty("spring.mail.username");
Properties prop = emailService.getMailSender();
Map<String, Object> templateModel = new HashMap<>();
templateModel.put("job", "VIP");
templateModel.put("errors", metaDataErrorList);
Session session = Session.getInstance(prop, new Authenticator() {
@ -104,6 +110,43 @@ public class VipInvoicingJob implements Job {
e.printStackTrace();
}
}
// emailing for low balance clients
List<BalanceData> lowBalanceDataList = balanceService.getLowBalanceClients(invoicedMetaDataList);
if(!lowBalanceDataList.isEmpty()) {
for(BalanceData data : lowBalanceDataList) {
log.info("Low balance client : {}", data.getClient().getInternalCode());
String subject = "[" + LocalDate.now() + "] WIA App low balance notification";
// TODO : change destEmail to client email for prod
String destEmail = env.getProperty("spring.mail.username");
Properties prop = emailService.getMailSender();
Map<String, Object> templateModel = new HashMap<>();
templateModel.put("firstname", data.getClient().getFirstName());
templateModel.put("lastname", data.getClient().getSurname());
templateModel.put("balance", data.getBalance());
templateModel.put("currency", data.getCurrency());
templateModel.put("clientCategory", "vip");
Session session = Session.getInstance(prop, new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(env.getProperty("spring.mail.username"), env.getProperty("spring.mail.password"));
}
});
try {
freemarkerConfigurer = emailService.freemarkerClassLoaderConfig();
Template freemarkerTemplate = freemarkerConfigurer.getConfiguration()
.getTemplate("client/lowBalanceNotification.ftl");
String htmlBody = FreeMarkerTemplateUtils.processTemplateIntoString(freemarkerTemplate, templateModel);
emailService.sendSimpleMessage(destEmail, subject, htmlBody, session);
//todo : update balance_notification date and reason
log.info("Mail sent successfully !");
} catch (Exception e) {
log.error("Error while sending low balance notification mail in VipInvoicingJob : ", e);
e.printStackTrace();
}
}
}
log.info("VIP invoicing job finished.");
}
}

View File

@ -4,6 +4,7 @@ import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.User;
import org.apache.commons.lang3.tuple.Pair;
import org.jeecg.modules.business.controller.UserException;
import org.jeecg.modules.business.domain.codeGeneration.CompleteInvoiceCodeRule;
@ -16,15 +17,20 @@ import org.jeecg.modules.business.vo.PromotionDetail;
import org.jeecg.modules.business.vo.ShippingFeesEstimation;
import org.jeecg.modules.business.vo.SkuQuantity;
import org.jeecg.modules.business.vo.SkuWeightDiscountServiceFees;
import org.jeecg.modules.business.vo.clientPlatformOrder.section.OrdersStatisticData;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import javax.mail.MessagingException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
@ -51,6 +57,8 @@ public class ShippingInvoiceFactory {
private final SkuPromotionHistoryMapper skuPromotionHistoryMapper;
private final ISavRefundService savRefundService;
private final ISavRefundWithDetailService savRefundWithDetailService;
private final EmailService emailService;
private final Environment env;
private final SimpleDateFormat SUBJECT_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
@ -78,7 +86,7 @@ public class ShippingInvoiceFactory {
ExchangeRatesMapper exchangeRatesMapper, IPurchaseOrderService purchaseOrderService,
PurchaseOrderContentMapper purchaseOrderContentMapper,
SkuPromotionHistoryMapper skuPromotionHistoryMapper, ISavRefundService savRefundService,
ISavRefundWithDetailService savRefundWithDetailService) {
ISavRefundWithDetailService savRefundWithDetailService, EmailService emailService, Environment env) {
this.platformOrderService = platformOrderService;
this.clientMapper = clientMapper;
this.shopMapper = shopMapper;
@ -93,6 +101,8 @@ public class ShippingInvoiceFactory {
this.skuPromotionHistoryMapper = skuPromotionHistoryMapper;
this.savRefundService = savRefundService;
this.savRefundWithDetailService = savRefundWithDetailService;
this.emailService = emailService;
this.env = env;
}
/**
@ -160,7 +170,7 @@ public class ShippingInvoiceFactory {
* channel price, this exception will be thrown.
*/
@Transactional
public CompleteInvoice createCompleteShippingInvoice(String username, String customerId, List<String> orderIds, String shippingMethod, String start, String end) throws UserException {
public CompleteInvoice createCompleteShippingInvoice(String username, String customerId, BigDecimal balance, List<String> orderIds, String shippingMethod, String start, String end) throws UserException, MessagingException {
log.info("Creating a complete invoice for \n client ID: {}, order IDs: {}]", customerId, orderIds);
// find orders and their contents of the invoice
Map<PlatformOrder, List<PlatformOrderContent>> uninvoicedOrderToContent = platformOrderService.fetchOrderData(orderIds);
@ -181,8 +191,9 @@ public class ShippingInvoiceFactory {
else if(shippingMethod.equals("all"))
subject = String.format("Purchase and Shipping fees, order time from %s to %s", start, end);
else throw new UserException("Couldn't create complete invoice for unknown shipping method");
return createInvoice(username, customerId, shopIds, uninvoicedOrderToContent, savRefunds, subject);
if(balance != null)
return createCompleteInvoiceWithBalance(username, customerId, balance, shopIds, uninvoicedOrderToContent, savRefunds, subject);
return createInvoice(username, customerId, null, shopIds, uninvoicedOrderToContent, savRefunds, subject);
}
@ -209,7 +220,7 @@ public class ShippingInvoiceFactory {
* channel price, this exception will be thrown.
*/
@Transactional
public CompleteInvoice createInvoice(String username, String customerId, List<String> shopIds,
public CompleteInvoice createInvoice(String username, String customerId, BigDecimal balance, List<String> shopIds,
Map<PlatformOrder, List<PlatformOrderContent>> orderAndContent,
List<SavRefundWithDetail> savRefunds, String subject) throws UserException {
Client client = clientMapper.selectById(customerId);
@ -235,10 +246,145 @@ public class ShippingInvoiceFactory {
shops.forEach(shop -> shopPackageMatFeeMap.put(shop.getId(), shop.getPackagingMaterialFee()));
String invoiceCode = generateCompleteInvoiceCode();
log.info("New invoice code: {}", invoiceCode);
calculateFees(logisticChannelMap, orderAndContent, channelPriceMap, countryList, skuRealWeights, skuServiceFees,
calculateFees(balance, logisticChannelMap, orderAndContent, channelPriceMap, countryList, skuRealWeights, skuServiceFees,
latestDeclaredValues, client, shopServiceFeeMap, shopPackageMatFeeMap, invoiceCode);
BigDecimal eurToUsd = exchangeRatesMapper.getLatestExchangeRate("EUR", "USD");
List<String> orderIds = orderAndContent.keySet().stream().map(PlatformOrder::getId).collect(toList());
List<SkuQuantity> skuQuantities = platformOrderContentService.searchOrderContent(orderIds);
String purchaseID = purchaseOrderService.addPurchase(username, client, invoiceCode, skuQuantities, orderAndContent);
List<PurchaseInvoiceEntry> purchaseOrderSkuList = purchaseOrderContentMapper.selectInvoiceDataByID(purchaseID);
List<PromotionDetail> promotionDetails = skuPromotionHistoryMapper.selectPromotionByPurchase(purchaseID);
if (savRefunds != null) {
updateSavRefundsInDb(savRefunds, invoiceCode);
}
updateOrdersAndContentsInDb(orderAndContent);
return new CompleteInvoice(client, invoiceCode, subject, orderAndContent, savRefunds,
purchaseOrderSkuList, promotionDetails, eurToUsd);
}
/**
* Creates a complete invoice for a client, based of its balance, a list of shops, a date range.
* <p>
* To generate an invoice, it
* <ol>
* <li>Search orders and their contents based on shop and date range</li>
* <li>Generate a new invoice code</li>
* <li>Find proper logistic channel price for each order </li>
* <li>Update prices of orders and their contents</li>
* <li>Generate a invoice</li>
* <li>Update invoiced their orders and contents to DB</li>
* </ol>
*
* @param username Current username
* @param customerId Customer ID
* @param balance Customer balance
* @param shopIds Shop IDs
* @param savRefunds List of SAV refunds
* @param subject Invoice subject
* @return the generated invoice
* @throws UserException if package used by the invoice can not or find more than 1 logistic
* channel price, this exception will be thrown.
*/
@Transactional
public CompleteInvoice createCompleteInvoiceWithBalance(String username, String customerId, BigDecimal balance, List<String> shopIds,
Map<PlatformOrder, List<PlatformOrderContent>> orderAndContent,
List<SavRefundWithDetail> savRefunds, String subject) throws UserException, MessagingException {
// sorting by order time
orderAndContent = orderAndContent.entrySet().stream().sorted(
Map.Entry.comparingByKey(Comparator.comparing(PlatformOrder::getOrderTime))
).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (oldValue, newValue) -> oldValue, LinkedHashMap::new));
Client client = clientMapper.selectById(customerId);
BigDecimal virtualBalance = balance;
Map<String, List<String>> ordersToRemove = new HashMap<>();
log.info("User {} is creating a complete invoice in {} order, for customer {}", username, client.getInternalCode(), client.getIsChronologicalOrder().equals("0") ? "first can invoice" : "chronological");
if (orderAndContent == null || orderAndContent.isEmpty()) {
throw new UserException("No platform order in the selected period!");
}
log.info("Orders to be invoiced: {}", orderAndContent);
Map<String, BigDecimal> skuRealWeights = new HashMap<>();
Map<String, BigDecimal> skuServiceFees = new HashMap<>();
skuDataPreparation(skuRealWeights, skuServiceFees);
List<Country> countryList = countryService.findAll();
Map<String, LogisticChannel> logisticChannelMap = logisticChannelMapper.getAll().stream()
.collect(toMap(LogisticChannel::getId, Function.identity()));
Map<LogisticChannel, List<LogisticChannelPrice>> channelPriceMap = getChannelPriceMap(logisticChannelMap, orderAndContent, true);
List<SkuDeclaredValue> latestDeclaredValues = skuDeclaredValueService.getLatestDeclaredValues();
List<Shop> shops = shopMapper.selectBatchIds(shopIds);
Map<String, BigDecimal> shopServiceFeeMap = new HashMap<>();
Map<String, BigDecimal> shopPackageMatFeeMap = new HashMap<>();
shops.forEach(shop -> shopServiceFeeMap.put(shop.getId(), shop.getOrderServiceFee()));
shops.forEach(shop -> shopPackageMatFeeMap.put(shop.getId(), shop.getPackagingMaterialFee()));
String invoiceCode = generateCompleteInvoiceCode();
log.info("New invoice code: {}", invoiceCode);
boolean skip = false;// isChronologicalOrder = 1 && insufficient balance => skip = true
for(Map.Entry<PlatformOrder, List<PlatformOrderContent>> entry : orderAndContent.entrySet()) {
if(skip) {
if(ordersToRemove.containsKey("skip"))
ordersToRemove.get("skip").add(entry.getKey().getPlatformOrderId());
else
ordersToRemove.put("skip", Collections.singletonList(entry.getKey().getPlatformOrderId()));
continue;
}
BigDecimal estimatedVirtualBalance;
try {
estimatedVirtualBalance = calculateFeeForOrder(username, virtualBalance, logisticChannelMap, entry.getKey(), entry.getValue(), channelPriceMap, countryList, skuRealWeights, skuServiceFees,
latestDeclaredValues, client, shopServiceFeeMap, shopPackageMatFeeMap, invoiceCode);
} catch (UserException e) {
log.error("Couldn't calculate fee for order {} !", entry.getKey().getId());
if(ordersToRemove.containsKey("error"))
ordersToRemove.get("error").add(entry.getKey().getPlatformOrderId());
else
ordersToRemove.put("error", Collections.singletonList(entry.getKey().getPlatformOrderId()));
continue;
}
if(estimatedVirtualBalance.compareTo(BigDecimal.ZERO) < 0) {
log.error("Not enough balance for order {} !", entry.getKey().getId());
if(ordersToRemove.containsKey("balance"))
ordersToRemove.get("balance").add(entry.getKey().getPlatformOrderId());
else
ordersToRemove.put("balance", Collections.singletonList(entry.getKey().getPlatformOrderId()));
if(client.getIsChronologicalOrder().equals("1"))
skip = true;
continue;
}
virtualBalance = estimatedVirtualBalance;
}
if(!ordersToRemove.isEmpty() && ordersToRemove.get("balance") != null) {
String emailSubject = "[" + LocalDate.now() + "] Rapport de facturation automatique WIA App";
String destEmail = env.getProperty("spring.mail.username");
String templateName = "client/confirmedClientsInvoicingJobReport.ftl";
Map<String, Object> templateModel = new HashMap<>();
templateModel.put("errors", ordersToRemove.get("balance"));
// templateModel.put("skipped", ordersToRemove.get("skip"));
templateModel.put("client", client);
templateModel.put("chronologicalOrder", client.getIsChronologicalOrder());
emailService.newSendSimpleMessage(destEmail, emailSubject, templateName, templateModel);
}
// removing orders that can't be invoiced
System.out.println("Orders and content size BEFORE : " + orderAndContent.size());
System.out.println("Orders to remove size : " + ordersToRemove.size());
for(Map.Entry<String, List<String>> entry : ordersToRemove.entrySet()) {
for(String platformOrderId : entry.getValue()) {
orderAndContent.keySet().removeIf(order -> order.getPlatformOrderId().equals(platformOrderId));
}
}
System.out.println("Orders and content size AFTER : " + orderAndContent.size());
BigDecimal eurToUsd = exchangeRatesMapper.getLatestExchangeRate("EUR", "USD");
if(orderAndContent.isEmpty()) {
log.error("No order was invoiced for customer {}, : Please check if the orders are in the correct erp status and if the customer has enough balance.", client.getInternalCode());
throw new UserException("No order was invoiced for customer " + client.getInternalCode() +
" : Please check if the orders are in the correct erp status and if the customer has enough balance.");
}
List<String> orderIds = orderAndContent.keySet().stream().map(PlatformOrder::getId).collect(toList());
List<SkuQuantity> skuQuantities = platformOrderContentService.searchOrderContent(orderIds);
@ -328,7 +474,7 @@ public class ShippingInvoiceFactory {
* channel price, this exception will be thrown.
*/
@Transactional
public ShippingInvoice createInvoice(String customerId, List<String> shopIds, Date begin, Date end, List<Integer> erpStatuses, List<String> warehouses) throws UserException {
public ShippingInvoice createInvoice(String customerId, List<String> shopIds, Date begin, Date end, List<Integer> erpStatuses, List<String> warehouses, BigDecimal balance) throws UserException {
log.info(
"Creating an invoice with arguments:\n client ID: {}, shop IDs: {}, period:[{} - {}]",
customerId, shopIds.toString(), begin, end
@ -345,7 +491,7 @@ public class ShippingInvoiceFactory {
);
uninvoicedOrderToContent = platformOrderService.findUninvoicedOrders(shopIds, begin, end, warehouses);
}
else if (erpStatuses.toString().equals("[1, 2]")) {
else if (erpStatuses.toString().equals("[1, 2]") || erpStatuses.toString().equals("[1]")) {
subject = String.format(
"Pre-Shipping fees order time from %s to %s",
SUBJECT_FORMAT.format(begin),
@ -361,6 +507,9 @@ public class ShippingInvoiceFactory {
);
uninvoicedOrderToContent = platformOrderService.findUninvoicedOrderContentsForShopsAndStatus(shopIds, begin, end, erpStatuses, warehouses);
}
if(balance != null) {
return createInvoiceWithBalance(customerId, balance, shopIds, uninvoicedOrderToContent, savRefunds, subject, false);
}
return createInvoice(customerId, shopIds, uninvoicedOrderToContent, savRefunds, subject, false);
}
@ -419,7 +568,7 @@ public class ShippingInvoiceFactory {
shops.forEach(shop -> shopPackageMatFeeMap.put(shop.getId(), shop.getPackagingMaterialFee()));
String invoiceCode = generateInvoiceCode();
log.info("New invoice code: {}", invoiceCode);
calculateFees(logisticChannelMap, orderAndContent, channelPriceMap, countryList, skuRealWeights, skuServiceFees,
calculateFees(null, logisticChannelMap, orderAndContent, channelPriceMap, countryList, skuRealWeights, skuServiceFees,
latestDeclaredValues, client, shopServiceFeeMap, shopPackageMatFeeMap, invoiceCode);
BigDecimal eurToUsd = exchangeRatesMapper.getLatestExchangeRate("EUR", "USD");
if (savRefunds != null) {
@ -429,6 +578,80 @@ public class ShippingInvoiceFactory {
updateOrdersAndContentsInDb(orderAndContent);
return invoice;
}
/**
* Creates an invoice based for a client, it's balance, a list of shops, a date range.
* <p>
* To generate an invoice, it
* <ol>
* <li>Search orders and their contents based on shop and date range</li>
* <li>Generate a new invoice code</li>
* <li>Find propre logistic channel price for each order </li>
* <li>Update prices of orders and their contents</li>
* <li>Generate a invoice</li>
* <li>Update invoiced their orders and contents to DB</li>
* </ol>
*
* @param customerId Customer ID
* @param balance Balance
* @param shopIds Shop IDs
* @param subject Invoice subject
* @param orderAndContent Map between PlatformOrder and their contents
* @param savRefunds List of SAV refunds
* @param skipShippingTimeComparing Skip comparing shipping time, true for Pre-shipping, false otherwise
* @return the generated invoice
* @throws UserException if package used by the invoice can not or find more than 1 logistic
* channel price, this exception will be thrown.
*/
@Transactional
public ShippingInvoice createInvoiceWithBalance(String customerId, BigDecimal balance, List<String> shopIds,
Map<PlatformOrder, List<PlatformOrderContent>> orderAndContent,
List<SavRefundWithDetail> savRefunds,
String subject, boolean skipShippingTimeComparing) throws UserException {
log.info("Orders to be invoiced: {}", orderAndContent);
if (orderAndContent == null || orderAndContent.isEmpty()) {
throw new UserException("No platform order in the selected period!");
}
// TODO : check why invoicing total changes everytime
Map<String, BigDecimal> skuRealWeights = new HashMap<>();
Map<String, BigDecimal> skuServiceFees = new HashMap<>();
skuDataPreparation(skuRealWeights, skuServiceFees);
List<Country> countryList = countryService.findAll();
Map<LogisticChannel, List<LogisticChannelPrice>> channelPriceMap;
Map<String, LogisticChannel> logisticChannelMap = logisticChannelMapper.getAll().stream()
.collect(toMap(LogisticChannel::getId, Function.identity()));
if(subject.contains("order time")) {
channelPriceMap = getChannelPriceMap(logisticChannelMap, orderAndContent, skipShippingTimeComparing, "order");
}
else {
channelPriceMap = getChannelPriceMap(logisticChannelMap, orderAndContent, skipShippingTimeComparing);
}
List<SkuDeclaredValue> latestDeclaredValues = skuDeclaredValueService.getLatestDeclaredValues();
Client client = clientMapper.selectById(customerId);
List<Shop> shops = shopMapper.selectBatchIds(shopIds);
Map<String, BigDecimal> shopServiceFeeMap = new HashMap<>();
Map<String, BigDecimal> shopPackageMatFeeMap = new HashMap<>();
shops.forEach(shop -> shopServiceFeeMap.put(shop.getId(), shop.getOrderServiceFee()));
shops.forEach(shop -> shopPackageMatFeeMap.put(shop.getId(), shop.getPackagingMaterialFee()));
String invoiceCode = generateInvoiceCode();
log.info("New invoice code: {}", invoiceCode);
System.out.println("Order and content size BEFORE : " + orderAndContent.size());
Map<String, List<String>> ordersWithPB = calculateFees(balance, logisticChannelMap, orderAndContent, channelPriceMap, countryList, skuRealWeights, skuServiceFees,
latestDeclaredValues, client, shopServiceFeeMap, shopPackageMatFeeMap, invoiceCode);
orderAndContent.entrySet().removeIf(entries -> ordersWithPB.containsKey(entries.getKey().getId()));
System.out.println("Order and content size AFTER : " + orderAndContent.size());
if(orderAndContent.isEmpty()) {
log.error("No order was invoiced for customer {} because : {}", client.getInternalCode(), ordersWithPB);
throw new UserException("Customer " + customerId + " errors : " + ordersWithPB);
}
BigDecimal eurToUsd = exchangeRatesMapper.getLatestExchangeRate("EUR", "USD");
if (savRefunds != null) {
updateSavRefundsInDb(savRefunds, invoiceCode);
}
ShippingInvoice invoice = new ShippingInvoice(client, invoiceCode, subject, orderAndContent, savRefunds, eurToUsd);
updateOrdersAndContentsInDb(orderAndContent);
return invoice;
}
/**
* Construct a map between LogisticChannel and LogisticChannelPrices, by using distinct country names and
@ -484,7 +707,7 @@ public class ShippingInvoiceFactory {
}
}
private Map<String, List<String>> calculateFees(Map<String, LogisticChannel> logisticChannelMap, Map<PlatformOrder, List<PlatformOrderContent>> orderAndContent,
private Map<String, List<String>> calculateFees(BigDecimal balance, Map<String, LogisticChannel> logisticChannelMap, Map<PlatformOrder, List<PlatformOrderContent>> orderAndContent,
Map<LogisticChannel, List<LogisticChannelPrice>> channelPriceMap,
List<Country> countryList,
Map<String, BigDecimal> skuRealWeights,
@ -496,30 +719,45 @@ public class ShippingInvoiceFactory {
String invoiceCode
) throws UserException {
Map<String, List<String>> platformOrderIdsWithPb = new HashMap<>();
// Virtual balance is only used for client type 1 in invoicing job
BigDecimal virtualBalance = balance;
List<PlatformOrder> insufficientBalanceOrders = new ArrayList<>();
boolean skip = false;
Map<PlatformOrder, List<PlatformOrderContent>> orderContentMap = new HashMap<>(orderAndContent);
orderContentMap = orderContentMap.entrySet().stream().sorted(
Map.Entry.comparingByKey(Comparator.comparing(PlatformOrder::getOrderTime))
).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (oldValue, newValue) -> oldValue, LinkedHashMap::new));
// find logistic channel price for each order based on its content
for (PlatformOrder uninvoicedOrder : orderAndContent.keySet()) {
List<PlatformOrderContent> contents = orderAndContent.get(uninvoicedOrder);
for (PlatformOrder uninvoicedOrder : orderContentMap.keySet()) {
if(skip) {
if(client.getInternalCode().equals("FT")) {
System.out.println("uninvoicedOrder order time : " + uninvoicedOrder.getOrderTime());
}
platformOrderIdsWithPb.put(uninvoicedOrder.getId(), Collections.singletonList("Skipped"));
continue;
}
List<PlatformOrderContent> contents = orderContentMap.get(uninvoicedOrder);
if (contents.isEmpty()) {
throw new UserException("Order: {} doesn't have content", uninvoicedOrder.getPlatformOrderId());
throw new UserException("Order: {} doesn't have content", uninvoicedOrder.getId());
}
log.info("Calculating price for {} of order {}", contents, uninvoicedOrder);
Map<String, Integer> contentMap = new HashMap<>();
Map<String, Integer> contentSkuQtyMap = new HashMap<>();
for (PlatformOrderContent content : contents) {
String skuId = content.getSkuId();
if (contentMap.containsKey(skuId)) {
contentMap.put(skuId, contentMap.get(skuId) + content.getQuantity());
if (contentSkuQtyMap.containsKey(skuId)) {
contentSkuQtyMap.put(skuId, contentSkuQtyMap.get(skuId) + content.getQuantity());
} else {
contentMap.put(skuId, content.getQuantity());
contentSkuQtyMap.put(skuId, content.getQuantity());
}
}
// calculate weight of an order
Pair<BigDecimal, List<String>> contentWeightResult = platformOrderContentService.calculateWeight(
contentMap,
contentSkuQtyMap,
skuRealWeights
);
if(!contentWeightResult.getValue().isEmpty()) {
platformOrderIdsWithPb.put(uninvoicedOrder.getPlatformOrderId(), contentWeightResult.getValue());
platformOrderIdsWithPb.put(uninvoicedOrder.getId(), contentWeightResult.getValue());
continue;
}
BigDecimal contentWeight = contentWeightResult.getKey();
@ -528,20 +766,16 @@ public class ShippingInvoiceFactory {
logisticChannelPair = findAppropriatePrice(countryList, logisticChannelMap,
channelPriceMap, uninvoicedOrder, contentWeight);
}
catch (UserException e) {
platformOrderIdsWithPb.put(uninvoicedOrder.getPlatformOrderId(), Collections.singletonList(e.getMessage()));
catch (RuntimeException | UserException e) {
platformOrderIdsWithPb.put(uninvoicedOrder.getId(), Collections.singletonList(e.getMessage()));
continue;
}
LogisticChannelPrice price = logisticChannelPair.getRight();
// update attributes of orders and theirs content
BigDecimal packageMatFee = shopPackageMatFeeMap.get(uninvoicedOrder.getShopId());
if(packageMatFee.compareTo(BigDecimal.ZERO) > 0 && logisticChannelPair.getLeft().getWarehouseInChina().equalsIgnoreCase("0")) {
uninvoicedOrder.setPackagingMaterialFee(packageMatFee);
}
uninvoicedOrder.setFretFee(price.getRegistrationFee());
uninvoicedOrder.setPickingFee(price.getAdditionalCost());
uninvoicedOrder.setOrderServiceFee(shopServiceFeeMap.get(uninvoicedOrder.getShopId()));
uninvoicedOrder.setShippingInvoiceNumber(invoiceCode);
BigDecimal fretFee = price.getRegistrationFee();
BigDecimal pickingFee = price.getAdditionalCost();
BigDecimal orderServiceFee = shopServiceFeeMap.get(uninvoicedOrder.getShopId());
BigDecimal totalShippingFee = price.calculateShippingPrice(contentWeight);
BigDecimal pickingFeePerItem = price.getPickingFeePerItem();
BigDecimal clientVatPercentage = client.getVatPercentage();
@ -560,6 +794,32 @@ public class ShippingInvoiceFactory {
if (vatApplicable && minimumDeclaredValue != null) {
totalVAT = calculateTotalVat(totalDeclaredValue, clientVatPercentage, minimumDeclaredValue);
}
if(virtualBalance != null){
virtualBalance = virtualBalance
.subtract(packageMatFee)
.subtract(fretFee)
.subtract(pickingFee)
.subtract(orderServiceFee)
.subtract(totalShippingFee)
.subtract(totalVAT);
if (virtualBalance.compareTo(BigDecimal.ZERO) < 0) {
if(client.getIsChronologicalOrder().equals("1")) {
skip = true;
}
insufficientBalanceOrders.add(uninvoicedOrder);
platformOrderIdsWithPb.put(uninvoicedOrder.getId(), Collections.singletonList("Insufficient balance, order was not invoiced."));
continue;
}
}
// update attributes of orders and theirs content
if(packageMatFee.compareTo(BigDecimal.ZERO) > 0 && logisticChannelPair.getLeft().getWarehouseInChina().equalsIgnoreCase("0")) {
uninvoicedOrder.setPackagingMaterialFee(packageMatFee);
}
uninvoicedOrder.setFretFee(fretFee);
uninvoicedOrder.setPickingFee(pickingFee);
uninvoicedOrder.setOrderServiceFee(orderServiceFee);
uninvoicedOrder.setShippingInvoiceNumber(invoiceCode);
// Since we always round up when distributing shipping fees to contents, sometimes the final total sum
// is bigger than initial total shipping fee, the remedy is to deduct from the initial total, so we never go
// above it
@ -570,9 +830,167 @@ public class ShippingInvoiceFactory {
vatApplicable, pickingFeePerItem, content, remainingShippingFee);
}
}
if(!insufficientBalanceOrders.isEmpty()) {
//send mail
String emailSubject = "[" + LocalDate.now() + "] Rapport de facturation automatique WIA App";
String destEmail = env.getProperty("spring.mail.username");
String templateName = "client/confirmedClientsInvoicingJobReport.ftl";
Map<String, Object> templateModel = new HashMap<>();
templateModel.put("errors", insufficientBalanceOrders);
templateModel.put("client", client);
templateModel.put("chronologicalOrder", client.getIsChronologicalOrder());
try {
emailService.newSendSimpleMessage(destEmail, emailSubject, templateName, templateModel);
} catch (MessagingException e) {
throw new RuntimeException(e);
}
}
// removing orders that can't be invoiced
log.info("Number of orders with problem for client {} : {}", client.getInternalCode(), platformOrderIdsWithPb.size());
platformOrderIdsWithPb.keySet().forEach(System.out::println);
return platformOrderIdsWithPb;
}
/**
* Calculates shipping fees of an order, updates order and contents
* and returns the estimated virtual balance
* if invoice is complete invoice, we make sure we have enough balance
* @param username
* @param balance virtual balance
* @param logisticChannelMap
* @param order
* @param contents
* @param channelPriceMap
* @param countryList
* @param skuRealWeights
* @param skuServiceFees
* @param latestDeclaredValues
* @param client
* @param shopServiceFeeMap
* @param shopPackageMatFeeMap
* @param invoiceCode
* @return
* @throws UserException
*/
private BigDecimal calculateFeeForOrder(String username,
BigDecimal balance,
Map<String, LogisticChannel> logisticChannelMap,
PlatformOrder order,
List<PlatformOrderContent> contents,
Map<LogisticChannel, List<LogisticChannelPrice>> channelPriceMap,
List<Country> countryList,
Map<String, BigDecimal> skuRealWeights,
Map<String, BigDecimal> skuServiceFees,
List<SkuDeclaredValue> latestDeclaredValues,
Client client,
Map<String, BigDecimal> shopServiceFeeMap,
Map<String, BigDecimal> shopPackageMatFeeMap,
String invoiceCode
) throws UserException {
// Virtual balance is only used for client type 1 in invoicing job
BigDecimal virtualBalance = balance;
// find logistic channel price for each order based on its content
if (contents.isEmpty()) {
throw new UserException("Order: {} doesn't have content", order.getPlatformOrderId());
}
log.info("Calculating price for {} of order {}", contents, order);
Map<String, Integer> contentSkuQtyMap = new HashMap<>();
for (PlatformOrderContent content : contents) {
String skuId = content.getSkuId();
if (contentSkuQtyMap.containsKey(skuId)) {
contentSkuQtyMap.put(skuId, contentSkuQtyMap.get(skuId) + content.getQuantity());
} else {
contentSkuQtyMap.put(skuId, content.getQuantity());
}
}
// calculate weight of an order
Pair<BigDecimal, List<String>> contentWeightResult = platformOrderContentService.calculateWeight(
contentSkuQtyMap,
skuRealWeights
);
if(!contentWeightResult.getValue().isEmpty()) {
return null;
}
BigDecimal contentWeight = contentWeightResult.getKey();
Pair<LogisticChannel, LogisticChannelPrice> logisticChannelPair;
try {
logisticChannelPair = findAppropriatePrice(countryList, logisticChannelMap,
channelPriceMap, order, contentWeight);
}
catch (UserException e) {
log.error(e.getMessage());
return null;
}
LogisticChannelPrice price = logisticChannelPair.getRight();
BigDecimal packageMatFee = shopPackageMatFeeMap.get(order.getShopId());
BigDecimal fretFee = price.getRegistrationFee();
BigDecimal pickingFee = price.getAdditionalCost();
BigDecimal orderServiceFee = shopServiceFeeMap.get(order.getShopId());
BigDecimal totalShippingFee = price.calculateShippingPrice(contentWeight);
BigDecimal pickingFeePerItem = price.getPickingFeePerItem();
BigDecimal clientVatPercentage = client.getVatPercentage();
Map<PlatformOrderContent, BigDecimal> contentDeclaredValueMap = new HashMap<>();
BigDecimal totalDeclaredValue = calculateTotalDeclaredValue(contents, contentDeclaredValueMap, latestDeclaredValues);
BigDecimal totalVAT = BigDecimal.ZERO;
boolean vatApplicable = clientVatPercentage.compareTo(BigDecimal.ZERO) > 0
&& EU_COUNTRY_LIST.contains(order.getCountry())
// If picking fee per item = 0, it means the package was sent from China so VAT applicable
&& price.getPickingFeePerItem().compareTo(BigDecimal.ZERO) == 0;
// In case where VAT is applicable, and the transport line has a minimum declared value (MDV) per PACKAGE
// We need to first calculate the total declared value and compare it to the MDV
// If the total declared value is below the MDV, then the VAT should be calculated with the MDV and
// then proportionally applied to each content
BigDecimal minimumDeclaredValue = price.getMinimumDeclaredValue();
if (vatApplicable && minimumDeclaredValue != null) {
totalVAT = calculateTotalVat(totalDeclaredValue, clientVatPercentage, minimumDeclaredValue);
}
if(virtualBalance != null){
virtualBalance = virtualBalance
.subtract(packageMatFee)
.subtract(fretFee)
.subtract(pickingFee)
.subtract(orderServiceFee)
.subtract(totalShippingFee)
.subtract(totalVAT);
if (virtualBalance.compareTo(BigDecimal.ZERO) < 0) {
return virtualBalance;
}
// if we are dealing with complete invoice, before inserting orders info
// we calcute purchase fee and make sure we have enough balance
if(invoiceCode.toCharArray()[8] == '7') {
BigDecimal eurToUsd = exchangeRatesMapper.getLatestExchangeRate("EUR", "USD");
List<SkuQuantity> skuQuantities = platformOrderContentService.searchOrderContent(Collections.singletonList(order.getId()));
List<OrderContentDetail> details = platformOrderService.searchPurchaseOrderDetail(skuQuantities);
OrdersStatisticData data = OrdersStatisticData.makeData(details, null);
BigDecimal purchaseFee = data.finalAmount();
virtualBalance = virtualBalance.subtract(purchaseFee.multiply(eurToUsd));
}
if (virtualBalance.compareTo(BigDecimal.ZERO) < 0) {
return virtualBalance;
}
}
// update attributes of orders and theirs content
if(packageMatFee.compareTo(BigDecimal.ZERO) > 0 && logisticChannelPair.getLeft().getWarehouseInChina().equalsIgnoreCase("0")) {
order.setPackagingMaterialFee(packageMatFee);
}
order.setFretFee(fretFee);
order.setPickingFee(pickingFee);
order.setOrderServiceFee(orderServiceFee);
order.setShippingInvoiceNumber(invoiceCode);
// Since we always round up when distributing shipping fees to contents, sometimes the final total sum
// is bigger than initial total shipping fee, the remedy is to deduct from the initial total, so we never go
// above it
BigDecimal remainingShippingFee = totalShippingFee;
for (PlatformOrderContent content : contents) {
remainingShippingFee = calculateAndUpdateContentFees(skuRealWeights, skuServiceFees, order, contentWeight,
totalShippingFee, clientVatPercentage, contentDeclaredValueMap, totalDeclaredValue, totalVAT,
vatApplicable, pickingFeePerItem, content, remainingShippingFee);
}
return virtualBalance;
}
private void updateOrdersAndContentsInDb(Map<PlatformOrder, List<PlatformOrderContent>> orderAndContent) {
// update them to DB after invoiced
platformOrderService.updateBatchById(orderAndContent.keySet());
@ -801,7 +1219,7 @@ public class ShippingInvoiceFactory {
shopPackageMatFeeMap.put(shop.getId(), shop.getPackagingMaterialFee());
Map<PlatformOrder, List<PlatformOrderContent>> orders = uninvoicedOrdersByShopId.get(shop.getId());
try {
Map<String, List<String>> orderIdErrorMap = calculateFees(logisticChannelMap, orders, channelPriceMap, countryList, skuRealWeights, skuServiceFees,
Map<String, List<String>> orderIdErrorMap = calculateFees(null, logisticChannelMap, orders, channelPriceMap, countryList, skuRealWeights, skuServiceFees,
latestDeclaredValues, client, shopServiceFeeMap,shopPackageMatFeeMap, null);
if(!orderIdErrorMap.isEmpty()) {
Map.Entry<String, List<String>> errorEntry = orderIdErrorMap.entrySet().iterator().next();
@ -858,7 +1276,6 @@ public class ShippingInvoiceFactory {
.collect(toMap(LogisticChannel::getId, Function.identity()));
Map<LogisticChannel, List<LogisticChannelPrice>> channelPriceMap = getChannelPriceMap(logisticChannelMap, ordersMap, true);
for (Shop shop : shops) {
Map<String, BigDecimal> shopServiceFeeMap = new HashMap<>();
Map<String, BigDecimal> shopPackageMatFeeMap = new HashMap<>();
@ -866,24 +1283,10 @@ public class ShippingInvoiceFactory {
shopPackageMatFeeMap.put(shop.getId(), shop.getPackagingMaterialFee());
Map<PlatformOrder, List<PlatformOrderContent>> orders = uninvoicedOrdersByShopId.get(shop.getId());
try {
Map<PlatformOrder, List<PlatformOrderContent>> ordersCopy = new HashMap<>(orders);
Map<String, List<String>> platformOrderIdErrorMap = calculateFees(logisticChannelMap, orders, channelPriceMap, countryList, skuRealWeights, skuServiceFees,
Map<String, List<String>> platformOrderIdErrorMap = calculateFees(null, logisticChannelMap, orders, channelPriceMap, countryList, skuRealWeights, skuServiceFees,
latestDeclaredValues, client, shopServiceFeeMap, shopPackageMatFeeMap, null);
System.out.println("Error List : " + platformOrderIdErrorMap);
for(Map.Entry<PlatformOrder,List<PlatformOrderContent>> entry : orders.entrySet()) {
for(Map.Entry<String, List<String>> errorEntry: platformOrderIdErrorMap.entrySet()) {
if(entry.getKey().getPlatformOrderId().equals(errorEntry.getKey())) {
errorMessages.addAll(errorEntry.getValue());
System.out.println("Error List size before : " + platformOrderIdErrorMap.size());
System.out.println("Platform Order Id to remove : " + errorEntry.getKey());
ordersCopy.remove(entry.getKey());
platformOrderIdErrorMap.remove(errorEntry.getKey());
System.out.println("Error List size after : " + platformOrderIdErrorMap.size());
break;
}
}
}
orders = ordersCopy;
platformOrderIdErrorMap.forEach((key, value) -> errorMessages.addAll(value));
orders.entrySet().removeIf(entries -> platformOrderIdErrorMap.containsKey(entries.getKey().getId()));
List<String> estimationsOrderIds = orders.keySet().stream().map(PlatformOrder::getId).collect(Collectors.toList());
BigDecimal eurToUsd = exchangeRatesMapper.getLatestExchangeRate("EUR", "USD");
ShippingInvoice invoice = new ShippingInvoice(client, "", "", orders, null, eurToUsd);

View File

@ -155,12 +155,6 @@ public class Client implements Serializable {
@Excel(name = "公司识别码数值", width = 15)
@ApiModelProperty(value = "公司识别码数值")
private String companyIdValue;
/**
*
*/
@Excel(name = "账户余额", width = 15)
@ApiModelProperty(value = "账户余额")
private BigDecimal balance;
/**
* IOSS
*/
@ -180,10 +174,32 @@ public class Client implements Serializable {
@Dict(dicCode = "yn")
@ApiModelProperty(value = "是否活跃")
private String active;
/**
*
* */
@Excel(name = "物流发票是否包含采购", width = 15)
@ApiModelProperty(value = "物流发票是否包含采购")
private java.lang.String isCompleteInvoice;
/**
* category id
* */
@Excel(name = "category id", width = 15, dictTable = "client_category", dicText = "name", dicCode = "id")
@Dict(dictTable = "client_category", dicText = "name", dicCode = "id")
@ApiModelProperty(value = "category id")
private java.lang.String clientCategoryId;
/**
* balance threshold before alert
* */
@Excel(name = "balance threshold before alert", width = 15)
@ApiModelProperty(value = "balance threshold before alert")
private java.math.BigDecimal balanceThreshold;
/**
* invoice in chronological order or first can invoice
* */
@Excel(name = "invoice in chronological order or first can invoice", width = 15)
@ApiModelProperty(value = "invoice in chronological order or first can invoice")
private java.lang.String isChronologicalOrder;
@Excel(name = "是否完整发票", width = 15)
@ApiModelProperty(value = "完整发票")
private String isCompleteInvoice;
public String fullName() {
return firstName + " " + surname;
}

View File

@ -60,4 +60,8 @@ public class ClientCategory implements Serializable {
@Excel(name = "description", width = 15)
@ApiModelProperty(value = "description")
private java.lang.String description;
/**balance threshold before alert*/
@Excel(name = "balance threshold before alert", width = 15)
@ApiModelProperty(value = "balance threshold before alert")
private java.math.BigDecimal balanceThreshold;
}

View File

@ -38,6 +38,9 @@ public class OrderContentDetail {
*/
public BigDecimal totalPrice() {
BigDecimal unit = skuDetail.getPrice().getPrice(quantity, exchangeRate);
if(unit == null) {
System.out.println("Unit is null : " + skuDetail.getSkuId() + "sku price: " + skuDetail.getPrice() + "quantity: " + quantity);
}
BigDecimal total = unit.multiply(new BigDecimal(quantity));
log.info("unit: {}", unit);
log.info("total: {}", total);

View File

@ -121,7 +121,7 @@ public class SkuPrice implements Serializable {
priceCandidate = priceRmb.divide(eurToRmb, RoundingMode.HALF_UP);
discountedPriceCandidate = discountedPriceRmb == null ? priceCandidate : discountedPriceRmb.divide(eurToRmb, RoundingMode.HALF_UP);
}
if (quantity >= threshold) {
if (threshold != null && quantity >= threshold) {
return discountedPriceCandidate;
}
return priceCandidate;

View File

@ -1,10 +1,12 @@
package org.jeecg.modules.business.mapper;
import java.math.BigDecimal;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import org.jeecg.modules.business.entity.ClientCategory;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.stereotype.Repository;
/**
* @Description: client category
@ -12,6 +14,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
* @Date: 2023-10-19
* @Version: V1.0
*/
@Repository
public interface ClientCategoryMapper extends BaseMapper<ClientCategory> {
BigDecimal getBalanceThresholdByCategoryId(@Param("id") String id);
}

View File

@ -19,7 +19,7 @@ public interface ClientMapper extends BaseMapper<Client> {
String getClientEntity(@Param("id") String id);
Map<String, String> getClientsEntity(@Param("ids") List<String> ids);
String getClientByInternalCode(@Param("code") String code);
String getClientIdByCode(@Param("code") String code);
List<Client> getClientByType(@Param("type") String type);
Client getClientByCode(@Param("code") String internalCode);
}

View File

@ -81,4 +81,5 @@ public interface PlatformOrderContentMapper extends BaseMapper<PlatformOrderCont
List<SkuPrice> searchSkuPrice(@Param("skuIds") List<String> skuIds);
void fetchHighestPriorityAttribute(PlatformOrderContent content);
}

View File

@ -176,7 +176,7 @@ public interface PlatformOrderMapper extends BaseMapper<PlatformOrder> {
@Param("shops") List<String> shops,
@Param("warehouses") List<String> warehouses);
List<PlatformOrder> fetchUninvoicedShippedOrderIDInShopsAndOrderTime(@Param("startDate") String startDate,
List<PlatformOrder> fetchUninvoicedOrderIDInShopsAndOrderTime(@Param("startDate") String startDate,
@Param("endDate") String endDate,
@Param("shops") List<String> shops,
@Param("erpStatuses") List<Integer> erpStatuses,
@ -192,4 +192,6 @@ public interface PlatformOrderMapper extends BaseMapper<PlatformOrder> {
List<PlatformOrder> fetchEmptyLogisticChannelOrders(@Param("startDate") String startDate,@Param("endDate") String endDate);
void updateErpStatusByCode(@Param("invoiceCode") String invoiceCode, @Param("erpStatus") int erpStatus);
}

View File

@ -91,4 +91,9 @@ public interface PurchaseOrderMapper extends BaseMapper<PurchaseOrder> {
String getInvoiceNumber(@Param("purchaseID") String purchaseID);
BigDecimal getPurchaseFeesByInvoiceCode(@Param("invoiceCode") String invoiceCode);
void deleteInvoice(@Param("invoiceNumber") String invoiceNumber);
void deleteBatchInvoice(@Param("invoiceNumbers") List<String> invoiceNumbers);
}

View File

@ -2,10 +2,7 @@ package org.jeecg.modules.business.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import org.jeecg.modules.business.entity.Client;
import org.jeecg.modules.business.entity.PlatformOrder;
import org.jeecg.modules.business.entity.PlatformOrderContent;
import org.jeecg.modules.business.entity.ShippingInvoice;
import org.jeecg.modules.business.entity.*;
import org.springframework.stereotype.Repository;
import java.util.List;
@ -23,4 +20,5 @@ public interface ShippingInvoiceMapper extends BaseMapper<ShippingInvoice> {
List<PlatformOrder> fetchPlatformOrder(@Param("invoiceNumber") String invoiceNumber);
List<PlatformOrderContent> fetchPlatformOrderContent(@Param("platformOrderId") String platformOrderId);
Client fetchShopOwnerFromInvoiceNumber(@Param("invoiceNumber") String invoiceNumber);
Currency fetchInvoiceCurrencyByCode(@Param("invoiceNumber") String invoiceCode);
}

View File

@ -1,5 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.jeecg.modules.demo.business.mapper.ClientCategoryMapper">
<mapper namespace="org.jeecg.modules.business.mapper.ClientCategoryMapper">
<select id="getBalanceThresholdByCategoryId" resultType="java.math.BigDecimal">
SELECT balance_threshold
FROM client_category
WHERE id = #{id}
</select>
</mapper>

View File

@ -14,10 +14,15 @@
#{id}
</foreach>;
</select>
<select id="getClientByInternalCode" parameterType="java.lang.String" resultType="java.lang.String">
<select id="getClientIdByCode" parameterType="java.lang.String" resultType="java.lang.String">
SELECT id
FROM client
WHERE internal_code = #{code}
</select>
<select id="getClientByCode" parameterType="java.lang.String" resultType="org.jeecg.modules.business.entity.Client">
SELECT *
FROM client
WHERE internal_code = #{code}
</select>
<select id="getClientByType" resultType="org.jeecg.modules.business.entity.Client">
SELECT c.*

View File

@ -295,18 +295,21 @@
</foreach>;
</insert>
<update id="cancelInvoice">
UPDATE platform_order_content
SET picking_fee = 0.0,
shipping_fee = NULL,
service_fee = NULL,
vat = NULL,
purchase_fee = 0.0,
erp_status =
CASE erp_status
WHEN '4' THEN '3'
ELSE erp_status
UPDATE platform_order_content poc
JOIN platform_order po ON poc.platform_order_id = po.id
JOIN shipping_invoice si ON po.shipping_invoice_number = si.invoice_number
SET poc.picking_fee = 0.0,
poc.shipping_fee = NULL,
poc.service_fee = NULL,
poc.vat = NULL,
poc.purchase_fee = 0.0,
poc.erp_status =
CASE
WHEN poc.erp_status = '4' THEN '3'
WHEN poc.erp_status = '2' AND si.create_by = 'system' THEN '1'
ELSE poc.erp_status
END
WHERE platform_order_id IN (SELECT id FROM platform_order WHERE shipping_invoice_number = #{invoiceNumber});
WHERE po.shipping_invoice_number = #{invoiceNumber};
</update>
<update id="cancelBatchInvoice">
UPDATE platform_order_content

View File

@ -88,6 +88,14 @@
WHERE id = #{orderID}
</update>
<update id="updateErpStatusByCode">
UPDATE platform_order po, platform_order_content poc
SET po.erp_status = #{erpStatus},
poc.erp_status = #{erpStatus}
WHERE po.shipping_invoice_number = #{invoiceCode}
AND poc.platform_order_id = po.id;
</update>
<update id="batchUpdateStatus">
UPDATE platform_order
SET status = #{status}
@ -488,7 +496,7 @@
AND po.shipping_time between #{startDate} AND #{endDate}
AND po.erp_status = 3;
</select>
<select id="fetchUninvoicedShippedOrderIDInShopsAndOrderTime" resultType="org.jeecg.modules.business.entity.PlatformOrder">
<select id="fetchUninvoicedOrderIDInShopsAndOrderTime" resultType="org.jeecg.modules.business.entity.PlatformOrder">
SELECT po.id
FROM platform_order po
JOIN logistic_channel lc ON po.logistic_channel_name = lc.zh_name
@ -643,15 +651,17 @@
</insert>
<update id="cancelInvoice">
UPDATE platform_order
UPDATE platform_order po
JOIN shipping_invoice si ON po.shipping_invoice_number = si.invoice_number
SET fret_fee = NULL,
order_service_fee = NULL,
shipping_invoice_number = NULL,
picking_fee = 0.0,
packaging_material_fee = 0.0,
erp_status =
CASE erp_status
WHEN '4' THEN '3'
CASE
WHEN erp_status = '4' THEN '3'
WHEN erp_status = '2' AND si.create_by = 'system' THEN '1'
ELSE erp_status
END
WHERE shipping_invoice_number = #{invoiceNumber};

View File

@ -63,4 +63,22 @@
FROM purchase_order
WHERE id = #{purchaseID}
</select>
<select id="getPurchaseFeesByInvoiceCode" resultType="java.math.BigDecimal">
SELECT final_amount
FROM purchase_order
WHERE invoice_number = #{invoiceCode};
</select>
<delete id="deleteInvoice">
DELETE
FROM purchase_order
WHERE invoice_number = #{invoiceNumber}
</delete>
<delete id="deleteBatchInvoice">
DELETE
FROM purchase_order
WHERE invoice_number IN
<foreach collection="invoiceNumbers" item="invoiceNumber" index="index" open="(" separator="," close=")">
#{invoiceNumber}
</foreach>
</delete>
</mapper>

View File

@ -29,4 +29,10 @@
FROM platform_order_content p
WHERE p.platform_order_id = #{platformOrderId}
</select>
<select id="fetchInvoiceCurrencyByCode" resultType="org.jeecg.modules.business.entity.Currency">
SELECT c.id, c.code
FROM shipping_invoice s
JOIN currency c ON s.currency_id = c.id
WHERE invoice_number = #{invoiceNumber}
</select>
</mapper>

View File

@ -5,10 +5,12 @@ import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import javax.mail.MessagingException;
import javax.mail.Session;
import java.io.IOException;
import java.util.Map;
import java.util.Properties;
public interface EmailService {
public void sendSimpleMessage(String recipient, String subject, String text, Session session) throws MessagingException;
public void newSendSimpleMessage(String recipient, String subject, String templateName, Map<String, Object> templateModel) throws MessagingException;
public void sendMessageWithAttachment(String recipient, String subject, String text, String attachment, Session session) throws MessagingException, IOException;
public Properties getMailSender();
public FreeMarkerConfigurer freemarkerClassLoaderConfig() throws IOException;

View File

@ -1,7 +1,10 @@
package org.jeecg.modules.business.service;
import org.apache.commons.lang3.tuple.Pair;
import org.jeecg.modules.business.entity.Balance;
import com.baomidou.mybatisplus.extension.service.IService;
import org.jeecg.modules.business.entity.Client;
import org.jeecg.modules.business.vo.BalanceData;
import org.jeecg.modules.business.vo.InvoiceMetaData;
import java.math.BigDecimal;
@ -37,4 +40,11 @@ public interface IBalanceService extends IService<Balance> {
void editBalance(String operationId, String operationType, String clientId, BigDecimal amount, String currencyId) throws Exception;
void deleteBatchBalance(List<String> operationIds, String operationType);
/**
* Get low balance clients from client list
* @param metaDataList list of meta data
* @return
*/
List<BalanceData> getLowBalanceClients(List<InvoiceMetaData> metaDataList);
}

View File

@ -40,7 +40,7 @@ public interface IClientService extends IService<Client> {
public void delBatchMain (Collection<? extends Serializable> idList);
public String getClientEntity(String id);
public Map<String, String> getClientsEntity(List<String> ids);
public String getClientByInternalCode(String code);
public String getClientIdByCode(String code);
/**
* Get current user's client information
* @return client or null if current user's role is not client

View File

@ -54,4 +54,5 @@ public interface IPlatformOrderContentService extends IService<PlatformOrderCont
* @param invoiceNumbers
*/
void cancelBatchInvoice(List<String> invoiceNumbers);
}

View File

@ -2,6 +2,7 @@ package org.jeecg.modules.business.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import org.jeecg.modules.business.controller.UserException;
import org.jeecg.modules.business.entity.*;
import org.jeecg.modules.business.vo.PlatformOrderQuantity;
import org.jeecg.modules.business.vo.SkuQuantity;
@ -54,19 +55,19 @@ public interface IPlatformOrderService extends IService<PlatformOrder> {
void processedPlatformOrderPage(IPage<ClientPlatformOrderPage> page);
OrdersStatisticData getPlatformOrdersStatisticData(List<String> orderIds);
OrdersStatisticData getPlatformOrdersStatisticData(List<String> orderIds) throws UserException;
List<PlatformOrderContent> selectByMainId(String mainId);
List<ClientPlatformOrderContent> selectClientVersionByMainId(String mainId);
PurchaseConfirmation confirmPurchaseByPlatformOrder(List<String> platformOrderIdList);
PurchaseConfirmation confirmPurchaseByPlatformOrder(List<String> platformOrderIdList) throws UserException;
PurchaseConfirmation confirmPurchaseBySkuQuantity(List<SkuQuantity> skuIDQuantityMap);
PurchaseConfirmation confirmPurchaseBySkuQuantity(List<SkuQuantity> skuIDQuantityMap) throws UserException;
PurchaseConfirmation confirmPurchaseBySkuQuantity(ClientInfo clientInfo, List<SkuQuantity> skuIDQuantityMap);
PurchaseConfirmation confirmPurchaseBySkuQuantity(ClientInfo clientInfo, List<SkuQuantity> skuIDQuantityMap) throws UserException;
List<OrderContentDetail> searchPurchaseOrderDetail(List<SkuQuantity> skuQuantities);
List<OrderContentDetail> searchPurchaseOrderDetail(List<SkuQuantity> skuQuantities) throws UserException;
OrderQuantity queryOrderQuantities();
@ -199,4 +200,5 @@ public interface IPlatformOrderService extends IService<PlatformOrder> {
* @return
*/
List<PlatformOrder> fetchEmptyLogisticChannelOrders(String startDate, String endDate);
}

View File

@ -2,6 +2,7 @@ package org.jeecg.modules.business.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import org.jeecg.modules.business.controller.UserException;
import org.jeecg.modules.business.domain.purchase.invoice.InvoiceData;
import org.jeecg.modules.business.entity.*;
import org.jeecg.modules.business.vo.SkuQuantity;
@ -10,6 +11,7 @@ import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.Serializable;
import java.math.BigDecimal;
import java.net.URISyntaxException;
import java.util.Collection;
import java.util.List;
@ -49,7 +51,7 @@ public interface IPurchaseOrderService extends IService<PurchaseOrder> {
*/
void setPageForCurrentClient(IPage<PurchaseOrder> page);
String addPurchase(List<SkuQuantity> skuQuantities);
String addPurchase(List<SkuQuantity> skuQuantities) throws UserException;
/**
* Add a new purchase. The purchase contains sku and its quantity indicated by
@ -65,10 +67,10 @@ public interface IPurchaseOrderService extends IService<PurchaseOrder> {
* @return the new purchase order identifier
*/
@Transactional
String addPurchase(List<SkuQuantity> SkuQuantity, List<String> orderIDs);
String addPurchase(List<SkuQuantity> SkuQuantity, List<String> orderIDs) throws UserException;
@Transactional
String addPurchase(String username, Client client, String invoiceNumber, List<SkuQuantity> skuQuantities, Map<PlatformOrder, List<PlatformOrderContent>> platformOrderIDs);
String addPurchase(String username, Client client, String invoiceNumber, List<SkuQuantity> skuQuantities, Map<PlatformOrder, List<PlatformOrderContent>> platformOrderIDs) throws UserException;
void savePaymentDocumentForPurchase(String purchaseID, MultipartFile in) throws IOException;
@ -105,4 +107,10 @@ public interface IPurchaseOrderService extends IService<PurchaseOrder> {
InvoiceData makeInvoice(String purchaseID) throws IOException, URISyntaxException;
byte[] getInvoiceByte(String invoiceCode) throws IOException;
BigDecimal getPurchaseFeesByInvoiceCode(String invoiceCode);
void cancelInvoice(String invoiceNumber);
void cancelBatchInvoice(List<String> invoiceNumbers);
}

View File

@ -1,9 +1,6 @@
package org.jeecg.modules.business.service;
import org.jeecg.modules.business.entity.Client;
import org.jeecg.modules.business.entity.PlatformOrder;
import org.jeecg.modules.business.entity.PlatformOrderContent;
import org.jeecg.modules.business.entity.ShippingInvoice;
import org.jeecg.modules.business.entity.*;
import com.baomidou.mybatisplus.extension.service.IService;
import java.io.Serializable;
@ -42,4 +39,5 @@ public interface IShippingInvoiceService extends IService<ShippingInvoice> {
public List<PlatformOrder> getPlatformOrder(String invoiceNumber);
public List<PlatformOrderContent> getPlatformOrderContent(String platformOrderId);
public Client getShopOwnerFromInvoiceNumber(String invoiceNumber);
Currency getInvoiceCurrencyByCode(String invoiceCode);
}

View File

@ -17,9 +17,11 @@ import org.jeecg.modules.business.vo.*;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.mail.MessagingException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
@ -52,6 +54,8 @@ public class PlatformOrderShippingInvoiceService {
@Autowired
ClientMapper clientMapper;
@Autowired
EmailService emailService;
@Autowired
ShopMapper shopMapper;
@Autowired
LogisticChannelPriceMapper logisticChannelPriceMapper;
@ -81,7 +85,8 @@ public class PlatformOrderShippingInvoiceService {
ISavRefundWithDetailService savRefundWithDetailService;
@Autowired
ISavRefundService savRefundService;
@Autowired
Environment env;
@Value("${jeecg.path.shippingTemplatePath_EU}")
private String SHIPPING_INVOICE_TEMPLATE_EU;
@ -140,6 +145,8 @@ public class PlatformOrderShippingInvoiceService {
}
public Period getValidOrderTimePeriod(List<String> shopIDs, List<Integer> erpStatuses) {
Date begin = platformOrderMapper.findEarliestUninvoicedPlatformOrderTime(shopIDs, erpStatuses);
if(begin == null)
return null;
ZoneId shanghai = ZoneId.of("Asia/Shanghai");
ZoneId paris = ZoneId.of("Europe/Paris");
LocalDateTime ldt = LocalDateTime.ofInstant(begin.toInstant(), shanghai);
@ -164,27 +171,21 @@ public class PlatformOrderShippingInvoiceService {
* @throws IOException exception related to invoice file IO.
*/
@Transactional
public InvoiceMetaData makeInvoice(ShippingInvoiceParam param) throws UserException, ParseException, IOException {
public InvoiceMetaData makeInvoice(ShippingInvoiceParam param, String ... user) throws UserException, ParseException, IOException {
// Creates factory
ShippingInvoiceFactory factory = new ShippingInvoiceFactory(
platformOrderService, clientMapper, shopMapper, logisticChannelMapper, logisticChannelPriceMapper,
platformOrderContentService, skuDeclaredValueService, countryService, exchangeRatesMapper,
purchaseOrderService, purchaseOrderContentMapper, skuPromotionHistoryMapper, savRefundService, savRefundWithDetailService);
Subject subject = null;
try {
subject = SecurityUtils.getSubject();
}
catch (Exception e) {
log.error("Error while getting subject", e);
}
String username = subject == null ? "admin" : ((LoginUser) subject.getPrincipal()).getUsername();
purchaseOrderService, purchaseOrderContentMapper, skuPromotionHistoryMapper, savRefundService, savRefundWithDetailService, emailService, env);
String username = user.length > 0 ? user[0] : ((LoginUser) SecurityUtils.getSubject().getPrincipal()).getUsername();
// Creates invoice by factory
ShippingInvoice invoice = factory.createInvoice(param.clientID(),
param.shopIDs(),
param.start(),
param.end(),
param.getErpStatuses(),
param.getWarehouses()
param.getWarehouses(),
param.getBalance()
);
// Chooses invoice template based on client's preference on currency
return getInvoiceMetaData(username, invoice);
@ -206,7 +207,7 @@ public class PlatformOrderShippingInvoiceService {
ShippingInvoiceFactory factory = new ShippingInvoiceFactory(
platformOrderService, clientMapper, shopMapper, logisticChannelMapper, logisticChannelPriceMapper,
platformOrderContentService, skuDeclaredValueService, countryService, exchangeRatesMapper,
purchaseOrderService, purchaseOrderContentMapper, skuPromotionHistoryMapper, savRefundService, savRefundWithDetailService);
purchaseOrderService, purchaseOrderContentMapper, skuPromotionHistoryMapper, savRefundService, savRefundWithDetailService, emailService, env);
String username = ((LoginUser) SecurityUtils.getSubject().getPrincipal()).getUsername();
// Creates invoice by factory
ShippingInvoice invoice = factory.createShippingInvoice(param.clientID(), param.orderIds(), param.getType(), param.getStart(), param.getEnd());
@ -224,15 +225,15 @@ public class PlatformOrderShippingInvoiceService {
* @throws IOException exception related to invoice file IO.
*/
@Transactional
public InvoiceMetaData makeCompleteInvoice(ShippingInvoiceOrderParam param) throws UserException, ParseException, IOException {
public InvoiceMetaData makeCompleteInvoice(ShippingInvoiceOrderParam param) throws UserException, ParseException, IOException, MessagingException {
// Creates factory
ShippingInvoiceFactory factory = new ShippingInvoiceFactory(
platformOrderService, clientMapper, shopMapper, logisticChannelMapper, logisticChannelPriceMapper,
platformOrderContentService, skuDeclaredValueService, countryService, exchangeRatesMapper,
purchaseOrderService, purchaseOrderContentMapper, skuPromotionHistoryMapper, savRefundService, savRefundWithDetailService);
purchaseOrderService, purchaseOrderContentMapper, skuPromotionHistoryMapper, savRefundService, savRefundWithDetailService, emailService, env);
String username = ((LoginUser) SecurityUtils.getSubject().getPrincipal()).getUsername();
// Creates invoice by factory
CompleteInvoice invoice = factory.createCompleteShippingInvoice(username, param.clientID(), param.orderIds(), param.getType(), param.getStart(), param.getEnd());
CompleteInvoice invoice = factory.createCompleteShippingInvoice(username, param.clientID(), null, param.orderIds(), param.getType(), param.getStart(), param.getEnd());
return getInvoiceMetaData(username, invoice);
}
@ -246,25 +247,25 @@ public class PlatformOrderShippingInvoiceService {
* @throws IOException
*/
@Transactional
public InvoiceMetaData makeCompleteInvoicePostShipping(ShippingInvoiceParam param, String method) throws UserException, ParseException, IOException {
public InvoiceMetaData makeCompleteInvoicePostShipping(ShippingInvoiceParam param, String method, String ... user) throws UserException, ParseException, IOException, MessagingException {
// Creates factory
ShippingInvoiceFactory factory = new ShippingInvoiceFactory(
platformOrderService, clientMapper, shopMapper, logisticChannelMapper, logisticChannelPriceMapper,
platformOrderContentService, skuDeclaredValueService, countryService, exchangeRatesMapper,
purchaseOrderService, purchaseOrderContentMapper, skuPromotionHistoryMapper, savRefundService, savRefundWithDetailService);
String username = ((LoginUser) SecurityUtils.getSubject().getPrincipal()).getUsername();
purchaseOrderService, purchaseOrderContentMapper, skuPromotionHistoryMapper, savRefundService, savRefundWithDetailService, emailService, env);
String username = user.length > 0 ? user[0] : ((LoginUser) SecurityUtils.getSubject().getPrincipal()).getUsername();
List<PlatformOrder> platformOrderList;
if(method.equals("post")) {
//On récupère les commandes entre 2 dates d'expédition avec un status 3
platformOrderList = platformOrderMapper.fetchUninvoicedShippedOrderIDInShops(param.getStart(), param.getEnd(), param.shopIDs(), param.getWarehouses());
} else {
// On récupère les commandes entre 2 dates de commandes avec un status (1,2) ou (1,2,3)
platformOrderList = platformOrderMapper.fetchUninvoicedShippedOrderIDInShopsAndOrderTime(param.getStart(), param.getEnd(), param.shopIDs(), param.getErpStatuses(), param.getWarehouses());
platformOrderList = platformOrderMapper.fetchUninvoicedOrderIDInShopsAndOrderTime(param.getStart(), param.getEnd(), param.shopIDs(), param.getErpStatuses(), param.getWarehouses());
}
// on récupère seulement les ID des commandes
List<String> orderIds = platformOrderList.stream().map(PlatformOrder::getId).collect(Collectors.toList());
// Creates invoice by factory
CompleteInvoice invoice = factory.createCompleteShippingInvoice(username, param.clientID(), orderIds, method, param.getStart(), param.getEnd());
CompleteInvoice invoice = factory.createCompleteShippingInvoice(username, param.clientID(), param.getBalance() ,orderIds, method, param.getStart(), param.getEnd());
return getInvoiceMetaData(username, invoice);
}
@NotNull
@ -316,7 +317,7 @@ public class PlatformOrderShippingInvoiceService {
ShippingInvoiceFactory factory = new ShippingInvoiceFactory(
platformOrderService, clientMapper, shopMapper, logisticChannelMapper, logisticChannelPriceMapper,
platformOrderContentService, skuDeclaredValueService, countryService, exchangeRatesMapper,
purchaseOrderService, purchaseOrderContentMapper, skuPromotionHistoryMapper, savRefundService, savRefundWithDetailService);
purchaseOrderService, purchaseOrderContentMapper, skuPromotionHistoryMapper, savRefundService, savRefundWithDetailService, emailService, env);
return factory.getEstimations(errorMessages);
}
@ -331,7 +332,7 @@ public class PlatformOrderShippingInvoiceService {
ShippingInvoiceFactory factory = new ShippingInvoiceFactory(
platformOrderService, clientMapper, shopMapper, logisticChannelMapper, logisticChannelPriceMapper,
platformOrderContentService, skuDeclaredValueService, countryService, exchangeRatesMapper,
purchaseOrderService, purchaseOrderContentMapper, skuPromotionHistoryMapper, savRefundService, savRefundWithDetailService);
purchaseOrderService, purchaseOrderContentMapper, skuPromotionHistoryMapper, savRefundService, savRefundWithDetailService, emailService, env);
return factory.getEstimations(clientId, orderIds, errorMessages);
}
@ -483,7 +484,7 @@ public class PlatformOrderShippingInvoiceService {
"\nclient : " + entry.getKey() +
"\nbetween dates : [" + start + "] --- [" + end + "]");
try {
ShippingInvoiceParam param = new ShippingInvoiceParam(entry.getKey(), entry.getValue(), start, end, Collections.singletonList(3), Arrays.asList("0", "1"));
ShippingInvoiceParam param = new ShippingInvoiceParam(entry.getKey(), null, entry.getValue(), start, end, Collections.singletonList(3), Arrays.asList("0", "1"));
InvoiceMetaData metaData;
if(invoiceType == 0) {
metaData = makeInvoice(param);
@ -498,6 +499,62 @@ public class PlatformOrderShippingInvoiceService {
String internalCode = clientMapper.selectById(entry.getKey()).getInternalCode();
invoiceList.add(new InvoiceMetaData("", "error", internalCode, entry.getKey(), e.getMessage()));
log.error(e.getMessage());
} catch (MessagingException e) {
throw new RuntimeException(e);
}
System.gc();
}
return invoiceList;
}
/**
* make shipping invoice by client and type (shipping or complete) and invoice only if client has sufficient balance
* @param balanceDataList list of balance data with client info, balance and currency
* @param invoiceType shipping invoice or complete invoice
* @return list of filename (invoices and details)
*/
@Transactional
public List<InvoiceMetaData> breakdownInvoiceClientByTypeAndBalance(List<BalanceData> balanceDataList, int invoiceType) {
Map<BalanceData, List<String>> clientShopIDsMap = new HashMap<>();
List<InvoiceMetaData> invoiceList = new ArrayList<>();
for(BalanceData data: balanceDataList) {
String id = data.getClient().getId();
clientShopIDsMap.put(data, shopService.listIdByClient(id));
}
for(Map.Entry<BalanceData, List<String>> entry: clientShopIDsMap.entrySet()) {
String clientId = entry.getKey().getClient().getId();
Period period = getValidOrderTimePeriod(entry.getValue(), Collections.singletonList(1));
if(period == null || !period.isValid()) {
String internalCode = entry.getKey().getClient().getInternalCode();
invoiceList.add(new InvoiceMetaData("", "error", internalCode, clientId, "No order to invoice."));
continue;
}
System.out.println("Period: [" + period.start() + " - " + period.end() + "]");
Calendar calendar = Calendar.getInstance();
calendar.setTime(period.start());
String start = calendar.get(Calendar.YEAR) + "-" + (calendar.get(Calendar.MONTH)+1 < 10 ? "0" : "") + (calendar.get(Calendar.MONTH)+1) + "-" + (calendar.get(Calendar.DAY_OF_MONTH) < 10 ? "0" : "") + (calendar.get(Calendar.DAY_OF_MONTH));
calendar.setTime(period.end());
String end = calendar.get(Calendar.YEAR) + "-" + (calendar.get(Calendar.MONTH)+1 < 10 ? "0" : "") + (calendar.get(Calendar.MONTH)+1) + "-" + (calendar.get(Calendar.DAY_OF_MONTH)+1 < 10 ? "0" : "") + (calendar.get(Calendar.DAY_OF_MONTH)+1);
log.info( "Invoicing : " + (invoiceType == 0 ? "Shipping Invoice" : "Complete Shipping Invoice") +
"\nclient : " + entry.getKey() +
"\nbetween dates : [" + start + "] --- [" + end + "]");
try {
ShippingInvoiceParam param = new ShippingInvoiceParam(clientId, entry.getKey().getBalance(), entry.getValue(), start, end, Collections.singletonList(1), Arrays.asList("0", "1"));
InvoiceMetaData metaData;
if(invoiceType == 0) {
metaData = makeInvoice(param, "system");
balanceService.updateBalance(clientId, metaData.getInvoiceCode(), "shipping");
}
else {
metaData = makeCompleteInvoicePostShipping(param, "pre-shipping", "system");
balanceService.updateBalance(clientId, metaData.getInvoiceCode(), "complete");
}
platformOrderMapper.updateErpStatusByCode(metaData.getInvoiceCode(), 2);
invoiceList.add(metaData);
} catch (UserException | IOException | ParseException | MessagingException e) {
String internalCode = entry.getKey().getClient().getInternalCode();
invoiceList.add(new InvoiceMetaData("", "error", internalCode, clientId, e.getMessage()));
log.error(e.getMessage());
}
System.gc();
}

View File

@ -1,15 +1,14 @@
package org.jeecg.modules.business.service.impl;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.modules.business.entity.Balance;
import org.jeecg.modules.business.entity.PlatformOrder;
import org.jeecg.modules.business.entity.PlatformOrderContent;
import org.jeecg.modules.business.entity.ShippingInvoice;
import org.apache.commons.lang3.tuple.Pair;
import org.jeecg.modules.business.entity.*;
import org.jeecg.modules.business.mapper.BalanceMapper;
import org.jeecg.modules.business.service.IBalanceService;
import org.jeecg.modules.business.service.ICurrencyService;
import org.jeecg.modules.business.service.IPlatformOrderService;
import org.jeecg.modules.business.service.IShippingInvoiceService;
import org.jeecg.modules.business.mapper.ClientCategoryMapper;
import org.jeecg.modules.business.mapper.ClientMapper;
import org.jeecg.modules.business.service.*;
import org.jeecg.modules.business.vo.BalanceData;
import org.jeecg.modules.business.vo.InvoiceMetaData;
import org.jeecg.modules.system.entity.SysUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@ -17,6 +16,7 @@ import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@ -33,11 +33,17 @@ public class BalanceServiceImpl extends ServiceImpl<BalanceMapper, Balance> impl
@Autowired
private BalanceMapper balanceMapper;
@Autowired
private ClientCategoryMapper clientCategoryMapper;
@Autowired
private ClientMapper clientMapper;
@Autowired
private ICurrencyService currencyService;
@Autowired
private IPlatformOrderService platformOrderService;
@Autowired
IShippingInvoiceService iShippingInvoiceService;
private IPurchaseOrderService purchaseOrderService;
@Autowired
IShippingInvoiceService shippingInvoiceService;
@Override
public BigDecimal getBalanceByClientIdAndCurrency(String clientId, String currency) {
return balanceMapper.getBalanceByClientIdAndCurrency(clientId, currency);
@ -47,20 +53,13 @@ public class BalanceServiceImpl extends ServiceImpl<BalanceMapper, Balance> impl
public void updateBalance(String clientId, String invoiceCode, String invoiceType) {
// balance update
ShippingInvoice invoice = iShippingInvoiceService.getShippingInvoice(invoiceCode);
ShippingInvoice invoice = shippingInvoiceService.getShippingInvoice(invoiceCode);
String currency = currencyService.getCodeById(invoice.getCurrencyId());
BigDecimal previousBalance = getBalanceByClientIdAndCurrency(clientId, currency);
BigDecimal currentBalance = previousBalance.subtract(invoice.getFinalAmount());
if(invoiceType.equals("complete")) {
List<String> orderIds = iShippingInvoiceService.getPlatformOrder(invoiceCode).stream().map(PlatformOrder::getId).collect(Collectors.toList());
Map<PlatformOrder, List<PlatformOrderContent>> orderMap = platformOrderService.fetchOrderData(orderIds);
BigDecimal purchaseFees = BigDecimal.ZERO;
for(Map.Entry<PlatformOrder, List<PlatformOrderContent>> entry : orderMap.entrySet()) {
for(PlatformOrderContent content : entry.getValue()) {
purchaseFees = purchaseFees.add(content.getPurchaseFee());
}
}
currentBalance = currentBalance.add(purchaseFees);
BigDecimal purchaseFees = purchaseOrderService.getPurchaseFeesByInvoiceCode(invoiceCode);
currentBalance = currentBalance.subtract(purchaseFees);
}
SysUser sysUser = new SysUser();
Balance balance = Balance.of(sysUser.getUsername(), clientId, invoice.getCurrencyId(), Balance.OperationType.Debit.name(), invoice.getId(), currentBalance);
@ -86,6 +85,7 @@ public class BalanceServiceImpl extends ServiceImpl<BalanceMapper, Balance> impl
public void deleteBatchBalance(List<String> operationIds, String operationType) {
balanceMapper.deleteBatchBalance(operationIds, operationType);
}
@Override
public void editBalance(String operationId, String operationType, String clientId, BigDecimal amount, String currencyId) throws Exception {
log.info("editing balance");
@ -105,4 +105,20 @@ public class BalanceServiceImpl extends ServiceImpl<BalanceMapper, Balance> impl
balanceMapper.insert(newBalance);
}
@Override
public List<BalanceData> getLowBalanceClients(List<InvoiceMetaData> metaDataList) {
List<BalanceData> lowBalanceDataList = new ArrayList<>();
for(InvoiceMetaData metaData : metaDataList) {
Client client = clientMapper.getClientByCode(metaData.getInternalCode());
Currency currency = shippingInvoiceService.getInvoiceCurrencyByCode(metaData.getInvoiceCode());
BigDecimal balance = getBalanceByClientIdAndCurrency(client.getId(), currency.getCode());
BigDecimal balanceThreshold = client.getBalanceThreshold() == null ?
clientCategoryMapper.getBalanceThresholdByCategoryId(client.getClientCategoryId()) : client.getBalanceThreshold();
if(balance.compareTo(balanceThreshold) < 0) {
lowBalanceDataList.add(new BalanceData(client, currency.getCode(), balance));
}
}
return lowBalanceDataList;
}
}

View File

@ -133,7 +133,7 @@ public class ClientServiceImpl extends ServiceImpl<ClientMapper, Client> impleme
}
@Override
public String getClientByInternalCode(String code) {
return clientMapper.getClientByInternalCode(code);
public String getClientIdByCode(String code) {
return clientMapper.getClientIdByCode(code);
}
}

View File

@ -3,25 +3,32 @@ package org.jeecg.modules.business.service.impl;
import freemarker.cache.FileTemplateLoader;
import freemarker.cache.TemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.modules.business.service.EmailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import javax.mail.*;
import javax.mail.internet.*;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
@Slf4j
@Service
public class EmailServiceImpl implements EmailService {
@Autowired
Environment env;
@Autowired
FreeMarkerConfigurer freemarkerConfigurer;
@Override
@Transactional
public Properties getMailSender() {
@ -49,6 +56,41 @@ public class EmailServiceImpl implements EmailService {
Transport.send(message);
}
@Override
@Transactional
public void newSendSimpleMessage(String recipient, String subject, String templateName, Map<String, Object> templateModel) {
Properties prop = getMailSender();
Session session = Session.getInstance(prop, new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(env.getProperty("spring.mail.username"), env.getProperty("spring.mail.password"));
}
});
try {
freemarkerConfigurer = freemarkerClassLoaderConfig();
Template freemarkerTemplate = freemarkerConfigurer.getConfiguration()
.getTemplate(templateName);
String htmlBody = FreeMarkerTemplateUtils.processTemplateIntoString(freemarkerTemplate, templateModel);
Message message = new MimeMessage(session);
message.setFrom(new InternetAddress(Objects.requireNonNull(env.getProperty("spring.mail.username"))));
message.setRecipient(Message.RecipientType.TO, InternetAddress.parse(recipient)[0]);
if(!recipient.equals(env.getProperty("spring.mail.username")))
message.setRecipient(Message.RecipientType.CC, InternetAddress.parse(Objects.requireNonNull(env.getProperty("spring.mail.username")))[0]);
message.setSubject(subject);
message.setContent(htmlBody, "text/html; charset=utf-8");
Transport.send(message);
log.info("Mail sent successfully");
} catch (Exception e) {
log.error("Error while sending mail in VipInvoicingJob", e);
e.printStackTrace();
}
}
@Override
@Transactional
public void sendMessageWithAttachment(String recipient, String subject, String text, String attachment, Session session) throws MessagingException, IOException {

View File

@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.modules.business.controller.UserException;
import org.jeecg.modules.business.entity.*;
import org.jeecg.modules.business.mapper.ExchangeRatesMapper;
import org.jeecg.modules.business.mapper.PlatformOrderContentMapper;
@ -13,6 +14,7 @@ import org.jeecg.modules.business.service.IClientService;
import org.jeecg.modules.business.service.IPlatformOrderService;
import org.jeecg.modules.business.service.IShippingFeesWaiverProductService;
import org.jeecg.modules.business.vo.PlatformOrderQuantity;
import org.jeecg.modules.business.vo.SkuDetail;
import org.jeecg.modules.business.vo.SkuQuantity;
import org.jeecg.modules.business.vo.SkuShippingFeesWaiver;
import org.jeecg.modules.business.vo.clientPlatformOrder.ClientPlatformOrderPage;
@ -182,7 +184,7 @@ public class PlatformOrderServiceImpl extends ServiceImpl<PlatformOrderMapper, P
}
@Override
public OrdersStatisticData getPlatformOrdersStatisticData(List<String> orderIds) {
public OrdersStatisticData getPlatformOrdersStatisticData(List<String> orderIds) throws UserException {
List<SkuQuantity> skuIDQuantityMap = platformOrderContentMap.searchOrderContent(orderIds);
List<OrderContentDetail> data = searchPurchaseOrderDetail(skuIDQuantityMap);
return OrdersStatisticData.makeData(data, null);
@ -200,14 +202,14 @@ public class PlatformOrderServiceImpl extends ServiceImpl<PlatformOrderMapper, P
}
@Override
public PurchaseConfirmation confirmPurchaseByPlatformOrder(List<String> platformOrderIdList) {
public PurchaseConfirmation confirmPurchaseByPlatformOrder(List<String> platformOrderIdList) throws UserException {
List<SkuQuantity> skuIDQuantityMap = platformOrderContentMap.searchOrderContent(platformOrderIdList);
return confirmPurchaseBySkuQuantity(skuIDQuantityMap);
}
@Override
public PurchaseConfirmation confirmPurchaseBySkuQuantity(List<SkuQuantity> skuIDQuantityMap) {
public PurchaseConfirmation confirmPurchaseBySkuQuantity(List<SkuQuantity> skuIDQuantityMap) throws UserException {
Client client = clientService.getCurrentClient();
ClientInfo clientInfo = new ClientInfo(client);
return new PurchaseConfirmation(clientInfo, searchPurchaseOrderDetail(skuIDQuantityMap),
@ -215,13 +217,13 @@ public class PlatformOrderServiceImpl extends ServiceImpl<PlatformOrderMapper, P
}
@Override
public PurchaseConfirmation confirmPurchaseBySkuQuantity(ClientInfo clientInfo, List<SkuQuantity> skuIDQuantityMap) {
public PurchaseConfirmation confirmPurchaseBySkuQuantity(ClientInfo clientInfo, List<SkuQuantity> skuIDQuantityMap) throws UserException {
return new PurchaseConfirmation(clientInfo, searchPurchaseOrderDetail(skuIDQuantityMap),
getShippingFeesWaiverMap(skuIDQuantityMap.stream().map(SkuQuantity::getID).collect(toList())));
}
@Override
public List<OrderContentDetail> searchPurchaseOrderDetail(List<SkuQuantity> skuQuantities) {
public List<OrderContentDetail> searchPurchaseOrderDetail(List<SkuQuantity> skuQuantities) throws UserException {
BigDecimal eurToRmb = exchangeRatesMapper.getLatestExchangeRate("EUR", "RMB");
// convert list of (ID, quantity) to map between ID and quantity
Map<String, Integer> skuQuantity =
@ -236,7 +238,14 @@ public class PlatformOrderServiceImpl extends ServiceImpl<PlatformOrderMapper, P
// Get list of sku ID
List<String> skuList = new ArrayList<>(skuQuantity.keySet());
List<OrderContentDetail> details = platformOrderContentMap.searchSkuDetail(skuList).stream()
List<SkuDetail> skuDetails = platformOrderContentMap.searchSkuDetail(skuList);
for(SkuDetail detail : skuDetails) {
if(detail.getPrice().getId() == null || detail.getPrice().getPrice() == null) {
throw new UserException("SKU " + detail.getSkuId() + " has no price or price id");
}
}
System.out.println("Breakpoint");
List<OrderContentDetail> details = skuDetails.stream()
.map(
skuDetail -> new OrderContentDetail(
skuDetail,

View File

@ -2,10 +2,7 @@ package org.jeecg.modules.business.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.modules.business.entity.Client;
import org.jeecg.modules.business.entity.PlatformOrder;
import org.jeecg.modules.business.entity.PlatformOrderContent;
import org.jeecg.modules.business.entity.ShippingInvoice;
import org.jeecg.modules.business.entity.*;
import org.jeecg.modules.business.mapper.ShippingInvoiceMapper;
import org.jeecg.modules.business.service.IShippingInvoiceService;
import org.springframework.beans.factory.annotation.Autowired;
@ -70,6 +67,11 @@ public class ShippingInvoiceServiceImpl extends ServiceImpl<ShippingInvoiceMappe
return shippingInvoiceMapper.fetchShopOwnerFromInvoiceNumber(invoiceNumber);
}
@Override
public Currency getInvoiceCurrencyByCode(String invoiceCode) {
return shippingInvoiceMapper.fetchInvoiceCurrencyByCode(invoiceCode);
}
@Override
@Transactional
public String getShippingInvoiceId(String invoiceNumber) {

View File

@ -2,6 +2,7 @@ package org.jeecg.modules.business.service.impl.purchase;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.jeecg.modules.business.controller.UserException;
import org.jeecg.modules.business.domain.codeGeneration.PurchaseInvoiceCodeRule;
import org.jeecg.modules.business.domain.purchase.invoice.InvoiceData;
import org.jeecg.modules.business.domain.purchase.invoice.PurchaseInvoice;
@ -212,7 +213,7 @@ public class PurchaseOrderServiceImpl extends ServiceImpl<PurchaseOrderMapper, P
* @return the purchase order's identifier (UUID)
*/
@Override
public String addPurchase(List<SkuQuantity> skuQuantities) {
public String addPurchase(List<SkuQuantity> skuQuantities) throws UserException {
return addPurchase(skuQuantities, Collections.emptyList());
}
@ -228,7 +229,7 @@ public class PurchaseOrderServiceImpl extends ServiceImpl<PurchaseOrderMapper, P
*/
@Override
@Transactional
public String addPurchase(List<SkuQuantity> skuQuantities, List<String> platformOrderIDs) {
public String addPurchase(List<SkuQuantity> skuQuantities, List<String> platformOrderIDs) throws UserException {
Objects.requireNonNull(platformOrderIDs);
Client client = clientService.getCurrentClient();
@ -318,7 +319,7 @@ public class PurchaseOrderServiceImpl extends ServiceImpl<PurchaseOrderMapper, P
@Override
@Transactional
public String addPurchase(String username, Client client, String invoiceNumber, List<SkuQuantity> skuQuantities,
Map<PlatformOrder, List<PlatformOrderContent>> orderAndContent) {
Map<PlatformOrder, List<PlatformOrderContent>> orderAndContent) throws UserException {
Objects.requireNonNull(orderAndContent);
List<OrderContentDetail> details = platformOrderService.searchPurchaseOrderDetail(skuQuantities);
@ -470,4 +471,19 @@ public class PurchaseOrderServiceImpl extends ServiceImpl<PurchaseOrderMapper, P
Path invoice = Paths.get(INVOICE_DIR, invoiceCode + ".xlsx");
return Files.readAllBytes(invoice);
}
@Override
public BigDecimal getPurchaseFeesByInvoiceCode(String invoiceCode) {
return purchaseOrderMapper.getPurchaseFeesByInvoiceCode(invoiceCode);
}
@Override
public void cancelInvoice(String invoiceNumber) {
purchaseOrderMapper.deleteInvoice(invoiceNumber);
}
@Override
public void cancelBatchInvoice(List<String> invoiceNumbers) {
purchaseOrderMapper.deleteBatchInvoice(invoiceNumbers);
}
}

View File

@ -0,0 +1,13 @@
package org.jeecg.modules.business.vo;
import lombok.Data;
import org.jeecg.modules.business.entity.Client;
import java.math.BigDecimal;
@Data
public class BalanceData {
private final Client client;
private final String currency;
private final BigDecimal balance;
}

View File

@ -1,16 +1,18 @@
package org.jeecg.modules.business.vo;
import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
@Data
public class ShippingInvoiceParam {
private final String clientID;
private final BigDecimal balance;
private final List<String> shopIDs;
private final String start;
private final String end;
@ -19,12 +21,14 @@ public class ShippingInvoiceParam {
private final static SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
public ShippingInvoiceParam(@JsonProperty("clientID") String clientID,
@JsonProperty("balance") BigDecimal balance,
@JsonProperty("shopIDs") List<String> shopIDs,
@JsonProperty("start") String start,
@JsonProperty("end") String end,
@JsonProperty("erpStatuses") List<Integer> erpStatuses,
@JsonProperty("warehouses") List<String> warehouses) {
this.clientID = clientID;
this.balance = balance;
this.shopIDs = shopIDs;
this.start = start;
this.end = end;
@ -47,16 +51,6 @@ public class ShippingInvoiceParam {
public Date end() throws ParseException {
return format.parse(end);
}
public String getStart() {
return this.start;
}
public String getEnd() {
return this.end;
}
public List<Integer> getErpStatuses() { return erpStatuses; }
public List<String> getWarehouses() {
return warehouses;
}
@Override
public String toString() {
return "ShippingInvoiceParam{" + clientID +

View File

@ -1,98 +1,22 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<table align="center" cellpadding="0" cellspacing="0" width="600" bgcolor="#FFF" style="font-family:Arial,Helvetica,sans-serif;text-align:center;table-layout:fixed;font-size: 16px;border: 1px solid #0B49A6">
<tbody>
<tr>
<td width="600" height="90" bgcolor="#0B49A6" valign="top" align="center" style="padding:20px 0;table-layout:fixed">
<a href="http://app.wia-sourcing.com/user/login">
<img src="https://wia-sourcing.com/wp-content/uploads/2022/10/Fichier-24Icons.png" alt="logo" width="360" style="width:100%;max-width:360px;">
</a>
</td>
</tr>
<tr>
<td align="left">
<table width="520" align="center" style="color:#000;">
<tbody>
<tr>
<td style="padding:35px 0;">Cher collègue,</td>
</tr>
<tr>
<td style="padding:0 0 35px 0;">Vous trouverez en pièce-jointe une archive de l'ensemble des factures générées :</td>
</tr>
<tr>
<td style="padding:10px 0;"><b>Erreurs :</b>
<#if errors?size = 0>
No error
</#if>
</td>
</tr>
<#list errors as error>
<tr>
<td style="padding:10px 0;">
Error: ${error.invoiceEntity} - ${error.errorMsg}
</td>
</tr>
</#list>
<tr>
<td style="padding:35px 0 5px 0;">Merci dutiliser nos services.</td>
</tr>
<tr>
<td style="padding:5px 0;">Cordialement</td>
</tr>
<tr>
<td style="padding:5px 0 35px 0;">Léquipe WIA Sourcing.</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td align="left" bgcolor="#0B49A6" width="600">
<table align="center" width="520">
<tbody>
<tr>
<td style="font-style: italic;padding: 20px 0;">Ce message a été envoyé automatiquement. Merci de ne pas répondre. Ce message et ainsi que toutes les pièces jointes sont confidentielles.</td>
</tr>
<tr>
<td style="padding: 0 0 20px 0;">Si vous avez reçu ce message par erreur, merci de nous avertir immédiatement et de détruire ce message.</td>
</tr>
<tr>
<td>Service client :</td>
</tr>
<tr>
<td>Pour obtenir plus dinformations concernant nos services, veuillez nous contacter à ladresse ci-dessous ou en visitant notre site web.</td>
</tr>
</tbody>
</table>
<table align="center" width="520" style="padding: 15px 0">
<tbody>
<tr>
<td width="220" style="text-align:center;border-radius:2em;padding:20px 10px 20px 0;;" bgcolor="#EF5A1A"><a href="https://wia-sourcing.com/contactez-nous" style="color:white;text-decoration:none">Nous contacter</a></td>
<td width="40" ></td>
<td width="220" style="text-align:center;border-radius:2em;padding:20px 0 20px 10px;" bgcolor="#EF5A1A"><a href="https://wia-sourcing.com/" style="color:white;text-decoration:none">Notre site web</a></td>
</tr>
</tbody>
</table>
<table align="center" width="520" style="padding: 0 0 35px 0;">
<tbody>
<tr>
<td style="color:#EF5A1A;">WIA SOURCING</td>
</tr>
<tr>
<td>© 2018/2023 par WIA Sourcing Agency.</td>
</tr>
<tr>
<td>TOUS DROITS RÉSERVÉS©</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</body>
</html>
<#include "components/header.ftl">
<tr>
<td style="padding:35px 0;">Cher collègue,</td>
</tr>
<tr>
<td style="padding:0 0 35px 0;">Vous trouverez en pièce-jointe une archive de l'ensemble des factures générées :</td>
</tr>
<tr>
<td style="padding:10px 0;"><b>Erreurs :</b>
<#if errors?size = 0>
No error
</#if>
</td>
</tr>
<#list errors as error>
<tr>
<td style="padding:10px 0;">
Error: ${error.invoiceEntity} - ${error.errorMsg}
</td>
</tr>
</#list>
<#include "components/footer.ftl">

View File

@ -0,0 +1,56 @@
<#include "../components/header.ftl">
<tr>
<td style="padding:35px 0;">Cher(e) ${client.firstName} ${client.surname},</td>
</tr>
<tr>
<td style="padding:0 0 35px 0;">Une ou plusieurs commandes n'ont pas pu être facturées en raison d'un solde insuffisant :</td>
</tr>
<tr>
<#if errors?size = 0>
<p>No error</p>
<#elseif chronologicalOrder=="1">
<p>Les commandes à partir du numéro ${errors[0].platformOrderNumber} - ${errors[0].orderTime?datetime?string('dd-MM-yyyy')} n'ont pas été facturées.</p>
<#else>
<table style="border: 1px solid #bbb; text-align: center;width: 100%">
<thead>
<tr>
<th>Numéro de commande</th>
<th>Date de commande</th>
</tr>
</thead>
<tbody>
<#list errors as error>
<tr>
<td>${error.platformOrderNumber}</td>
<td>${error.orderTime?datetime?string('dd-MM-yyyy')}</td>
</tr>
</#list>
</tbody>
</table>
</#if>
<#if skipped??>
<table style="border: 1px solid #bbb; text-align: center;width: 100%">
<thead>
<tr>
<th>Numéro de commande</th>
<th>Date de commande</th>
</tr>
</thead>
<tbody>
<#list skipped as error>
<tr>
<td>${error.platformOrderNumber}</td>
<td>${error.orderTime?datetime?string('dd-MM-yyyy')}</td>
</tr>
</#list>
</tbody>
</table>
</#if>
</tr>
<tr>
<td style="padding:0 0 35px 0;">Pour continuer à utiliser nos services, nous vous invitons à recharger votre compte.</td>
</tr>
<tr>
<td style="padding:0 0 35px 0;">Pour toute information complémentaire nous vous invitons à vous rapprocher de votre conseiller.</td>
</tr>
<#include "../components/footer.ftl">

View File

@ -0,0 +1,16 @@
<#include "../components/header.ftl">
<tr>
<td style="padding:35px 0;">Cher(e) ${firstname} ${lastname},</td>
</tr>
<tr>
<td style="padding:0 0 35px 0;">Vous recevez cet e-mail car votre solde sur la plateforme de WIA App est actuellement de <b>${balance} ${currency}</b></td>
</tr>
<#if clientCategory != "vip">
<tr>
<td style="padding:0 0 35px 0;">Pour continuer à utiliser nos services, nous vous invitons à recharger votre compte.</td>
</tr>
</#if>
<tr>
<td style="padding:0 0 35px 0;">Pour toute information complémentaire nous vous invitons à vous rapprocher de votre conseiller.</td>
</tr>
<#include "../components/footer.ftl">

View File

@ -0,0 +1,59 @@
<tr>
<td style="padding:35px 0 5px 0;">Merci dutiliser nos services.</td>
</tr>
<tr>
<td style="padding:5px 0;">Cordialement</td>
</tr>
<tr>
<td style="padding:5px 0 35px 0;">Léquipe WIA Sourcing.</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td align="left" bgcolor="#0B49A6" width="600" style="padding: 20px 0;">
<#-- <table align="center" width="520" style="padding: 15px 0">-->
<#-- <tbody>-->
<#-- <tr>-->
<#-- <td width="220" style="text-align:center;border-radius:2em;padding:20px 10px 20px 0;;" bgcolor="#EF5A1A"><a href="https://wia-sourcing.com/contactez-nous" style="color:white;text-decoration:none">Nous contacter</a></td>-->
<#-- <td width="40" ></td>-->
<#-- <td width="220" style="text-align:center;border-radius:2em;padding:20px 0 20px 10px;" bgcolor="#EF5A1A"><a href="https://wia-sourcing.com/" style="color:white;text-decoration:none">Notre site web</a></td>-->
<#-- </tr>-->
<#-- </tbody>-->
<#-- </table>-->
<table align="center" width="520">
<tbody>
<tr>
<td style="color:#EF5A1A;">WIA SOURCING</td>
</tr>
<tr>
<td>© 2018/2023 par WIA Sourcing Agency.</td>
</tr>
<tr>
<td>TOUS DROITS RÉSERVÉS©</td>
</tr>
</tbody>
</table>
<table align="center" width="520">
<tbody style="color: white; font-size: 12px;">
<tr>
<td style="font-style: italic;padding: 20px 0;">Ce message a été envoyé automatiquement. Merci de ne pas y répondre. <br/>Ce message et ainsi que toutes les pièces jointes sont confidentielles.</td>
</tr>
<tr>
<td style="padding: 0 0 20px 0;">Si vous avez reçu ce message par erreur, merci de nous en avertir immédiatement et de détruire ce message.</td>
</tr>
<#-- <tr>-->
<#-- <td>Service client :</td>-->
<#-- </tr>-->
<#-- <tr>-->
<#-- <td>Pour obtenir plus dinformations concernant nos services, veuillez nous contacter à ladresse ci-dessous ou en visitant notre site web.</td>-->
<#-- </tr>-->
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</body>
</html>

View File

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<table align="center" cellpadding="0" cellspacing="0" width="600" bgcolor="#FFF" style="font-family:Arial,Helvetica,sans-serif;text-align:center;table-layout:fixed;font-size: 16px;border: 1px solid #0B49A6">
<tbody>
<tr>
<td width="600" height="90" bgcolor="#0B49A6" valign="top" align="center" style="padding:20px 0;table-layout:fixed">
<a href="http://app.wia-sourcing.com/user/login">
<img src="https://wia-sourcing.com/wp-content/uploads/2022/10/Fichier-24Icons.png" alt="logo" width="360" style="width:100%;max-width:360px;">
</a>
</td>
</tr>
<tr>
<td align="left">
<table width="520" align="center" style="color:#000;">
<tbody>

View File

@ -1,93 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<table align="center" cellpadding="0" cellspacing="0" width="600" bgcolor="#FFF" style="font-family:Arial,Helvetica,sans-serif;text-align:center;table-layout:fixed;font-size: 16px;border: 1px solid #0B49A6">
<tbody>
<tr>
<td width="600" height="90" bgcolor="#0B49A6" valign="top" align="center" style="padding:20px 0;table-layout:fixed">
<a href="http://app.wia-sourcing.com/user/login">
<img src="https://wia-sourcing.com/wp-content/uploads/2022/10/Fichier-24Icons.png" alt="logo" width="360" style="width:100%;max-width:360px;">
</a>
</td>
</tr>
<tr>
<td align="left">
<table width="520" align="center" style="color:#000;">
<tbody>
<tr>
<td style="padding:35px 0;">Cher Client(e),</td>
</tr>
<tr>
<td style="padding:0 0 35px 0;">Vous trouverez en pièce-jointe le fichier que vous nous avez demandé :</td>
</tr>
<tr>
<td style="padding:10px 0;"><b>Type de fichier :</b> ${fileType}</td>
</tr>
<tr>
<td style="padding:10px 0;"><b>Client :</b> ${invoiceEntity}</td>
</tr>
<tr>
<td style="padding:10px 0;"><b>Numéro de facture :</b> <a href="http://app.wia-sourcing.com/business/admin/shippingInvoice/Invoice?invoice=${invoiceNumber}"> ${invoiceNumber} </a></td>
</tr>
<tr>
<td style="padding:35px 0 5px 0;">Merci dutiliser nos services.</td>
</tr>
<tr>
<td style="padding:5px 0;">Cordialement</td>
</tr>
<tr>
<td style="padding:5px 0 35px 0;">Léquipe WIA Sourcing.</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td align="left" bgcolor="#0B49A6" width="600">
<table align="center" width="520">
<tbody>
<tr>
<td style="font-style: italic;padding: 20px 0;">Ce message a été envoyé automatiquement. Merci de ne pas répondre. Ce message et ainsi que toutes les pièces jointes sont confidentielles.</td>
</tr>
<tr>
<td style="padding: 0 0 20px 0;">Si vous avez reçu ce message par erreur, merci de nous avertir immédiatement et de détruire ce message.</td>
</tr>
<tr>
<td>Service client :</td>
</tr>
<tr>
<td>Pour obtenir plus dinformations concernant nos services, veuillez nous contacter à ladresse ci-dessous ou en visitant notre site web.</td>
</tr>
</tbody>
</table>
<table align="center" width="520" style="padding: 15px 0">
<tbody>
<tr>
<td width="220" style="text-align:center;border-radius:2em;padding:20px 10px 20px 0;;" bgcolor="#EF5A1A"><a href="https://wia-sourcing.com/contactez-nous" style="color:white;text-decoration:none">Nous contacter</a></td>
<td width="40" ></td>
<td width="220" style="text-align:center;border-radius:2em;padding:20px 0 20px 10px;" bgcolor="#EF5A1A"><a href="https://wia-sourcing.com/" style="color:white;text-decoration:none">Notre site web</a></td>
</tr>
</tbody>
</table>
<table align="center" width="520" style="padding: 0 0 35px 0;">
<tbody>
<tr>
<td style="color:#EF5A1A;">WIA SOURCING</td>
</tr>
<tr>
<td>© 2018/2023 par WIA Sourcing Agency.</td>
</tr>
<tr>
<td>TOUS DROITS RÉSERVÉS©</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</body>
</html>"
<#include "components/header.ftl">
<tr>
<td style="padding:35px 0;">Cher Client(e),</td>
</tr>
<tr>
<td style="padding:0 0 35px 0;">Vous trouverez en pièce-jointe le fichier que vous nous avez demandé :</td>
</tr>
<tr>
<td style="padding:10px 0;"><b>Type de fichier :</b> ${fileType}</td>
</tr>
<tr>
<td style="padding:10px 0;"><b>Client :</b> ${invoiceEntity}</td>
</tr>
<tr>
<td style="padding:10px 0;"><b>Numéro de facture :</b> <a href="http://app.wia-sourcing.com/business/admin/shippingInvoice/Invoice?invoice=${invoiceNumber}"> ${invoiceNumber} </a></td>
</tr>
<#include "components/footer.ftl">

View File

@ -1,113 +1,37 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<table align="center" cellpadding="0" cellspacing="0" width="600" bgcolor="#FFF" style="font-family:Arial,Helvetica,sans-serif;text-align:center;table-layout:fixed;font-size: 16px;border: 1px solid #0B49A6">
<tbody>
<#include "components/header.ftl">
<tr>
<td style="padding:35px 0;">Cher(s) collègue(s),</td>
</tr>
<tr>
<td style="padding:0 0 35px 0;">Des erreurs se sont produites lors de l'attribution d'une ligne de transport à une ou plusieurs commandes :</td>
</tr>
<tr>
<td style="padding:10px 0;"><b>Erreurs :</b>
<#if errors?size = 0>
No error
</#if>
</td>
</tr>
<tr>
<table style="border: 1px solid #bbb; text-align: center;">
<thead>
<tr>
<td width="600" height="90" bgcolor="#0B49A6" valign="top" align="center" style="padding:20px 0;table-layout:fixed">
<a href="http://app.wia-sourcing.com/user/login">
<img src="https://wia-sourcing.com/wp-content/uploads/2022/10/Fichier-24Icons.png" alt="logo" width="360" style="width:100%;max-width:360px;">
</a>
</td>
</tr>
<tr>
<td align="left">
<table width="520" align="center" style="color:#000;">
<tbody>
<tr>
<td style="padding:35px 0;">Cher(s) collègue(s),</td>
</tr>
<tr>
<td style="padding:0 0 35px 0;">Des erreurs se sont produites lors de l'attribution d'une ligne de transport à une ou plusieurs commandes :</td>
</tr>
<tr>
<td style="padding:10px 0;"><b>Erreurs :</b>
<#if errors?size = 0>
No error
</#if>
</td>
</tr>
<tr>
<table style="border: 1px solid #bbb; text-align: center;">
<thead>
<tr>
<th>Shop</th>
<th>Order ID</th>
<th>Country</th>
<th>Sensitive Attribute</th>
</tr>
</thead>
<tbody>
<#list errors as error>
<tr>
<td>${error.shop}</td>
<td>${error.orderId}</td>
<td>${error.country}</td>
<td>${error.sensitiveAttribute}</td>
</tr>
</#list>
</tbody>
</table>
</tr>
<tr>
<td style="padding:35px 0 5px 0;">Merci dutiliser nos services.</td>
</tr>
<tr>
<td style="padding:5px 0;">Cordialement</td>
</tr>
<tr>
<td style="padding:5px 0 35px 0;">Léquipe WIA Sourcing.</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td align="left" bgcolor="#0B49A6" width="600">
<table align="center" width="520">
<tbody>
<tr>
<td style="font-style: italic;padding: 20px 0;">Ce message a été envoyé automatiquement. Merci de ne pas répondre. Ce message et ainsi que toutes les pièces jointes sont confidentielles.</td>
</tr>
<tr>
<td style="padding: 0 0 20px 0;">Si vous avez reçu ce message par erreur, merci de nous avertir immédiatement et de détruire ce message.</td>
</tr>
<tr>
<td>Service client :</td>
</tr>
<tr>
<td>Pour obtenir plus dinformations concernant nos services, veuillez nous contacter à ladresse ci-dessous ou en visitant notre site web.</td>
</tr>
</tbody>
</table>
<table align="center" width="520" style="padding: 15px 0">
<tbody>
<tr>
<td width="220" style="text-align:center;border-radius:2em;padding:20px 10px 20px 0;;" bgcolor="#EF5A1A"><a href="https://wia-sourcing.com/contactez-nous" style="color:white;text-decoration:none">Nous contacter</a></td>
<td width="40" ></td>
<td width="220" style="text-align:center;border-radius:2em;padding:20px 0 20px 10px;" bgcolor="#EF5A1A"><a href="https://wia-sourcing.com/" style="color:white;text-decoration:none">Notre site web</a></td>
</tr>
</tbody>
</table>
<table align="center" width="520" style="padding: 0 0 35px 0;">
<tbody>
<tr>
<td style="color:#EF5A1A;">WIA SOURCING</td>
</tr>
<tr>
<td>© 2018/2023 par WIA Sourcing Agency.</td>
</tr>
<tr>
<td>TOUS DROITS RÉSERVÉS©</td>
</tr>
</tbody>
</table>
</td>
<th>Shop</th>
<th>Order ID</th>
<th>Country</th>
<th>Sensitive Attribute</th>
</tr>
</thead>
<tbody>
<#list errors as error>
<tr>
<td>${error.shop}</td>
<td>${error.orderId}</td>
<td>${error.country}</td>
<td>${error.sensitiveAttribute}</td>
</tr>
</#list>
</tbody>
</table>
</body>
</html>
</tr>
<#include "components/footer.ftl">

View File

@ -1,109 +1,33 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<table align="center" cellpadding="0" cellspacing="0" width="600" bgcolor="#FFF" style="font-family:Arial,Helvetica,sans-serif;text-align:center;table-layout:fixed;font-size: 16px;border: 1px solid #0B49A6">
<#include "components/header.ftl">
<tr>
<td style="padding:35px 0;">Cher(s) collègue(s),</td>
</tr>
<tr>
<td style="padding:0 0 35px 0;">Des erreurs se sont produites lors du déroulement de la tâche plannifiée suivante : <br/><b>${job} invoicing job</b></td>
</tr>
<tr>
<td style="padding:10px 0;"><b>Erreurs :</b>
<#if errors?size = 0>
No error
</#if>
</td>
</tr>
<tr>
<table style="border: 1px solid #bbb; border-collapse: collapse; text-align: center; width: 100%;">
<thead>
<tr>
<th style="border: 1px solid #bbb;">Client</th>
<th style="border: 1px solid #bbb;">Error Message</th>
</tr>
</thead>
<tbody>
<#list errors as error>
<tr>
<td width="600" height="90" bgcolor="#0B49A6" valign="top" align="center" style="padding:20px 0;table-layout:fixed">
<a href="http://app.wia-sourcing.com/user/login">
<img src="https://wia-sourcing.com/wp-content/uploads/2022/10/Fichier-24Icons.png" alt="logo" width="360" style="width:100%;max-width:360px;">
</a>
</td>
</tr>
<tr>
<td align="left">
<table width="520" align="center" style="color:#000;">
<tbody>
<tr>
<td style="padding:35px 0;">Cher(s) collègue(s),</td>
</tr>
<tr>
<td style="padding:0 0 35px 0;">Des erreurs se sont produites lors du déroulement de la tâche plannifiée : <b>VIP invoicing job</b></td>
</tr>
<tr>
<td style="padding:10px 0;"><b>Erreurs :</b>
<#if errors?size = 0>
No error
</#if>
</td>
</tr>
<tr>
<table style="border: 1px solid #bbb; text-align: center;">
<thead>
<tr>
<th>Client</th>
<th>Error Message</th>
</tr>
</thead>
<tbody>
<#list errors as error>
<tr>
<td>${error.internalCode}</td>
<td>${error.errorMsg}</td>
</tr>
</#list>
</tbody>
</table>
</tr>
<tr>
<td style="padding:35px 0 5px 0;">Merci dutiliser nos services.</td>
</tr>
<tr>
<td style="padding:5px 0;">Cordialement</td>
</tr>
<tr>
<td style="padding:5px 0 35px 0;">Léquipe WIA Sourcing.</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td align="left" bgcolor="#0B49A6" width="600">
<table align="center" width="520">
<tbody>
<tr>
<td style="font-style: italic;padding: 20px 0;">Ce message a été envoyé automatiquement. Merci de ne pas répondre. Ce message et ainsi que toutes les pièces jointes sont confidentielles.</td>
</tr>
<tr>
<td style="padding: 0 0 20px 0;">Si vous avez reçu ce message par erreur, merci de nous avertir immédiatement et de détruire ce message.</td>
</tr>
<tr>
<td>Service client :</td>
</tr>
<tr>
<td>Pour obtenir plus dinformations concernant nos services, veuillez nous contacter à ladresse ci-dessous ou en visitant notre site web.</td>
</tr>
</tbody>
</table>
<table align="center" width="520" style="padding: 15px 0">
<tbody>
<tr>
<td width="220" style="text-align:center;border-radius:2em;padding:20px 10px 20px 0;;" bgcolor="#EF5A1A"><a href="https://wia-sourcing.com/contactez-nous" style="color:white;text-decoration:none">Nous contacter</a></td>
<td width="40" ></td>
<td width="220" style="text-align:center;border-radius:2em;padding:20px 0 20px 10px;" bgcolor="#EF5A1A"><a href="https://wia-sourcing.com/" style="color:white;text-decoration:none">Notre site web</a></td>
</tr>
</tbody>
</table>
<table align="center" width="520" style="padding: 0 0 35px 0;">
<tbody>
<tr>
<td style="color:#EF5A1A;">WIA SOURCING</td>
</tr>
<tr>
<td>© 2018/2023 par WIA Sourcing Agency.</td>
</tr>
<tr>
<td>TOUS DROITS RÉSERVÉS©</td>
</tr>
</tbody>
</table>
</td>
<td style="border: 1px solid #bbb;">${error.internalCode}</td>
<td style="border: 1px solid #bbb;">${error.errorMsg}</td>
</tr>
</#list>
</tbody>
</table>
</body>
</html>
</tr>
<#include "components/footer.ftl">