feat: client sku purchase access, purchase invoice USD fix, complete invoice bypass stock

pull/8523/head
Gauthier LO 2025-06-09 12:07:28 +02:00
parent 4926576b70
commit b07318a891
19 changed files with 281 additions and 130 deletions

View File

@ -8,6 +8,7 @@ import freemarker.template.Template;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpStatus;
import org.apache.shiro.SecurityUtils;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.aspect.annotation.AutoLog;
@ -58,6 +59,8 @@ import static org.jeecg.common.util.SqlInjectionUtil.specialFilterContentForDict
@RequestMapping("/sku")
@Slf4j
public class SkuController {
@Autowired
private IClientService clientService;
@Autowired
private IShopService shopService;
@Autowired
@ -75,6 +78,8 @@ public class SkuController {
@Autowired
private SkuMongoService skuMongoService;
@Autowired
private ISecurityService securityService;
@Autowired
private EmailService emailService;
@Autowired
private FreeMarkerConfigurer freemarkerConfigurer;
@ -380,6 +385,19 @@ public class SkuController {
@RequestParam(name = "zhNames", required = false) String zhNames,
@RequestParam(name = "enNames", required = false) String enNames,
ServletRequest servletRequest) {
if(!securityService.checkIsEmployee()) {
Client client = clientService.getCurrentClient();
// Here we don't explicitly tell the user that they are not allowed to access this endpoint for security measure.
if (client == null) {
return Result.error(HttpStatus.SC_NOT_FOUND, "Profile not found");
}
if( clientId == null) {
return Result.error(HttpStatus.SC_BAD_REQUEST, "Bad request");
}
if(!client.getId().equals(clientId)) {
return Result.error(HttpStatus.SC_NOT_FOUND, "Client not found");
}
}
String parsedColumn = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, column.replace("_dictText", ""));
String parsedOrder = order.toUpperCase();
if(!parsedOrder.equals("ASC") && !parsedOrder.equals("DESC")) {

View File

@ -10,6 +10,7 @@ import freemarker.template.Template;
import freemarker.template.TemplateException;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpStatus;
import org.apache.shiro.SecurityUtils;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.query.QueryGenerator;
@ -76,6 +77,8 @@ public class InvoiceController {
@Autowired
private IClientService clientService;
@Autowired
private IClientSkuService clientSkuService;
@Autowired
private ICurrencyService currencyService;
@Autowired
private ExchangeRatesMapper exchangeRatesMapper;
@ -383,12 +386,12 @@ public class InvoiceController {
*/
@Transactional
@PostMapping(value = "/makeManualComplete")
public Result<?> makeManualCompleteInvoice(@RequestBody ShippingInvoiceOrderParam param) {
public Result<?> makeManualCompleteInvoice(@RequestBody ManualInvoiceOrderParam param) {
try {
InvoiceMetaData metaData = shippingInvoiceService.makeCompleteInvoice(param);
String clientCategory = clientCategoryService.getClientCategoryByClientId(param.clientID());
InvoiceMetaData metaData = shippingInvoiceService.makeManualCompleteInvoice(param);
String clientCategory = clientCategoryService.getClientCategoryByClientId(param.getClientID());
if(clientCategory.equals(ClientCategory.CategoryName.CONFIRMED.getName()) || clientCategory.equals(ClientCategory.CategoryName.VIP.getName())) {
balanceService.updateBalance(param.clientID(), metaData.getInvoiceCode(), COMPLETE.name());
balanceService.updateBalance(param.getClientID(), metaData.getInvoiceCode(), COMPLETE.name());
}
if(clientCategory.equals(ClientCategory.CategoryName.SELF_SERVICE.getName())) {
String subject = "Self-service complete invoice";
@ -396,7 +399,7 @@ public class InvoiceController {
Properties prop = emailService.getMailSender();
Map<String, Object> templateModel = new HashMap<>();
templateModel.put("invoiceType", "complete invoice");
templateModel.put("invoiceEntity", clientService.getById(param.clientID()).getInternalCode());
templateModel.put("invoiceEntity", clientService.getById(param.getClientID()).getInternalCode());
templateModel.put("invoiceNumber", metaData.getInvoiceCode());
Session session = Session.getInstance(prop, new Authenticator() {
@ -423,10 +426,22 @@ public class InvoiceController {
}
@PostMapping("/makeManualSkuPurchaseInvoice")
public Result<?> createOrder(@RequestBody Map<String, Integer> payload) {
boolean isEmployee = securityService.checkIsEmployee();
Client client = null;
if(!isEmployee) {
client = clientService.getCurrentClient();
if(client == null)
return Result.error(HttpStatus.SC_NOT_FOUND, "Client not found");
}
InvoiceMetaData metaData;
List<SkuQuantity> skuQuantities = new ArrayList<>();
for(Map.Entry<String, Integer> entry : payload.entrySet()) {
String skuId = skuService.getIdFromErpCode(entry.getKey());
if(client != null) {
String skuClientId = clientSkuService.getClientIdFromSkuId(skuId);
if (!skuClientId.equals(client.getId()))
return Result.error(HttpStatus.SC_NOT_FOUND, "Sku " + entry.getKey() + " for client " + client.getInternalCode() + " not found.");
}
skuQuantities.add(new SkuQuantity(skuId, entry.getKey(), entry.getValue()));
}
try {

View File

@ -42,6 +42,8 @@ import static org.jeecg.modules.business.entity.Invoice.InvoicingMethod.*;
@Component
public class ShippingInvoiceFactory {
@Autowired
private IClientService clientService;
@Autowired
private IExtraFeeService extraFeeService;
@Autowired
@ -99,6 +101,7 @@ public class ShippingInvoiceFactory {
return skuDeclaredValueService.getDeclaredValueForDate(skuIdAndDate.getLeft(), skuIdAndDate.getRight());
}
});
private final List<String> CLIENT_STOCK_BYPASS_LIST = Arrays.asList("LA", "AP");
/**
* Creates an invoice for a client according to type
@ -149,6 +152,7 @@ public class ShippingInvoiceFactory {
/**
* Creates a complete shipping (purchase + shipping) invoice for a client
* /!\ if the client is in the bypass list and ordersWithStock is not empty we only invoice the shipping fee of these orders, and we don't invoice the purchase fee
* <p>
* To generate an invoice, it
* <ol>
@ -164,12 +168,13 @@ public class ShippingInvoiceFactory {
* @param customerId the customer id
* @param orderIds the list of order IDs
* @param shippingMethod "post" = postShipping, "pre" = preShipping, "all" = all shipping methods
* @param ordersWithStock for orders with stock, if the client is in the bypass list, we only invoice the shipping fee and we don't invoice the purchase fee of the orders with stock
* @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 createCompleteShippingInvoice(String username, String customerId, BigDecimal balance, List<String> orderIds, String shippingMethod, String start, String end) throws UserException, MessagingException, InterruptedException {
public CompleteInvoice createCompleteShippingInvoice(String username, String customerId, BigDecimal balance, List<String> orderIds, String shippingMethod, String start, String end, List<String> ordersWithStock) throws UserException, MessagingException, InterruptedException {
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.fetchUninvoicedOrderDataForUpdate(orderIds);
@ -184,19 +189,32 @@ public class ShippingInvoiceFactory {
.collect(Collectors.toList());
List<ExtraFeeResult> extraFees = extraFeeService.findNotInvoicedByShops(shopIds);
log.info("Orders to be invoiced: {}", uninvoicedOrderToContent);
String subject;
if(shippingMethod.equals("shipping"))
subject = String.format("Purchase and Shipping fees from %s to %s", start, end);
else if(shippingMethod.equals(POSTSHIPPING.getMethod()))
subject = String.format("Purchase and post-Shipping fees from %s to %s", start, end);
else if (shippingMethod.equals(PRESHIPPING.getMethod()))
subject = String.format("Purchase and pre-Shipping fees, order time from %s to %s", start, end);
else if(shippingMethod.equals(ALL.getMethod()))
subject = String.format("Purchase and Shipping fees, order time from %s to %s", start, end);
StringBuilder subject = new StringBuilder();
boolean hasPeriod = start != null && !start.isEmpty() && end != null && !end.isEmpty();
if(shippingMethod.equals("shipping")) {
subject.append("Purchase and Shipping fees");
if(hasPeriod)
subject.append(String.format(" from %s to %s", start, end));
}
else if(shippingMethod.equals(POSTSHIPPING.getMethod())) {
subject.append("Purchase and post-Shipping fees");
if(hasPeriod)
subject.append(String.format(" from %s to %s", start, end));
}
else if (shippingMethod.equals(PRESHIPPING.getMethod())) {
subject.append("Purchase and Pre-Shipping fees");
if(hasPeriod)
subject.append(String.format(" order time from %s to %s", start, end));
}
else if(shippingMethod.equals(ALL.getMethod())) {
subject.append("Purchase and Shipping fees");
if(hasPeriod)
subject.append(String.format(" order time from %s to %s", start, end));
}
else throw new UserException("Couldn't create complete invoice for unknown shipping method");
if(balance != null)
return createCompleteInvoiceWithBalance(username, customerId, balance, shopIds, uninvoicedOrderToContent, savRefunds, extraFees, subject);
return createInvoice(username, customerId, null, shopIds, uninvoicedOrderToContent, savRefunds, extraFees, subject);
return createCompleteInvoiceWithBalance(username, customerId, balance, shopIds, uninvoicedOrderToContent, savRefunds, extraFees, subject.toString());
return createInvoice(username, customerId, null, shopIds, uninvoicedOrderToContent, savRefunds, extraFees, subject.toString(), ordersWithStock);
}
@ -225,7 +243,7 @@ public class ShippingInvoiceFactory {
@Transactional
public CompleteInvoice createInvoice(String username, String customerId, BigDecimal balance, List<String> shopIds,
Map<PlatformOrder, List<PlatformOrderContent>> orderAndContent,
List<SavRefundWithDetail> savRefunds, List<ExtraFeeResult> extraFees, String subject) throws UserException {
List<SavRefundWithDetail> savRefunds, List<ExtraFeeResult> extraFees, String subject, List<String> ordersWithStock) throws UserException {
Client client = clientMapper.selectById(customerId);
log.info("User {} is creating a complete invoice for customer {}", username, client.getInternalCode());
@ -251,20 +269,29 @@ public class ShippingInvoiceFactory {
log.info("New invoice code: {}", invoiceCode);
calculateFees(balance, logisticChannelMap, orderAndContent, channelPriceMap, countryList, skuRealWeights, skuServiceFees,
latestDeclaredValues, client, shopServiceFeeMap, shopPackageMatFeeMap, invoiceCode);
// Purchase fees
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);
List<PurchaseInvoiceEntry> purchaseOrderSkuList = new ArrayList<>();
List<PromotionDetail> promotionDetails = new ArrayList<>();
List<String> clientsThatByPassStock = clientService.getClientsByCode(CLIENT_STOCK_BYPASS_LIST);
if(clientsThatByPassStock.contains(customerId) && ordersWithStock != null && !ordersWithStock.isEmpty()) {
orderIds = orderIds.stream().filter(orderId -> !ordersWithStock.contains(orderId)).collect(toList());
}
if(extraFees != null && !extraFees.isEmpty()) {
List<String> extraFeesIds = extraFees.stream().map(ExtraFeeResult::getId).collect(toList());
extraFeeService.updateInvoiceNumberByIds(extraFeesIds, invoiceCode);
if(!orderIds.isEmpty()){
List<SkuQuantity> skuQuantities = platformOrderContentService.searchOrderContent(orderIds);
String purchaseID = purchaseOrderService.addPurchase(username, client, invoiceCode, skuQuantities, orderAndContent, ordersWithStock);
purchaseOrderSkuList = purchaseOrderContentMapper.selectInvoiceDataByID(purchaseID);
promotionDetails = skuPromotionHistoryMapper.selectPromotionByPurchase(purchaseID);
if (savRefunds != null) {
updateSavRefundsInDb(savRefunds, invoiceCode);
}
if(extraFees != null && !extraFees.isEmpty()) {
List<String> extraFeesIds = extraFees.stream().map(ExtraFeeResult::getId).collect(toList());
extraFeeService.updateInvoiceNumberByIds(extraFeesIds, invoiceCode);
}
}
updateOrdersAndContentsInDb(orderAndContent);
@ -391,7 +418,7 @@ public class ShippingInvoiceFactory {
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);
String purchaseID = purchaseOrderService.addPurchase(username, client, invoiceCode, skuQuantities, orderAndContent, null);
List<PurchaseInvoiceEntry> purchaseOrderSkuList = purchaseOrderContentMapper.selectInvoiceDataByID(purchaseID);
List<PromotionDetail> promotionDetails = skuPromotionHistoryMapper.selectPromotionByPurchase(purchaseID);

View File

@ -38,4 +38,6 @@ public interface ClientMapper extends BaseMapper<Client> {
Client getByShopId(@Param("shopId") String shopId);
Client getClientFromCredit(@Param("invoiceNumber") String invoiceNumber);
List<String> getClientsByCode(@Param("codes") List<String> clientCodes);
}

View File

@ -21,4 +21,6 @@ public interface ClientSkuMapper extends BaseMapper<ClientSku> {
List<ClientSku> selectByMainId(@Param("mainId") String mainId);
List<Sku> getUnpairedSkus();
String getClientIdFromSkuId(@Param("skuId") String skuId);
}

View File

@ -107,4 +107,13 @@
JOIN credit ON c.id = credit.client_id
WHERE credit.invoice_number = #{invoiceNumber};
</select>
<select id="getClientsByCode" resultType="java.lang.String">
SELECT c.id
FROM client c
WHERE c.internal_code IN
<foreach collection="codes" item="code" index="index" separator="," open="(" close=")">
#{code}
</foreach>
AND c.active = 1;
</select>
</mapper>

View File

@ -2,22 +2,28 @@
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.jeecg.modules.business.mapper.ClientSkuMapper">
<delete id="deleteByMainId" parameterType="java.lang.String">
DELETE
FROM client_sku
WHERE
client_id = #{mainId} </delete>
<delete id="deleteByMainId" parameterType="java.lang.String">
DELETE
FROM client_sku
WHERE
client_id = #{mainId}
</delete>
<select id="selectByMainId" parameterType="java.lang.String" resultType="org.jeecg.modules.business.entity.ClientSku">
SELECT *
FROM client_sku
WHERE
client_id = #{mainId} </select>
<select id="selectByMainId" parameterType="java.lang.String" resultType="org.jeecg.modules.business.entity.ClientSku">
SELECT *
FROM client_sku
WHERE
client_id = #{mainId} </select>
<select id="getUnpairedSkus" resultType="org.jeecg.modules.business.entity.Sku">
SELECT s.*
FROM sku s
LEFT JOIN client_sku cs ON s.id = cs.sku_id
WHERE cs.sku_id IS NULL;
</select>
<select id="getUnpairedSkus" resultType="org.jeecg.modules.business.entity.Sku">
SELECT s.*
FROM sku s
LEFT JOIN client_sku cs ON s.id = cs.sku_id
WHERE cs.sku_id IS NULL;
</select>
<select id="getClientIdFromSkuId" resultType="java.lang.String">
SELECT client_id
FROM client_sku
WHERE sku_id = #{skuId}
</select>
</mapper>

View File

@ -184,10 +184,18 @@
JOIN shop s ON po.shop_id = s.id
JOIN client c ON s.owner_id = c.id
WHERE c.id = #{clientId}
AND poc.create_time &gt;= '2025-01-01 00:00:00'
AND po.erp_status IN ('1','2')
AND po.can_send = 1
AND poc.erp_status IN ('1','2')
GROUP BY sku_id
), latest_exchange_rate AS (
SELECT rate
FROM exchange_rates
WHERE original_currency = 'EUR' AND target_currency = 'RMB'
ORDER BY create_time DESC LIMIT 1
), rmb_id AS (
SELECT id FROM currency WHERE code = 'RMB'
)
SELECT s.id,
s.erp_code,
@ -196,31 +204,25 @@
s.purchasing_amount,
s.available_amount,
qtyInOrdersNotShipped.quantity as qtyInOrdersNotShipped,
s.available_amount + s.purchasing_amount - IF(qtyInOrdersNotShipped.quantity IS NULL, 0, qtyInOrdersNotShipped.quantity) as stock,
s.available_amount + s.purchasing_amount - COALESCE(qtyInOrdersNotShipped.quantity, 0) as stock,
s.image_source,
s.service_fee,
IF(sp.price_rmb IS NULL, sp.price,
IF(sp.currency_id = (SELECT id FROM rmb_id),
(
ROUND(
sp.price_rmb /
(SELECT rate
FROM exchange_rates
WHERE original_currency = 'EUR' AND target_currency = 'RMB'
ORDER BY create_time DESC LIMIT 1)
sp.price /
(SELECT rate FROM latest_exchange_rate)
,2)
)
), sp.price
) as sku_price,
sp.threshold as discount_moq,
IF(sp.price_rmb IS NULL, sp.discounted_price,
IF(sp.currency_id = (SELECT id FROM rmb_id),
(
ROUND(
sp.discounted_price_rmb /
(SELECT rate
FROM exchange_rates
WHERE target_currency = 'EUR' AND original_currency = 'RMB'
ORDER BY create_time DESC LIMIT 1)
sp.discounted_price /
(SELECT rate FROM latest_exchange_rate)
,2)
)
),sp.discounted_price
) as discounted_price,
s7.quantity as sales_last_week,
s28.quantity as sales_four_weeks,
@ -337,6 +339,13 @@
AND po.can_send = 1
AND poc.erp_status IN ('1','2')
GROUP BY sku_id
), latest_exchange_rate AS (
SELECT rate
FROM exchange_rates
WHERE original_currency = 'EUR' AND target_currency = 'RMB'
ORDER BY create_time DESC LIMIT 1
), rmb_id AS (
SELECT id FROM currency WHERE code = 'RMB'
)
SELECT s.id,
s.erp_code,
@ -345,31 +354,25 @@
s.purchasing_amount,
s.available_amount,
qtyInOrdersNotShippedCTE.quantity as qtyInOrdersNotShipped,
s.available_amount + s.purchasing_amount - IF(qtyInOrdersNotShippedCTE.quantity IS NULL, 0, qtyInOrdersNotShippedCTE.quantity) as stock,
s.available_amount + s.purchasing_amount - COALESCE(qtyInOrdersNotShippedCTE.quantity, 0) as stock,
s.image_source,
s.service_fee,
IF(sp.price_rmb IS NULL, sp.price,
IF(sp.currency_id = (SELECT id FROM rmb_id),
(
ROUND(
sp.price_rmb /
(SELECT rate
FROM exchange_rates
WHERE original_currency = 'EUR' AND target_currency = 'RMB'
ORDER BY create_time DESC LIMIT 1)
sp.price /
(SELECT rate FROM latest_exchange_rate)
,2)
)
), sp.price
) as sku_price,
sp.threshold as discount_moq,
IF(sp.price_rmb IS NULL, sp.discounted_price,
IF(sp.currency_id = (SELECT id FROM rmb_id),
(
ROUND(
sp.discounted_price_rmb /
(SELECT rate
FROM exchange_rates
WHERE target_currency = 'EUR' AND original_currency = 'RMB'
ORDER BY create_time DESC LIMIT 1)
sp.discounted_price /
(SELECT rate FROM latest_exchange_rate)
,2)
)
),sp.discounted_price
) as discounted_price,
s7.quantity as sales_last_week,
s28.quantity as sales_four_weeks,
@ -418,6 +421,9 @@
AND po.can_send = 1
AND poc.erp_status IN ('1','2')
GROUP BY sku_id
),
rmb_id AS (
SELECT id FROM currency WHERE code = 'RMB'
)
SELECT s.id,
s.erp_code,
@ -429,28 +435,30 @@
s.available_amount + s.purchasing_amount - IF(qtyInOrdersNotShippedCTE.quantity IS NULL, 0, qtyInOrdersNotShippedCTE.quantity) as stock,
s.image_source,
s.service_fee,
IF(sp.price_rmb IS NULL, sp.price,
IF(sp.currency_id = (SELECT id FROM rmb_id),
(
ROUND(
sp.price_rmb /
sp.price /
(SELECT rate
FROM exchange_rates
WHERE original_currency = 'EUR' AND target_currency = 'RMB'
ORDER BY create_time DESC LIMIT 1)
,2)
,2
)
), sp.price
) as sku_price,
sp.threshold as discount_moq,
IF(sp.price_rmb IS NULL, sp.discounted_price,
IF(sp.currency_id = (SELECT id FROM rmb_id),
(
ROUND(
sp.discounted_price_rmb /
sp.discounted_price /
(SELECT rate
FROM exchange_rates
WHERE target_currency = 'EUR' AND original_currency = 'RMB'
ORDER BY create_time DESC LIMIT 1)
,2)
,2
)
), sp.discounted_price
) as discounted_price,
s7.quantity as sales_last_week,
s28.quantity as sales_four_weeks,
@ -464,10 +472,7 @@
LEFT JOIN qtyInOrdersNotShippedCTE ON s.id = qtyInOrdersNotShippedCTE.ID
WHERE client_sku.client_id = #{clientId}
AND s.status = 3
AND (
(sp.price_rmb IS NOT NULL AND sp.price_rmb &lt;&gt; 0)
OR (sp.price IS NOT NULL AND sp.price &lt;&gt; 0)
)
AND sp.price IS NOT NULL AND sp.price &lt;&gt; 0
ORDER BY s.erp_code
;
</select>

View File

@ -65,4 +65,6 @@ public interface IClientService extends IService<Client> {
Client getByShopId(String shopId);
Client getClientFromCredit(String invoiceNumber);
List<String> getClientsByCode(List<String> clientCodes);
}

View File

@ -21,4 +21,6 @@ public interface IClientSkuService extends IService<ClientSku> {
void addClientSku(String clientId, String skuId);
List<Sku> getUnpairedSkus();
String getClientIdFromSkuId(String skuCode);
}

View File

@ -72,7 +72,7 @@ public interface IPurchaseOrderService extends IService<PurchaseOrder> {
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) throws UserException;
String addPurchase(String username, Client client, String invoiceNumber, List<SkuQuantity> skuQuantities, Map<PlatformOrder, List<PlatformOrderContent>> orderContentMap, List<String> ordersWithStock) throws UserException;
void savePaymentDocumentForPurchase(String purchaseID, MultipartFile in) throws IOException;

View File

@ -98,8 +98,10 @@ public class PlatformOrderShippingInvoiceService {
@Value("${jeecg.path.completeTemplatePath_US}")
private String COMPLETE_INVOICE_TEMPLATE_US;
@Value("${jeecg.path.purchaseTemplatePath}")
private String PURCHASE_INVOICE_TEMPLATE;
@Value("${jeecg.path.purchaseTemplatePath_EU}")
private String PURCHASE_INVOICE_TEMPLATE_EU;
@Value("${jeecg.path.purchaseTemplatePath_US}")
private String PURCHASE_INVOICE_TEMPLATE_US;
@Value("${jeecg.path.shippingInvoiceDir}")
private String INVOICE_DIR;
@ -253,21 +255,11 @@ public class PlatformOrderShippingInvoiceService {
return getInvoiceMetaDataAndInsert(username, invoice);
}
/**
* Make a complete shipping invoice (purchase + shipping) invoice for specified orders and order statuses
*
* @param param the parameters to make the invoice
* @return name of the invoice, can be used to in {@code getInvoiceBinary}.
* @throws UserException exception due to error of user input, message will contain detail
* @throws ParseException exception because of format of "start" and "end" date does not follow
* pattern: "yyyy-MM-dd"
* @throws IOException exception related to invoice file IO.
*/
@Transactional
public InvoiceMetaData makeCompleteInvoice(ShippingInvoiceOrderParam param) throws UserException, ParseException, IOException, MessagingException, InterruptedException {
public InvoiceMetaData makeManualCompleteInvoice(ManualInvoiceOrderParam param) throws UserException, ParseException, IOException, MessagingException, InterruptedException {
String username = ((LoginUser) SecurityUtils.getSubject().getPrincipal()).getUsername();
// Creates invoice by factory
CompleteInvoice invoice = factory.createCompleteShippingInvoice(username, param.clientID(), null, param.orderIds(), param.getType(), param.getStart(), param.getEnd());
CompleteInvoice invoice = factory.createCompleteShippingInvoice(username, param.getClientID(), null, param.getOrderIds(), param.getType(), null, null, param.getOrdersWithStock());
return getInvoiceMetaDataAndInsert(username, invoice);
}
@ -292,7 +284,7 @@ public class PlatformOrderShippingInvoiceService {
orderIds = platformOrderMapper.fetchUninvoicedOrderIDInShopsAndOrderTime(param.getStart(), param.getEnd(), param.shopIDs(), param.getErpStatuses(), param.getWarehouses());
}
// Creates invoice by factory
CompleteInvoice invoice = factory.createCompleteShippingInvoice(username, param.clientID(), param.getBalance() ,orderIds, method, param.getStart(), param.getEnd());
CompleteInvoice invoice = factory.createCompleteShippingInvoice(username, param.clientID(), param.getBalance() ,orderIds, method, param.getStart(), param.getEnd(), null);
return getInvoiceMetaDataAndInsert(username, invoice);
}
@NotNull
@ -341,8 +333,11 @@ public class PlatformOrderShippingInvoiceService {
@NotNull
private InvoiceMetaData getInvoiceMetaData(PurchaseInvoice invoice) throws IOException {
// Chooses invoice template based on client's preference on currency
Path template = Paths.get(PURCHASE_INVOICE_TEMPLATE );
Path template;
if(invoice.getTargetClient().getCurrency().equals("USD"))
template = Paths.get(PURCHASE_INVOICE_TEMPLATE_US );
else
template = Paths.get(PURCHASE_INVOICE_TEMPLATE_EU);
// Writes invoice content to a new excel file
String filename = "Invoice N°" + invoice.getCode() + " (" + invoice.getTargetClient().getInvoiceEntity() + ").xlsx";
Path out = Paths.get(PURCHASE_INVOICE_DIR, filename);

View File

@ -64,6 +64,9 @@ public class BalanceServiceImpl extends ServiceImpl<BalanceMapper, Balance> impl
BigDecimal previousBalance = getBalanceByClientIdAndCurrency(clientId, currency);
BigDecimal currentBalance = previousBalance.subtract(invoice.getFinalAmount());
BigDecimal purchaseFees = purchaseOrderService.getPurchaseFeesByInvoiceCode(invoiceCode);
if(purchaseFees == null) {
purchaseFees = BigDecimal.ZERO;
}
currentBalance = currentBalance.subtract(purchaseFees);
SysUser sysUser = new SysUser();
Balance balance = Balance.of(sysUser.getUsername(), clientId, invoice.getCurrencyId(), Balance.OperationType.Debit.name(), invoice.getId(), currentBalance);

View File

@ -180,4 +180,9 @@ public class ClientServiceImpl extends ServiceImpl<ClientMapper, Client> impleme
public Client getClientFromCredit(String invoiceNumber) {
return clientMapper.getClientFromCredit(invoiceNumber);
}
@Override
public List<String> getClientsByCode(List<String> clientCodes) {
return clientMapper.getClientsByCode(clientCodes);
}
}

View File

@ -86,4 +86,9 @@ public class ClientSkuServiceImpl extends ServiceImpl<ClientSkuMapper, ClientSku
public List<Sku> getUnpairedSkus() {
return clientSkuMapper.getUnpairedSkus();
}
@Override
public String getClientIdFromSkuId(String skuId) {
return clientSkuMapper.getClientIdFromSkuId(skuId);
}
}

View File

@ -129,7 +129,8 @@ public class InvoiceServiceImpl extends ServiceImpl<InvoiceMapper, Invoice> impl
else if(invoiceType.equalsIgnoreCase(COMPLETE.name())) {
ShippingInvoice shippingInvoice = shippingInvoiceService.getById(id);
PurchaseOrder purchase = purchaseOrderService.getPurchaseByInvoiceNumberAndClientId(invoiceNumber, clientId);
if(shippingInvoice == null || purchase == null) {
// we don't test if purchase is null, because certain clients can have complete invoices without purchase orders, eg: LA, AP
if(shippingInvoice == null) {
log.error("Error while cancelling complete invoice : invoice or purchase not found for id : {}", id);
return false;
}
@ -143,23 +144,29 @@ public class InvoiceServiceImpl extends ServiceImpl<InvoiceMapper, Invoice> impl
log.error("Complete invoice {} older than {} days, client is not allowed to cancel it", invoiceNumber, CANCEL_DAYS_LIMIT);
return false;
}
if(!isEmployee && purchase.isOrdered()) {
log.error("Purchase order {} for invoice {} is already ordered, cannot be cancelled", id, invoiceNumber);
return false;
if(purchase != null) {
if (!isEmployee && purchase.isOrdered()) {
log.error("Purchase order {} for invoice {} is already ordered, cannot be cancelled", id, invoiceNumber);
return false;
}
if (purchase.getInventoryDocumentString() != null && !purchase.getInventoryDocumentString().isEmpty())
shippingInvoiceService.deleteAttachmentFile(purchase.getInventoryDocumentString());
if (purchase.getPaymentDocumentString() != null && !purchase.getPaymentDocumentString().isEmpty())
shippingInvoiceService.deleteAttachmentFile(purchase.getPaymentDocumentString());
purchaseOrderService.cancelInvoice(purchase.getId());
} else {
log.info("Purchase order not found for invoice : {}", invoiceNumber);
}
if(purchase.getInventoryDocumentString() != null && !purchase.getInventoryDocumentString().isEmpty())
shippingInvoiceService.deleteAttachmentFile(purchase.getInventoryDocumentString());
if(purchase.getPaymentDocumentString() != null && !purchase.getPaymentDocumentString().isEmpty())
shippingInvoiceService.deleteAttachmentFile(purchase.getPaymentDocumentString());
platformOrderContentService.cancelInvoice(invoiceNumber, clientId);
platformOrderMabangService.deleteOrderRemark(invoiceNumber);
platformOrderService.removePurchaseInvoiceNumber(invoiceNumber, clientId);
platformOrderService.cancelInvoice(invoiceNumber, clientId);
purchaseOrderService.cancelInvoice(purchase.getId());
shippingInvoiceService.cancelInvoice(id);
// reminder : in complete invoicing balance is updated only once with shipping invoice ID and the amount is the sum of shipping fees and purchase fees
amount = shippingInvoice.getFinalAmount().add(purchase.getFinalAmount());
amount = shippingInvoice.getFinalAmount();
if(purchase != null)
amount = amount.add(purchase.getFinalAmount());
currencyId = shippingInvoice.getCurrencyId();
}
else if(invoiceType.equalsIgnoreCase(CREDIT.name())) {

View File

@ -66,6 +66,8 @@ public class PurchaseOrderServiceImpl extends ServiceImpl<PurchaseOrderMapper, P
private ICurrencyService currencyService;
@Autowired
private IInvoiceNumberReservationService invoiceNumberReservationService;
@Autowired
private ISecurityService securityService;
/**
* Directory where payment documents are put
@ -73,8 +75,10 @@ public class PurchaseOrderServiceImpl extends ServiceImpl<PurchaseOrderMapper, P
@Value("${jeecg.path.save}")
private String PAYMENT_DOC_DIR;
@Value("${jeecg.path.purchaseTemplatePath}")
private String INVOICE_TEMPLATE;
@Value("${jeecg.path.purchaseTemplatePath_EU}")
private String INVOICE_TEMPLATE_EU;
@Value("${jeecg.path.purchaseTemplatePath_US}")
private String INVOICE_TEMPLATE_US;
@Value("${jeecg.path.purchaseInvoiceDir}")
private String INVOICE_DIR;
@ -403,7 +407,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) throws UserException {
Map<PlatformOrder, List<PlatformOrderContent>> orderAndContent, List<String> ordersWithStock) throws UserException {
Objects.requireNonNull(orderAndContent);
List<OrderContentDetail> details = platformOrderService.searchPurchaseOrderDetail(skuQuantities);
@ -438,7 +442,15 @@ public class PurchaseOrderServiceImpl extends ServiceImpl<PurchaseOrderMapper, P
purchaseOrderContentMapper.addAll(username, purchaseID, entries);
// 2.1 save extra sku
skuService.addInventory(skuQuantities, orderAndContent.keySet().stream().map(PlatformOrder::getId).collect(Collectors.toList()));
List<String> orderIds = orderAndContent.keySet().stream()
.map(PlatformOrder::getId)
.collect(Collectors.toList());
if( ordersWithStock != null && !ordersWithStock.isEmpty()) {
orderIds = orderIds.stream()
.filter(orderId -> !ordersWithStock.contains(orderId))
.collect(Collectors.toList());
}
skuService.addInventory(skuQuantities, orderIds);
// 3. save the application of promotion information
List<PromotionHistoryEntry> promotionHistoryEntries = details.stream()
@ -459,6 +471,9 @@ public class PurchaseOrderServiceImpl extends ServiceImpl<PurchaseOrderMapper, P
}
for (Map.Entry<PlatformOrder, List<PlatformOrderContent>> entry : orderAndContent.entrySet()) {
PlatformOrder platformOrder = entry.getKey();
if(ordersWithStock != null && ordersWithStock.contains(platformOrder.getId()))
continue;
// TODO : what to do with order status ????????
List<PlatformOrderContent> orderContents = entry.getValue();
platformOrder.setStatus(PlatformOrder.Status.Purchasing.code);
for (PlatformOrderContent orderContent : orderContents) {
@ -533,22 +548,26 @@ public class PurchaseOrderServiceImpl extends ServiceImpl<PurchaseOrderMapper, P
@Override
public InvoiceMetaData makeInvoice(String purchaseID) throws IOException, UserException {
Client client = clientService.getCurrentClient();
if(client == null) {
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
if (sysUser.getOrgCode().contains("A01") || sysUser.getOrgCode().contains("A03")) {
client = clientService.getClientFromPurchase(purchaseID);
}
else
boolean isEmployee = securityService.checkIsEmployee();
Client client;
if(!isEmployee) {
client = clientService.getCurrentClient();
if (client == null)
throw new UserException("User is not a client");
}
} else
client = clientService.getClientFromPurchase(purchaseID);
List<PurchaseInvoiceEntry> purchaseOrderSkuList = purchaseOrderContentMapper.selectInvoiceDataByID(purchaseID);
List<PromotionDetail> promotionDetails = skuPromotionHistoryMapper.selectPromotionByPurchase(purchaseID);
String invoiceCode = purchaseOrderMapper.selectById(purchaseID).getInvoiceNumber();
BigDecimal eurToUsd = exchangeRatesMapper.getLatestExchangeRate("EUR", "USD");
String filename = "Invoice N°" + invoiceCode + " (" + client.getInvoiceEntity() + ").xlsx";
Path template = Paths.get(INVOICE_TEMPLATE);
Path template;
if (client.getCurrency().equals("USD")) {
template = Paths.get(INVOICE_TEMPLATE_US);
} else {
template = Paths.get(INVOICE_TEMPLATE_EU);
}
Path newInvoice = Paths.get(INVOICE_DIR, filename);
Files.copy(template, newInvoice, StandardCopyOption.REPLACE_EXISTING);
PurchaseInvoice pv = new PurchaseInvoice(client, invoiceCode, "Purchase Invoice", purchaseOrderSkuList, promotionDetails, eurToUsd);
@ -558,6 +577,7 @@ public class PurchaseOrderServiceImpl extends ServiceImpl<PurchaseOrderMapper, P
@Override
public InvoiceMetaData makeInvoiceTest(int nbOfLines) throws Exception {
Client client = clientService.getClientBySku("test");
client.setCurrency("USD");
List<PurchaseInvoiceEntry> purchaseOrderSkuList = new ArrayList<>();
// -5 because we have at least 5 lines of promotions
for (int i = 0; i < nbOfLines - 5; i++) {
@ -571,7 +591,11 @@ public class PurchaseOrderServiceImpl extends ServiceImpl<PurchaseOrderMapper, P
BigDecimal eurToUsd = exchangeRatesMapper.getLatestExchangeRate("EUR", "USD");
String filename = "Invoice N°" + invoiceCode + " (" + client.getInvoiceEntity() + ")_" + nbOfLines + ".xlsx";
Path template = Paths.get(INVOICE_TEMPLATE);
Path template;
if(client.getCurrency().equals("USD"))
template = Paths.get(INVOICE_TEMPLATE_US);
else
template = Paths.get(INVOICE_TEMPLATE_EU);
Path newInvoice = Paths.get(INVOICE_TEST_DIR, filename);
Files.copy(template, newInvoice, StandardCopyOption.REPLACE_EXISTING);
PurchaseInvoice pv = new PurchaseInvoice(client, invoiceCode, "Purchase Invoice", purchaseOrderSkuList, promotionDetails, eurToUsd);

View File

@ -0,0 +1,23 @@
package org.jeecg.modules.business.vo;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
public class ManualInvoiceOrderParam {
private final String clientID;
private final List<String> orderIds;
private final String type;
private final List<String> ordersWithStock = new ArrayList<>();
public ManualInvoiceOrderParam(@JsonProperty("clientID") String clientID, @JsonProperty("orderIds") List<String> orderIds, @JsonProperty("type") String type, @JsonProperty("ordersWithStock") List<String> ordersWithStock) {
this.clientID = clientID;
this.orderIds = orderIds;
this.type = type;
if (ordersWithStock != null) {
this.ordersWithStock.addAll(ordersWithStock);
}
}
}

View File

@ -229,7 +229,8 @@ jeecg:
creditInvoiceDir: /wia/invoices/credit
# purchase invoice template
purchaseTemplatePath: /wia/files/Purchase_Invoice_Template.xlsx
purchaseTemplatePath_EU: /wia/files/Purchase_Invoice_Template_EU.xlsx
purchaseTemplatePath_US: /wia/files/Purchase_Invoice_Template_US.xlsx
# where to store generated file
purchaseInvoiceDir: /wia/invoices/purchase
purchaseInvoiceTestDir: /wia/invoices/test/purchase