Compare commits

...
4 Commits
Author SHA1 Message Date
timi 425922fd04 Merge pull request 'v1.0.44' (#56) from dev into master
Reviewed-on: #56
2026-09-12 17:42:51 +00:00
Timi b24d6b3a01 v1.0.44
CI / build-deploy (pull_request) Successful in 25s
CI / notify-on-failure (pull_request) Skipped
2026-09-13 01:37:51 +08:00
Timi c35132d06b total_earned DESC order for point account 2026-09-13 01:37:41 +08:00
Timi 91072d6d50 auto complete order 2026-09-13 01:25:48 +08:00
12 changed files with 222 additions and 8 deletions
+1 -1
View File
@@ -11,7 +11,7 @@
<groupId>com.imyeyu.timiserverapi</groupId>
<artifactId>TimiServerAPI</artifactId>
<version>1.0.43</version>
<version>1.0.44</version>
<packaging>jar</packaging>
<name>TimiServerAPI</name>
<description>imyeyu.com API</description>
@@ -0,0 +1,17 @@
package com.imyeyu.api.modules.gao.service;
import com.imyeyu.api.modules.gao.entity.GaoSalesOrder;
import com.imyeyu.api.modules.gao.vo.GaoOrderStraightThroughRequest;
/// GAO 订单创建直通服务
///
/// @author Codex
/// @since 2026-09-13
public interface GaoOrderStraightThroughService {
/// 在订单创建事务内完成可选的全额收款和全部履约
///
/// @param order 销售订单
/// @param straightThrough 直通请求,空为不直通
void complete(GaoSalesOrder order, GaoOrderStraightThroughRequest straightThrough);
}
@@ -46,6 +46,9 @@ public interface GaoProductStockService {
/// 为履约预占库存
boolean hasSufficientForFulfillment(String storeId, List<GaoStockChangeItem> items);
/// 获取履约库存缺口提示,无缺口时返回 null
String getFulfillmentStockShortageMessage(String storeId, List<GaoStockChangeItem> items);
/// 为履约预占库存
List<GaoProductStock> reserveForFulfillment(String storeId, String bizId, List<GaoStockChangeItem> items, String requestNo, String operatorUserId);
@@ -13,6 +13,7 @@ import com.imyeyu.api.modules.gao.entity.GaoSalesOrder;
import com.imyeyu.api.modules.gao.entity.GaoSalesOrderItem;
import com.imyeyu.api.modules.gao.mapper.GaoCustomerPackageMapper;
import com.imyeyu.api.modules.gao.service.GaoSalesOrderService;
import com.imyeyu.api.modules.gao.service.GaoOrderStraightThroughService;
import com.imyeyu.api.modules.gao.service.GaoCustomerService;
import com.imyeyu.api.modules.gao.service.GaoCustomerPackageService;
import com.imyeyu.api.modules.gao.service.GaoPackageConfigService;
@@ -67,6 +68,7 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService<Ga
private final GaoProductSkuService skuService;
private final GaoCustomerService customerService;
private final GaoSalesOrderService salesOrderService;
private final GaoOrderStraightThroughService straightThroughService;
private final GaoStoreScopeService storeScopeService;
private final AttachmentService attachmentService;
private final UserLoginService userLoginService;
@@ -168,6 +170,7 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService<Ga
if (existingOrder != null) {
GaoCustomerPackage existingPackage = getBySalesOrderId(existingOrder.getId());
verifyPurchaseIdempotency(existingOrder, existingPackage, request);
straightThroughService.complete(salesOrderService.detail(existingOrder.getId()), request.getStraightThrough());
return existingPackage;
}
Long purchasedAt = TimiJava.defaultIfNull(request.getPurchasedAt(), Time.now());
@@ -205,6 +208,8 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService<Ga
result.setStandardAmountSnapshot(config.getStandardAmount());
result.setRemark(request.getRemark());
super.create(result);
straightThroughService.complete(salesOrder, request.getStraightThrough());
salesOrder = salesOrderService.detail(salesOrder.getId());
result.setSalesOrder(salesOrder);
result.setItemList(salesOrder.getItemList());
result.setStatus(salesOrder.getStatus());
@@ -207,7 +207,8 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
List<GaoStockChangeItem> stockItems = buildStockItems(order.getStoreId(), sourceList);
applySkuSpecSnapshots(stockItems, fulfillmentItemList);
TimiException.requiredTrue(!stockItems.isEmpty(), "履约单没有需要占用的实物库存");
TimiException.requiredTrue(productStockService.hasSufficientForFulfillment(order.getStoreId(), stockItems), "库存仍然不足,不能准备履约");
String stockShortageMessage = productStockService.getFulfillmentStockShortageMessage(order.getStoreId(), stockItems);
TimiException.requiredTrue(stockShortageMessage == null, stockShortageMessage);
String operatorUserId = userLoginService.getRequireLoginUserId();
productStockService.reserveForFulfillment(order.getStoreId(), fulfillment.getId(), stockItems,
stockRequestNo(fulfillment, "RESERVE"), operatorUserId);
@@ -0,0 +1,129 @@
package com.imyeyu.api.modules.gao.service.implement;
import com.imyeyu.api.bean.ModuleCode;
import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
import com.imyeyu.api.modules.gao.bean.GaoPermissionCode;
import com.imyeyu.api.modules.gao.entity.GaoFundRecord;
import com.imyeyu.api.modules.gao.entity.GaoOrderFulfillment;
import com.imyeyu.api.modules.gao.entity.GaoSalesOrder;
import com.imyeyu.api.modules.gao.entity.GaoSalesOrderItem;
import com.imyeyu.api.modules.gao.service.GaoOrderFulfillmentService;
import com.imyeyu.api.modules.gao.service.GaoOrderPaymentService;
import com.imyeyu.api.modules.gao.service.GaoOrderStraightThroughService;
import com.imyeyu.api.modules.gao.vo.GaoOrderFulfillmentCreateRequest;
import com.imyeyu.api.modules.gao.vo.GaoOrderFulfillmentActionRequest;
import com.imyeyu.api.modules.gao.vo.GaoOrderFulfillmentItemRequest;
import com.imyeyu.api.modules.gao.vo.GaoOrderPaymentReceiveRequest;
import com.imyeyu.api.modules.gao.vo.GaoOrderStraightThroughRequest;
import com.imyeyu.api.modules.user.service.PermissionChecker;
import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiException;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
/// GAO 订单创建直通服务实现
///
/// @author Codex
/// @since 2026-09-13
@Service
@RequiredArgsConstructor
public class GaoOrderStraightThroughServiceImplement implements GaoOrderStraightThroughService {
private final GaoOrderPaymentService paymentService;
private final GaoOrderFulfillmentService fulfillmentService;
private final PermissionChecker permissionChecker;
@Override
@Transactional(TimiServerDBConfig.ROLLBACKER)
public void complete(GaoSalesOrder order, GaoOrderStraightThroughRequest straightThrough) {
if (straightThrough == null) {
return;
}
boolean receiveFullPayment = Boolean.TRUE.equals(straightThrough.getReceiveFullPayment());
boolean fulfillAll = Boolean.TRUE.equals(straightThrough.getFulfillAll());
if (!receiveFullPayment && !fulfillAll) {
return;
}
TimiException.required(order, "not found sales order");
TimiException.required(order.getId(), "not found salesOrderId");
TimiException.requiredTrue(!fulfillAll || receiveFullPayment || order.getSaleAmount() == 0,
"全部履约直通必须同时直接全额收款");
checkPermissions(order.getOrderType(), receiveFullPayment, fulfillAll);
if (receiveFullPayment) {
settle(order, straightThrough);
}
if (fulfillAll) {
fulfill(order, straightThrough);
}
}
private void checkPermissions(GaoSalesOrder.OrderType orderType, boolean receiveFullPayment, boolean fulfillAll) {
List<String> permissionCodeList = new ArrayList<>();
permissionCodeList.add(orderType == GaoSalesOrder.OrderType.PACKAGE
? GaoPermissionCode.CUSTOMER_PACKAGE_CREATE.getValue() : GaoPermissionCode.SALES_ORDER_CREATE.getValue());
if (receiveFullPayment) {
permissionCodeList.add(GaoPermissionCode.FUND_RECEIVE_CREATE.getValue());
permissionCodeList.add(GaoPermissionCode.FUND_RECEIVE_VERIFY.getValue());
}
if (fulfillAll) {
permissionCodeList.add(GaoPermissionCode.ORDER_FULFILLMENT_CREATE.getValue());
permissionCodeList.add(GaoPermissionCode.ORDER_FULFILLMENT_VERIFY.getValue());
}
permissionChecker.checkAll(ModuleCode.GAO, permissionCodeList.toArray(String[]::new));
}
private void settle(GaoSalesOrder order, GaoOrderStraightThroughRequest straightThrough) {
if (order.getSaleAmount() == null || order.getSaleAmount() == 0) {
return;
}
GaoOrderPaymentReceiveRequest paymentRequest = new GaoOrderPaymentReceiveRequest();
paymentRequest.setSalesOrderId(order.getId());
paymentRequest.setRequestNo("ORDER-%s-FULL-PAY".formatted(order.getId()));
paymentRequest.setAmount(order.getSaleAmount());
paymentRequest.setOccurredAt(TimiJava.defaultIfNull(straightThrough.getPaymentOccurredAt(), order.getPurchasedAt()));
paymentRequest.setAttachmentList(straightThrough.getPaymentAttachmentList());
paymentRequest.setRemark(TimiJava.defaultIfEmpty(straightThrough.getPaymentRemark(), "随订单创建直接全额收款"));
GaoFundRecord record = paymentService.receive(paymentRequest);
if (record.getStatus() == GaoFundRecord.Status.PENDING) {
paymentService.verify(record.getId());
return;
}
TimiException.requiredTrue(record.getStatus() == GaoFundRecord.Status.VERIFIED, "直通收款记录不是已核销状态");
}
private void fulfill(GaoSalesOrder order, GaoOrderStraightThroughRequest straightThrough) {
List<GaoSalesOrderItem> orderItemList = order.getItemList();
TimiException.requiredTrue(orderItemList != null && !orderItemList.isEmpty(), "销售订单没有可履约明细");
List<GaoOrderFulfillmentItemRequest> itemList = orderItemList.stream().map(item -> {
GaoOrderFulfillmentItemRequest result = new GaoOrderFulfillmentItemRequest();
result.setSourceOrderItemId(item.getId());
result.setSkuUnitId(item.getSkuUnitId());
result.setQuantity(item.getQuantity());
return result;
}).toList();
GaoOrderFulfillmentCreateRequest fulfillmentRequest = new GaoOrderFulfillmentCreateRequest();
fulfillmentRequest.setSalesOrderId(order.getId());
fulfillmentRequest.setRequestNo("ORDER-%s-FULL-FULFILL".formatted(order.getId()));
fulfillmentRequest.setDeliveredAt(TimiJava.defaultIfNull(straightThrough.getDeliveredAt(), order.getPurchasedAt()));
fulfillmentRequest.setItemList(itemList);
fulfillmentRequest.setAttachmentList(straightThrough.getFulfillmentAttachmentList());
fulfillmentRequest.setRemark(TimiJava.defaultIfEmpty(straightThrough.getFulfillmentRemark(), "随订单创建直接全部履约"));
GaoOrderFulfillment fulfillment = fulfillmentService.create(fulfillmentRequest);
if (fulfillment.getStatus() == GaoOrderFulfillment.Status.PENDING) {
fulfillmentService.verify(actionRequest(fulfillment.getId()));
return;
}
TimiException.requiredTrue(fulfillment.getStatus() == GaoOrderFulfillment.Status.VERIFIED,
fulfillment.getStatus() == GaoOrderFulfillment.Status.WAITING_STOCK ? "库存不足,不能全部履约直通" : "直通履约单不是已核销状态");
}
private GaoOrderFulfillmentActionRequest actionRequest(String fulfillmentId) {
GaoOrderFulfillmentActionRequest request = new GaoOrderFulfillmentActionRequest();
request.setFulfillmentId(fulfillmentId);
return request;
}
}
@@ -156,19 +156,30 @@ public class GaoProductStockServiceImplement implements GaoProductStockService {
@Override
@Transactional(TimiServerDBConfig.ROLLBACKER)
public boolean hasSufficientForFulfillment(String storeId, List<GaoStockChangeItem> items) {
return getFulfillmentStockShortageMessage(storeId, items) == null;
}
@Override
@Transactional(TimiServerDBConfig.ROLLBACKER)
public String getFulfillmentStockShortageMessage(String storeId, List<GaoStockChangeItem> items) {
TimiException.required(storeId, "not found storeId");
if (items == null || items.isEmpty()) {
return true;
return null;
}
List<GaoStockChangeItem> sortedItems = new ArrayList<>(items);
sortedItems.sort(Comparator.comparing(GaoStockChangeItem::getSkuId));
List<String> shortageList = new ArrayList<>();
for (GaoStockChangeItem item : sortedItems) {
GaoProductStock stock = stockMapper.selectForUpdate(storeId, item.getSkuId());
if (stock == null || decimal(stock.getOnHandQuantity()).subtract(decimal(stock.getReservedQuantity())).compareTo(item.getBaseQuantity()) < 0) {
return false;
BigDecimal availableQuantity = stock == null ? ZERO : decimal(stock.getOnHandQuantity()).subtract(decimal(stock.getReservedQuantity()));
BigDecimal shortageQuantity = item.getBaseQuantity().subtract(availableQuantity);
if (0 < shortageQuantity.signum()) {
shortageList.add("%s(%s)可用 %s %s,缺少 %s %s".formatted(
item.getProductNameSnapshot(), item.getSkuNameSnapshot(), availableQuantity.toPlainString(), item.getUnitSnapshot(),
shortageQuantity.toPlainString(), item.getUnitSnapshot()));
}
}
return true;
return shortageList.isEmpty() ? null : "库存不足:" + String.join(";", shortageList);
}
@Override
@@ -21,6 +21,7 @@ import com.imyeyu.api.modules.gao.service.GaoCustomerService;
import com.imyeyu.api.modules.gao.service.GaoEventRecordService;
import com.imyeyu.api.modules.gao.service.GaoFundRecordService;
import com.imyeyu.api.modules.gao.service.GaoOrderFulfillmentService;
import com.imyeyu.api.modules.gao.service.GaoOrderStraightThroughService;
import com.imyeyu.api.modules.gao.service.GaoPointTriggerService;
import com.imyeyu.api.modules.gao.service.GaoProductService;
import com.imyeyu.api.modules.gao.service.GaoProductSkuService;
@@ -80,6 +81,7 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService<GaoSale
private final GaoSalesOrderItemMapper itemMapper;
private final GaoFundRecordService fundRecordService;
private final ObjectProvider<GaoOrderFulfillmentService> fulfillmentServiceProvider;
private final ObjectProvider<GaoOrderStraightThroughService> straightThroughServiceProvider;
private final GaoCustomerService customerService;
private final GaoProductService productService;
private final GaoProductSkuService skuService;
@@ -143,7 +145,9 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService<GaoSale
@Transactional(TimiServerDBConfig.ROLLBACKER)
@Override
public GaoSalesOrder createDirectProductOrder(GaoDirectProductOrderCreateRequest request) {
return createDirectInternal(request);
GaoSalesOrder order = createDirectInternal(request);
straightThroughServiceProvider.getObject().complete(order, request.getStraightThrough());
return detail(order.getId());
}
@OperationAudit(AuditOperation.GAO_SALES_ORDER_UPDATE)
@@ -33,6 +33,9 @@ public class GaoCustomerPackagePurchaseRequest {
/// 购买照片
private List<Attachment> attachmentList;
/// 创建订单后的直通处理
private GaoOrderStraightThroughRequest straightThrough;
/// 备注
private String remark;
}
@@ -30,6 +30,9 @@ public class GaoDirectProductOrderCreateRequest {
/// 购买照片
private List<Attachment> attachmentList;
/// 创建订单后的直通处理
private GaoOrderStraightThroughRequest straightThrough;
/// 备注
private String remark;
}
@@ -0,0 +1,38 @@
package com.imyeyu.api.modules.gao.vo;
import com.imyeyu.api.modules.common.entity.Attachment;
import lombok.Data;
import java.util.List;
/// GAO 订单创建直通请求
///
/// @author Codex
/// @since 2026-09-13
@Data
public class GaoOrderStraightThroughRequest {
/// true 为直接全额收款并核销
private Boolean receiveFullPayment;
/// 实际收款时间,空为订单下单时间
private Long paymentOccurredAt;
/// 收款凭证照片
private List<Attachment> paymentAttachmentList;
/// 收款备注
private String paymentRemark;
/// true 为直接全部履约并核销
private Boolean fulfillAll;
/// 实际履约时间,空为订单下单时间
private Long deliveredAt;
/// 履约凭证照片
private List<Attachment> fulfillmentAttachmentList;
/// 履约备注
private String fulfillmentRemark;
}
@@ -53,7 +53,7 @@
INNER JOIN `gao_customer` `c` ON `c`.`id` = `a`.`customer_id` AND `c`.`store_id` = `a`.`store_id`
WHERE
<include refid="queryWhere"/>
ORDER BY `a`.`created_at` DESC, `a`.`id` DESC
ORDER BY `a`.`total_earned` DESC, `a`.`created_at` DESC, `a`.`id` DESC
LIMIT #{offset}, #{limit}
</select>