diff --git a/pom.xml b/pom.xml
index ee44e77..6d6d03c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -11,7 +11,7 @@
com.imyeyu.timiserverapi
TimiServerAPI
- 1.0.43
+ 1.0.44
jar
TimiServerAPI
imyeyu.com API
diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoOrderStraightThroughService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoOrderStraightThroughService.java
new file mode 100644
index 0000000..b624eb7
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoOrderStraightThroughService.java
@@ -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);
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoProductStockService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoProductStockService.java
index 43a9999..f85156c 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/service/GaoProductStockService.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoProductStockService.java
@@ -46,6 +46,9 @@ public interface GaoProductStockService {
/// 为履约预占库存
boolean hasSufficientForFulfillment(String storeId, List items);
+ /// 获取履约库存缺口提示,无缺口时返回 null
+ String getFulfillmentStockShortageMessage(String storeId, List items);
+
/// 为履约预占库存
List reserveForFulfillment(String storeId, String bizId, List items, String requestNo, String operatorUserId);
diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerPackageServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerPackageServiceImplement.java
index c6faeb0..580e226 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerPackageServiceImplement.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerPackageServiceImplement.java
@@ -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 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);
diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoOrderStraightThroughServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoOrderStraightThroughServiceImplement.java
new file mode 100644
index 0000000..2e53cc5
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoOrderStraightThroughServiceImplement.java
@@ -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 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 orderItemList = order.getItemList();
+ TimiException.requiredTrue(orderItemList != null && !orderItemList.isEmpty(), "销售订单没有可履约明细");
+ List 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;
+ }
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoProductStockServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoProductStockServiceImplement.java
index f2d0dae..bc7ef7c 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoProductStockServiceImplement.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoProductStockServiceImplement.java
@@ -156,19 +156,30 @@ public class GaoProductStockServiceImplement implements GaoProductStockService {
@Override
@Transactional(TimiServerDBConfig.ROLLBACKER)
public boolean hasSufficientForFulfillment(String storeId, List items) {
+ return getFulfillmentStockShortageMessage(storeId, items) == null;
+ }
+
+ @Override
+ @Transactional(TimiServerDBConfig.ROLLBACKER)
+ public String getFulfillmentStockShortageMessage(String storeId, List items) {
TimiException.required(storeId, "not found storeId");
if (items == null || items.isEmpty()) {
- return true;
+ return null;
}
List sortedItems = new ArrayList<>(items);
sortedItems.sort(Comparator.comparing(GaoStockChangeItem::getSkuId));
+ List 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
diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoSalesOrderServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoSalesOrderServiceImplement.java
index ed77d79..1c1eba9 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoSalesOrderServiceImplement.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoSalesOrderServiceImplement.java
@@ -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 fulfillmentServiceProvider;
+ private final ObjectProvider straightThroughServiceProvider;
private final GaoCustomerService customerService;
private final GaoProductService productService;
private final GaoProductSkuService skuService;
@@ -143,7 +145,9 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService attachmentList;
+ /// 创建订单后的直通处理
+ private GaoOrderStraightThroughRequest straightThrough;
+
/// 备注
private String remark;
}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoDirectProductOrderCreateRequest.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoDirectProductOrderCreateRequest.java
index 2fd8703..73017f5 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoDirectProductOrderCreateRequest.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoDirectProductOrderCreateRequest.java
@@ -30,6 +30,9 @@ public class GaoDirectProductOrderCreateRequest {
/// 购买照片
private List attachmentList;
+ /// 创建订单后的直通处理
+ private GaoOrderStraightThroughRequest straightThrough;
+
/// 备注
private String remark;
}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoOrderStraightThroughRequest.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoOrderStraightThroughRequest.java
new file mode 100644
index 0000000..6632167
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoOrderStraightThroughRequest.java
@@ -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 paymentAttachmentList;
+
+ /// 收款备注
+ private String paymentRemark;
+
+ /// true 为直接全部履约并核销
+ private Boolean fulfillAll;
+
+ /// 实际履约时间,空为订单下单时间
+ private Long deliveredAt;
+
+ /// 履约凭证照片
+ private List fulfillmentAttachmentList;
+
+ /// 履约备注
+ private String fulfillmentRemark;
+}
diff --git a/src/main/resources/mapper/timi-server/gao/GaoPointAccountMapper.xml b/src/main/resources/mapper/timi-server/gao/GaoPointAccountMapper.xml
index 5451847..4918411 100644
--- a/src/main/resources/mapper/timi-server/gao/GaoPointAccountMapper.xml
+++ b/src/main/resources/mapper/timi-server/gao/GaoPointAccountMapper.xml
@@ -53,7 +53,7 @@
INNER JOIN `gao_customer` `c` ON `c`.`id` = `a`.`customer_id` AND `c`.`store_id` = `a`.`store_id`
WHERE
- 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}