Compare commits

..
52 Commits
Author SHA1 Message Date
timi d22df698b6 Merge pull request 'v1.0.41' (#53) from dev into master
Reviewed-on: #53
2026-09-11 15:55:02 +00:00
timi 1771bf9909 Merge pull request 'v1.0.40' (#52) from dev into master
Reviewed-on: #52
2026-09-10 16:29:28 +00:00
timi 15d69f9361 Merge pull request 'v1.0.39' (#51) from dev into master
Reviewed-on: #51
2026-09-10 06:10:30 +00:00
timi 2b4089ef59 Merge pull request 'v1.0.38' (#50) from dev into master
Reviewed-on: #50
2026-09-09 17:33:25 +00:00
timi 20c3e1f422 Merge pull request 'v1.0.37' (#49) from dev into master
Reviewed-on: #49
2026-09-09 06:21:55 +00:00
timi 3b19d9e930 Merge pull request 'v1.0.36' (#48) from dev into master
Reviewed-on: #48
2026-09-08 17:52:47 +00:00
timi 5fd11e9caf Merge pull request 'v1.0.35' (#47) from dev into master
Reviewed-on: #47
2026-09-07 17:06:28 +00:00
timi 0b98dcdfe9 Merge pull request 'v1.0.34' (#46) from dev into master
Reviewed-on: #46
2026-09-07 15:11:06 +00:00
timi b373cbbe46 Merge pull request 'v1.0.33' (#45) from dev into master
Reviewed-on: #45
2026-09-07 09:40:39 +00:00
timi acd97c7b9b Merge pull request 'v1.0.32' (#44) from dev into master
Reviewed-on: #44
2026-09-07 08:42:29 +00:00
timi 118bdb1666 Merge pull request 'v1.0.31' (#43) from dev into master
Reviewed-on: #43
2026-09-07 07:40:03 +00:00
timi 944de228f7 Merge pull request 'v1.0.30' (#42) from dev into master
Reviewed-on: #42
2026-09-01 17:44:48 +00:00
timi 25c9cd4dfc Merge pull request 'v1.0.30' (#41) from dev into master
Reviewed-on: #41
2026-09-01 17:42:44 +00:00
timi 882cd226ec Merge pull request 'v1.0.30' (#40) from dev into master
Reviewed-on: #40
2026-09-01 17:38:50 +00:00
timi feb4d7af66 Merge pull request 'v1.0.28' (#38) from dev into master
Reviewed-on: #38
2026-08-30 15:51:37 +00:00
timi d75ab1ed5f Merge pull request 'v1.0.27' (#37) from dev into master
Reviewed-on: #37
2026-08-29 17:49:23 +00:00
timi a501eb7443 Merge pull request 'v1.0.26' (#36) from dev into master
Reviewed-on: #36
2026-08-28 16:52:15 +00:00
timi 19941d9480 Merge pull request 'v1.0.25' (#35) from dev into master
Reviewed-on: #35
2026-08-23 17:50:25 +00:00
timi cd3d522d4f Merge pull request 'v1.0.24' (#34) from dev into master
Reviewed-on: #34
2026-08-22 09:56:13 +00:00
timi 181c5078e9 Merge pull request 'v1.0.23' (#33) from dev into master
Reviewed-on: #33
2026-08-22 09:51:37 +00:00
timi e97f46e6b4 Merge pull request 'v1.0.22' (#32) from dev into master
Reviewed-on: #32
2026-08-21 17:31:33 +00:00
timi c0f89deb83 Merge pull request 'v1.0.22' (#31) from dev into master
Reviewed-on: #31
2026-08-21 17:28:32 +00:00
timi ce695463d3 Merge pull request 'v1.0.21' (#30) from dev into master
Reviewed-on: #30
2026-08-21 07:29:34 +00:00
timi 83c683d473 Merge pull request 'v1.0.20' (#29) from dev into master
Reviewed-on: #29
2026-08-21 07:25:32 +00:00
timi 9555474b0b Merge pull request 'v1.0.19' (#28) from dev into master
Reviewed-on: #28
2026-08-21 05:52:36 +00:00
timi a150f9e017 Merge pull request 'v1.0.18' (#27) from dev into master
Reviewed-on: #27
2026-08-20 17:40:22 +00:00
timi ecd2fa1c33 Merge pull request 'v1.0.17' (#26) from dev into master
Reviewed-on: #26
2026-08-19 17:30:35 +00:00
timi e3236d262d Merge pull request 'v1.0.16' (#25) from dev into master
Reviewed-on: #25
2026-08-16 03:37:08 +00:00
timi c8e2e16ba5 Merge pull request 'v1.0.15' (#24) from dev into master
Reviewed-on: #24
2026-08-15 17:49:51 +00:00
timi 99b44faf09 Merge pull request 'v1.0.15' (#23) from dev into master
Reviewed-on: #23
2026-08-15 17:45:26 +00:00
timi b3e3cbf84b Merge pull request 'v1.0.14' (#22) from dev into master
Reviewed-on: #22
2026-08-14 17:31:11 +00:00
timi 0aee11d824 Merge pull request 'v1.0.14' (#21) from dev into master
Reviewed-on: #21
2026-08-14 17:26:10 +00:00
timi fcf358a768 Merge pull request 'v1.0.13' (#20) from dev into master
Reviewed-on: #20
2026-08-13 09:52:26 +00:00
timi 619369104c Merge pull request 'v1.0.12' (#19) from dev into master
Reviewed-on: #19
2026-08-12 17:02:43 +00:00
timi 4f1408b9d0 Merge pull request 'v1.0.11' (#18) from dev into master
Reviewed-on: #18
2026-08-11 16:49:08 +00:00
timi 029eea70cc Merge pull request 'v1.0.10' (#17) from dev into master
Reviewed-on: #17
2026-08-11 16:45:36 +00:00
timi 22b1e65385 Merge pull request 'v1.0.9' (#16) from dev into master
Reviewed-on: #16
2026-08-11 16:01:10 +00:00
timi 2170c48763 Merge pull request 'v1.0.8' (#15) from dev into master
Reviewed-on: #15
2026-08-03 16:36:19 +00:00
timi c2d1c48aae Merge pull request 'v0.0.18' (#14) from dev into master
Reviewed-on: #14
2026-08-03 11:59:20 +00:00
timi e14958c643 Merge pull request 'v0.0.18' (#13) from dev into master
Reviewed-on: #13
2026-08-03 11:58:16 +00:00
timi da288338cd Merge pull request 'v1.0.7' (#12) from dev into master
Reviewed-on: #12
2026-08-01 09:34:18 +00:00
timi ea430f2468 Merge pull request 'v1.0.6' (#11) from dev into master
Reviewed-on: #11
2026-08-01 09:03:48 +00:00
timi f76f79744a Merge pull request 'v1.0.5' (#10) from dev into master
Reviewed-on: #10
2026-08-01 08:52:52 +00:00
timi 19daaff907 Merge pull request 'v1.0.4' (#9) from dev into master
Reviewed-on: #9
2026-08-01 03:13:00 +00:00
timi db94d8f932 Merge pull request 'v1.0.3' (#8) from dev into master
Reviewed-on: #8
2026-07-31 17:19:48 +00:00
timi 53618fe362 Merge pull request 'v1.0.3' (#7) from dev into master
Reviewed-on: #7
2026-07-31 17:13:37 +00:00
timi 523edffc4e Merge pull request 'v1.0.2' (#6) from dev into master
Reviewed-on: #6
2026-04-13 10:28:11 +00:00
timi c7ddb1a8b0 Merge pull request 'v1.0.1' (#5) from dev into master
Reviewed-on: #5
2026-04-12 16:17:02 +00:00
timi 45c9fc814a Merge pull request 'v1.0.0' (#4) from dev into master
Reviewed-on: #4
2026-04-09 05:16:04 +00:00
timi 407dc13ac4 Merge pull request 'v1.0.0' (#3) from dev into master
Reviewed-on: #3
2026-04-09 04:09:16 +00:00
timi 0c06bf16c2 Merge pull request 'v1.0.0' (#2) from dev into master
Reviewed-on: #2
2026-04-09 04:03:43 +00:00
timi 1db39e77d3 Merge pull request 'v1.0.0' (#1) from dev into master
Reviewed-on: #1
2026-04-08 08:48:12 +00:00
18 changed files with 22 additions and 352 deletions
+1 -1
View File
@@ -11,7 +11,7 @@
<groupId>com.imyeyu.timiserverapi</groupId> <groupId>com.imyeyu.timiserverapi</groupId>
<artifactId>TimiServerAPI</artifactId> <artifactId>TimiServerAPI</artifactId>
<version>1.0.46</version> <version>1.0.41</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>TimiServerAPI</name> <name>TimiServerAPI</name>
<description>imyeyu.com API</description> <description>imyeyu.com API</description>
@@ -34,8 +34,6 @@ public class Article extends UUIDEntity implements SettingSupport {
/// GAO 门店日记 /// GAO 门店日记
GAO_STORE_DIARY, GAO_STORE_DIARY,
GAO_HELP,
GAO_SYSTEM, GAO_SYSTEM,
/// 反馈 /// 反馈
@@ -16,9 +16,6 @@ public interface GaoOrderPaymentService {
/// 登记收款 /// 登记收款
GaoFundRecord receive(GaoOrderPaymentReceiveRequest request); GaoFundRecord receive(GaoOrderPaymentReceiveRequest request);
/// 登记已收款并直接核销
GaoFundRecord receiveAndVerify(GaoOrderPaymentReceiveRequest request);
/// 核销收款 /// 核销收款
GaoFundRecord verify(String fundRecordId); GaoFundRecord verify(String fundRecordId);
@@ -1,17 +0,0 @@
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);
}
@@ -33,14 +33,6 @@ public interface GaoProductSkuService extends BaseService<GaoProductSku, String>
/// 查询并校验可销售 SKU 单位 /// 查询并校验可销售 SKU 单位
GaoProductSkuUnitSelection getSellableSkuUnit(String storeId, String skuId, String skuUnitId); GaoProductSkuUnitSelection getSellableSkuUnit(String storeId, String skuId, String skuUnitId);
/// 查询并校验可用于历史订单履约的 SKU 单位
///
/// @param storeId 门店 ID
/// @param skuId SKU ID
/// @param skuUnitId SKU 单位 ID
/// @return SKU 单位选择结果
GaoProductSkuUnitSelection getFulfillableSkuUnit(String storeId, String skuId, String skuUnitId);
/// 查询并校验 SKU 单位 /// 查询并校验 SKU 单位
GaoProductSkuUnitSelection getSkuUnit(String storeId, String skuId, String skuUnitId, boolean purchase); GaoProductSkuUnitSelection getSkuUnit(String storeId, String skuId, String skuUnitId, boolean purchase);
@@ -46,9 +46,6 @@ public interface GaoProductStockService {
/// 为履约预占库存 /// 为履约预占库存
boolean hasSufficientForFulfillment(String storeId, List<GaoStockChangeItem> items); 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); List<GaoProductStock> reserveForFulfillment(String storeId, String bizId, List<GaoStockChangeItem> items, String requestNo, String operatorUserId);
@@ -13,7 +13,6 @@ import com.imyeyu.api.modules.gao.entity.GaoSalesOrder;
import com.imyeyu.api.modules.gao.entity.GaoSalesOrderItem; import com.imyeyu.api.modules.gao.entity.GaoSalesOrderItem;
import com.imyeyu.api.modules.gao.mapper.GaoCustomerPackageMapper; import com.imyeyu.api.modules.gao.mapper.GaoCustomerPackageMapper;
import com.imyeyu.api.modules.gao.service.GaoSalesOrderService; 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.GaoCustomerService;
import com.imyeyu.api.modules.gao.service.GaoCustomerPackageService; import com.imyeyu.api.modules.gao.service.GaoCustomerPackageService;
import com.imyeyu.api.modules.gao.service.GaoPackageConfigService; import com.imyeyu.api.modules.gao.service.GaoPackageConfigService;
@@ -68,7 +67,6 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService<Ga
private final GaoProductSkuService skuService; private final GaoProductSkuService skuService;
private final GaoCustomerService customerService; private final GaoCustomerService customerService;
private final GaoSalesOrderService salesOrderService; private final GaoSalesOrderService salesOrderService;
private final GaoOrderStraightThroughService straightThroughService;
private final GaoStoreScopeService storeScopeService; private final GaoStoreScopeService storeScopeService;
private final AttachmentService attachmentService; private final AttachmentService attachmentService;
private final UserLoginService userLoginService; private final UserLoginService userLoginService;
@@ -170,7 +168,6 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService<Ga
if (existingOrder != null) { if (existingOrder != null) {
GaoCustomerPackage existingPackage = getBySalesOrderId(existingOrder.getId()); GaoCustomerPackage existingPackage = getBySalesOrderId(existingOrder.getId());
verifyPurchaseIdempotency(existingOrder, existingPackage, request); verifyPurchaseIdempotency(existingOrder, existingPackage, request);
straightThroughService.complete(salesOrderService.detail(existingOrder.getId()), request.getStraightThrough());
return existingPackage; return existingPackage;
} }
Long purchasedAt = TimiJava.defaultIfNull(request.getPurchasedAt(), Time.now()); Long purchasedAt = TimiJava.defaultIfNull(request.getPurchasedAt(), Time.now());
@@ -208,8 +205,6 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService<Ga
result.setStandardAmountSnapshot(config.getStandardAmount()); result.setStandardAmountSnapshot(config.getStandardAmount());
result.setRemark(request.getRemark()); result.setRemark(request.getRemark());
super.create(result); super.create(result);
straightThroughService.complete(salesOrder, request.getStraightThrough());
salesOrder = salesOrderService.detail(salesOrder.getId());
result.setSalesOrder(salesOrder); result.setSalesOrder(salesOrder);
result.setItemList(salesOrder.getItemList()); result.setItemList(salesOrder.getItemList());
result.setStatus(salesOrder.getStatus()); result.setStatus(salesOrder.getStatus());
@@ -207,8 +207,7 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
List<GaoStockChangeItem> stockItems = buildStockItems(order.getStoreId(), sourceList); List<GaoStockChangeItem> stockItems = buildStockItems(order.getStoreId(), sourceList);
applySkuSpecSnapshots(stockItems, fulfillmentItemList); applySkuSpecSnapshots(stockItems, fulfillmentItemList);
TimiException.requiredTrue(!stockItems.isEmpty(), "履约单没有需要占用的实物库存"); TimiException.requiredTrue(!stockItems.isEmpty(), "履约单没有需要占用的实物库存");
String stockShortageMessage = productStockService.getFulfillmentStockShortageMessage(order.getStoreId(), stockItems); TimiException.requiredTrue(productStockService.hasSufficientForFulfillment(order.getStoreId(), stockItems), "库存仍然不足,不能准备履约");
TimiException.requiredTrue(stockShortageMessage == null, stockShortageMessage);
String operatorUserId = userLoginService.getRequireLoginUserId(); String operatorUserId = userLoginService.getRequireLoginUserId();
productStockService.reserveForFulfillment(order.getStoreId(), fulfillment.getId(), stockItems, productStockService.reserveForFulfillment(order.getStoreId(), fulfillment.getId(), stockItems,
stockRequestNo(fulfillment, "RESERVE"), operatorUserId); stockRequestNo(fulfillment, "RESERVE"), operatorUserId);
@@ -451,32 +450,12 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
GaoSalesOrderItem.ItemType expectedItemType = order.getOrderType() == GaoSalesOrder.OrderType.PACKAGE GaoSalesOrderItem.ItemType expectedItemType = order.getOrderType() == GaoSalesOrder.OrderType.PACKAGE
? GaoSalesOrderItem.ItemType.PACKAGE_ENTITLEMENT : GaoSalesOrderItem.ItemType.DIRECT_PRODUCT; ? GaoSalesOrderItem.ItemType.PACKAGE_ENTITLEMENT : GaoSalesOrderItem.ItemType.DIRECT_PRODUCT;
TimiException.requiredTrue(source.getItemType() == expectedItemType, "订单项类型与订单类型不匹配"); TimiException.requiredTrue(source.getItemType() == expectedItemType, "订单项类型与订单类型不匹配");
if (requestItem.getSkuUnitId().equals(source.getSkuUnitId())) { GaoProductSkuUnitSelection selection = skuService.getSellableSkuUnit(order.getStoreId(), source.getSkuId(), requestItem.getSkuUnitId());
result.add(toSourceByOrderSnapshot(order, source, requestItem.getQuantity(), checkAvailable));
continue;
}
GaoProductSkuUnitSelection selection = skuService.getFulfillableSkuUnit(order.getStoreId(), source.getSkuId(), requestItem.getSkuUnitId());
result.add(toSource(order, source, selection.getProduct(), selection.getSku(), selection.getUnit(), requestItem.getQuantity(), checkAvailable)); result.add(toSource(order, source, selection.getProduct(), selection.getSku(), selection.getUnit(), requestItem.getQuantity(), checkAvailable));
} }
return result; return result;
} }
private SourceEntry toSourceByOrderSnapshot(GaoSalesOrder order, GaoSalesOrderItem source, BigDecimal quantity, boolean checkAvailable) {
GaoProductSku sku = skuService.get(source.getSkuId());
TimiException.required(sku, "履约来源 SKU 不存在");
GaoProduct product = productService.get(sku.getProductId());
TimiException.required(product, "履约来源产品不存在");
TimiException.requiredTrue(order.getStoreId().equals(product.getStoreId()), "履约来源产品不属于当前门店");
quantity = requiredPositiveQuantity(quantity, "履约数量");
if (!Boolean.TRUE.equals(sku.getAllowDecimalQuantity())) {
TimiException.requiredTrue(quantity.stripTrailingZeros().scale() <= 0, "该产品不允许小数数量");
}
BigDecimal baseQuantityPerUnit = requiredBaseQuantityPerUnit(source.getBaseQuantityPerUnitSnapshot());
BigDecimal baseQuantity = toBaseQuantity(quantity, baseQuantityPerUnit, "履约基础数量最多保留三位小数");
return buildSource(order, source, source.getSkuUnitId(), source.getUnitSnapshot(), baseQuantityPerUnit, quantity,
baseQuantity, product, sku, checkAvailable);
}
private List<SourceEntry> loadPersistedSources(GaoSalesOrder order, List<GaoOrderFulfillmentItem> itemList) { private List<SourceEntry> loadPersistedSources(GaoSalesOrder order, List<GaoOrderFulfillmentItem> itemList) {
TimiException.requiredTrue(itemList != null && !itemList.isEmpty(), "履约单没有明细"); TimiException.requiredTrue(itemList != null && !itemList.isEmpty(), "履约单没有明细");
List<GaoOrderFulfillmentItem> sortedItemList = new ArrayList<>(itemList); List<GaoOrderFulfillmentItem> sortedItemList = new ArrayList<>(itemList);
@@ -49,17 +49,10 @@ public class GaoOrderPaymentServiceImplement implements GaoOrderPaymentService {
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public GaoFundRecord receive(GaoOrderPaymentReceiveRequest request) { public GaoFundRecord receive(GaoOrderPaymentReceiveRequest request) {
return receiveInternal(request, false); return receiveInternal(request);
} }
@OperationAudit(AuditOperation.GAO_SALES_ORDER_PAYMENT_RECEIVE) private GaoFundRecord receiveInternal(GaoOrderPaymentReceiveRequest request) {
@Transactional(TimiServerDBConfig.ROLLBACKER)
@Override
public GaoFundRecord receiveAndVerify(GaoOrderPaymentReceiveRequest request) {
return receiveInternal(request, true);
}
private GaoFundRecord receiveInternal(GaoOrderPaymentReceiveRequest request, boolean verified) {
TimiException.required(request, "not found order payment request"); TimiException.required(request, "not found order payment request");
TimiException.required(request.getSalesOrderId(), "not found salesOrderId"); TimiException.required(request.getSalesOrderId(), "not found salesOrderId");
TimiException.required(request.getAmount(), "收款金额不能为空"); TimiException.required(request.getAmount(), "收款金额不能为空");
@@ -69,7 +62,7 @@ public class GaoOrderPaymentServiceImplement implements GaoOrderPaymentService {
GaoFundRecord existing = fundRecordService.getByRequestNo(order.getStoreId(), requestNo); GaoFundRecord existing = fundRecordService.getByRequestNo(order.getStoreId(), requestNo);
if (existing != null) { if (existing != null) {
verifySameReceive(existing, order, request); verifySameReceive(existing, order, request);
return verified && existing.getStatus() == GaoFundRecord.Status.PENDING ? verify(existing.getId()) : existing; return existing;
} }
TimiException.requiredTrue(order.getStatus() != GaoSalesOrder.Status.CANCELLED, "已取消订单不能收款"); TimiException.requiredTrue(order.getStatus() != GaoSalesOrder.Status.CANCELLED, "已取消订单不能收款");
TimiException.requiredTrue(order.getRefundStatus() != GaoSalesOrder.RefundStatus.REFUNDED, "已完成退款订单不能收款"); TimiException.requiredTrue(order.getRefundStatus() != GaoSalesOrder.RefundStatus.REFUNDED, "已完成退款订单不能收款");
@@ -78,27 +71,9 @@ public class GaoOrderPaymentServiceImplement implements GaoOrderPaymentService {
TimiException.requiredTrue(pending + paid + request.getAmount() <= safe(order.getSaleAmount()), "累计收款不能超过成交价"); TimiException.requiredTrue(pending + paid + request.getAmount() <= safe(order.getSaleAmount()), "累计收款不能超过成交价");
TimiException.requiredTrue(hasPhoto(request.getAttachmentList()), "收款必须上传照片"); TimiException.requiredTrue(hasPhoto(request.getAttachmentList()), "收款必须上传照片");
GaoFundRecord record = newRecord(order, GaoFundRecord.OperationType.RECEIVE, requestNo, request.getAmount(), request.getOccurredAt(), request.getRemark()); GaoFundRecord record = newRecord(order, GaoFundRecord.OperationType.RECEIVE, requestNo, request.getAmount(), request.getOccurredAt(), request.getRemark());
record.setStatus(verified ? GaoFundRecord.Status.VERIFIED : GaoFundRecord.Status.PENDING); record.setStatus(GaoFundRecord.Status.PENDING);
String verifierUserId = verified ? userLoginService.getRequireLoginUserId() : null;
Long verifiedAt = verified ? Time.now() : null;
if (verified) {
record.setVerifierUserId(verifierUserId);
record.setVerifiedAt(verifiedAt);
}
fundRecordService.createByOrderPayment(record); fundRecordService.createByOrderPayment(record);
if (!verified) {
order.setPendingPaidAmount(pending + request.getAmount()); order.setPendingPaidAmount(pending + request.getAmount());
} else {
Long paidAtBefore = order.getPaidAt();
order.setPaidAmount(paid + request.getAmount());
orderService.recomputePaymentStatus(order);
orderService.updatePayment(order);
if (paidAtBefore == null && order.getPaidAt() != null) {
pointTriggerService.onSalesOrderPaid(order);
}
attachmentService.updateByBizId(Attachment.BizType.GAO_FUND_RECORD, record.getId(), request.getAttachmentList());
return record;
}
orderService.recomputePaymentStatus(order); orderService.recomputePaymentStatus(order);
orderService.updatePayment(order); orderService.updatePayment(order);
attachmentService.updateByBizId(Attachment.BizType.GAO_FUND_RECORD, record.getId(), request.getAttachmentList()); attachmentService.updateByBizId(Attachment.BizType.GAO_FUND_RECORD, record.getId(), request.getAttachmentList());
@@ -1,155 +0,0 @@
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 paymentVerified = Boolean.TRUE.equals(straightThrough.getPaymentVerified());
boolean receiveFullPayment = Boolean.TRUE.equals(straightThrough.getReceiveFullPayment());
boolean fulfillAll = Boolean.TRUE.equals(straightThrough.getFulfillAll());
Long paymentAmount = paymentVerified ? straightThrough.getPaymentAmount() : order.getSaleAmount();
if (!paymentVerified && !receiveFullPayment && !fulfillAll) {
return;
}
TimiException.required(order, "not found sales order");
TimiException.required(order.getId(), "not found salesOrderId");
TimiException.requiredTrue(!(paymentVerified && receiveFullPayment), "已核销收款和已全额收款和核销不能同时启用");
if (paymentVerified) {
TimiException.required(paymentAmount, "已核销收款金额不能为空");
TimiException.requiredTrue(0 < paymentAmount && paymentAmount <= safe(order.getSaleAmount()), "已核销收款金额不能超过订单金额");
}
TimiException.requiredTrue(!fulfillAll || receiveFullPayment || order.getSaleAmount() == 0
|| (paymentVerified && paymentAmount.equals(order.getSaleAmount())),
"全部履约直通必须同时完成收款核销");
checkPermissions(order.getOrderType(), paymentVerified || receiveFullPayment, fulfillAll);
if (receiveFullPayment) {
settle(order, straightThrough);
} else if (paymentVerified) {
verifyReceivedPayment(order, straightThrough, paymentAmount);
}
if (fulfillAll) {
fulfill(order, straightThrough);
}
}
private void checkPermissions(GaoSalesOrder.OrderType orderType, boolean paymentCompleted, boolean fulfillAll) {
List<String> permissionCodeList = new ArrayList<>();
permissionCodeList.add(orderType == GaoSalesOrder.OrderType.PACKAGE
? GaoPermissionCode.CUSTOMER_PACKAGE_CREATE.getValue() : GaoPermissionCode.SALES_ORDER_CREATE.getValue());
if (paymentCompleted) {
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 verifyReceivedPayment(GaoSalesOrder order, GaoOrderStraightThroughRequest straightThrough, Long paymentAmount) {
GaoOrderPaymentReceiveRequest paymentRequest = new GaoOrderPaymentReceiveRequest();
paymentRequest.setSalesOrderId(order.getId());
paymentRequest.setRequestNo("ORDER-%s-VERIFIED-PAY".formatted(order.getId()));
paymentRequest.setAmount(paymentAmount);
paymentRequest.setOccurredAt(TimiJava.defaultIfNull(straightThrough.getPaymentOccurredAt(), order.getPurchasedAt()));
paymentRequest.setAttachmentList(straightThrough.getPaymentAttachmentList());
paymentRequest.setRemark(TimiJava.defaultIfEmpty(straightThrough.getPaymentRemark(), "随订单创建直接核销已有收款"));
GaoFundRecord record = paymentService.receiveAndVerify(paymentRequest);
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;
}
private long safe(Long value) {
return value == null ? 0L : value;
}
}
@@ -109,22 +109,6 @@ public class GaoProductSkuServiceImplement extends AbstractEntityService<GaoProd
return getSkuUnit(storeId, skuId, skuUnitId, false); return getSkuUnit(storeId, skuId, skuUnitId, false);
} }
@Override
public GaoProductSkuUnitSelection getFulfillableSkuUnit(String storeId, String skuId, String skuUnitId) {
TimiException.required(storeId, "门店 ID 不能为空");
TimiException.required(skuId, "SKU ID 不能为空");
TimiException.required(skuUnitId, "SKU 单位 ID 不能为空");
GaoProductSku sku = mapper.selectForUpdate(skuId);
TimiException.required(sku, "SKU 不存在");
TimiException.requiredTrue(storeId.equals(sku.getStoreId()), "SKU 不属于当前门店");
GaoProduct product = productServiceProvider.getObject().get(sku.getProductId());
TimiException.required(product, "产品不存在");
GaoProductSkuUnit unit = unitMapper.selectForUpdate(skuUnitId);
TimiException.required(unit, "SKU 单位不存在");
TimiException.requiredTrue(skuId.equals(unit.getSkuId()), "SKU 单位不属于当前 SKU");
return new GaoProductSkuUnitSelection(product, sku, unit);
}
@Override @Override
public GaoProductSkuUnitSelection getSkuUnit(String storeId, String skuId, String skuUnitId, boolean purchase) { public GaoProductSkuUnitSelection getSkuUnit(String storeId, String skuId, String skuUnitId, boolean purchase) {
TimiException.required(storeId, "not found storeId"); TimiException.required(storeId, "not found storeId");
@@ -370,8 +354,11 @@ public class GaoProductSkuServiceImplement extends AbstractEntityService<GaoProd
TimiException.requiredTrue(1 == unitMapper.updateUnit(unit), "SKU 单位更新失败"); TimiException.requiredTrue(1 == unitMapper.updateUnit(unit), "SKU 单位更新失败");
} }
} }
TimiException.requiredTrue(existingUnitList.stream().allMatch(existing -> retainedIdSet.contains(existing.getId())), for (GaoProductSkuUnit existing : existingUnitList) {
"已保存的 SKU 单位不能删除,请关闭允许销售和允许采购"); if (!retainedIdSet.contains(existing.getId())) {
unitMapper.delete(existing.getId());
}
}
} }
private void saveSpecValues(String skuId, List<String> specValueIdList) { private void saveSpecValues(String skuId, List<String> specValueIdList) {
@@ -156,30 +156,19 @@ public class GaoProductStockServiceImplement implements GaoProductStockService {
@Override @Override
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
public boolean hasSufficientForFulfillment(String storeId, List<GaoStockChangeItem> items) { 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"); TimiException.required(storeId, "not found storeId");
if (items == null || items.isEmpty()) { if (items == null || items.isEmpty()) {
return null; return true;
} }
List<GaoStockChangeItem> sortedItems = new ArrayList<>(items); List<GaoStockChangeItem> sortedItems = new ArrayList<>(items);
sortedItems.sort(Comparator.comparing(GaoStockChangeItem::getSkuId)); sortedItems.sort(Comparator.comparing(GaoStockChangeItem::getSkuId));
List<String> shortageList = new ArrayList<>();
for (GaoStockChangeItem item : sortedItems) { for (GaoStockChangeItem item : sortedItems) {
GaoProductStock stock = stockMapper.selectForUpdate(storeId, item.getSkuId()); GaoProductStock stock = stockMapper.selectForUpdate(storeId, item.getSkuId());
BigDecimal availableQuantity = stock == null ? ZERO : decimal(stock.getOnHandQuantity()).subtract(decimal(stock.getReservedQuantity())); if (stock == null || decimal(stock.getOnHandQuantity()).subtract(decimal(stock.getReservedQuantity())).compareTo(item.getBaseQuantity()) < 0) {
BigDecimal shortageQuantity = item.getBaseQuantity().subtract(availableQuantity); return false;
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 shortageList.isEmpty() ? null : "库存不足:" + String.join(";", shortageList); return true;
} }
@Override @Override
@@ -21,7 +21,6 @@ import com.imyeyu.api.modules.gao.service.GaoCustomerService;
import com.imyeyu.api.modules.gao.service.GaoEventRecordService; import com.imyeyu.api.modules.gao.service.GaoEventRecordService;
import com.imyeyu.api.modules.gao.service.GaoFundRecordService; import com.imyeyu.api.modules.gao.service.GaoFundRecordService;
import com.imyeyu.api.modules.gao.service.GaoOrderFulfillmentService; 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.GaoPointTriggerService;
import com.imyeyu.api.modules.gao.service.GaoProductService; import com.imyeyu.api.modules.gao.service.GaoProductService;
import com.imyeyu.api.modules.gao.service.GaoProductSkuService; import com.imyeyu.api.modules.gao.service.GaoProductSkuService;
@@ -81,7 +80,6 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService<GaoSale
private final GaoSalesOrderItemMapper itemMapper; private final GaoSalesOrderItemMapper itemMapper;
private final GaoFundRecordService fundRecordService; private final GaoFundRecordService fundRecordService;
private final ObjectProvider<GaoOrderFulfillmentService> fulfillmentServiceProvider; private final ObjectProvider<GaoOrderFulfillmentService> fulfillmentServiceProvider;
private final ObjectProvider<GaoOrderStraightThroughService> straightThroughServiceProvider;
private final GaoCustomerService customerService; private final GaoCustomerService customerService;
private final GaoProductService productService; private final GaoProductService productService;
private final GaoProductSkuService skuService; private final GaoProductSkuService skuService;
@@ -145,9 +143,7 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService<GaoSale
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public GaoSalesOrder createDirectProductOrder(GaoDirectProductOrderCreateRequest request) { public GaoSalesOrder createDirectProductOrder(GaoDirectProductOrderCreateRequest request) {
GaoSalesOrder order = createDirectInternal(request); return createDirectInternal(request);
straightThroughServiceProvider.getObject().complete(order, request.getStraightThrough());
return detail(order.getId());
} }
@OperationAudit(AuditOperation.GAO_SALES_ORDER_UPDATE) @OperationAudit(AuditOperation.GAO_SALES_ORDER_UPDATE)
@@ -33,9 +33,6 @@ public class GaoCustomerPackagePurchaseRequest {
/// 购买照片 /// 购买照片
private List<Attachment> attachmentList; private List<Attachment> attachmentList;
/// 创建订单后的直通处理
private GaoOrderStraightThroughRequest straightThrough;
/// 备注 /// 备注
private String remark; private String remark;
} }
@@ -30,9 +30,6 @@ public class GaoDirectProductOrderCreateRequest {
/// 购买照片 /// 购买照片
private List<Attachment> attachmentList; private List<Attachment> attachmentList;
/// 创建订单后的直通处理
private GaoOrderStraightThroughRequest straightThrough;
/// 备注 /// 备注
private String remark; private String remark;
} }
@@ -1,44 +0,0 @@
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 paymentVerified;
/// 已核销收款金额,单位分
private Long paymentAmount;
/// 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;
}
@@ -348,23 +348,10 @@
FROM `daily_stat` d FROM `daily_stat` d
JOIN `open_calendar` o ON d.`registeredDateValue` = o.`dateValue` JOIN `open_calendar` o ON d.`registeredDateValue` = o.`dateValue`
), ),
`customer_stat` AS (
SELECT
d.`customerId`,
MAX(CASE WHEN d.`registeredDateValue` = #{todayDateValue} THEN 1 ELSE 0 END) AS `hasToday`
FROM `daily_stat` d
GROUP BY d.`customerId`
),
`anchor_date` AS ( `anchor_date` AS (
SELECT SELECT
s.`customerId`, MAX(`openIndex`) AS `anchorOpenIndex`
CASE FROM `open_calendar`
WHEN s.`hasToday` = 1 THEN MAX(CASE WHEN c.`dateValue` = #{todayDateValue} THEN c.`openIndex` END)
ELSE MAX(CASE WHEN c.`dateValue` &lt; #{todayDateValue} THEN c.`openIndex` END)
END AS `anchorOpenIndex`
FROM `customer_stat` s
CROSS JOIN `open_calendar` c
GROUP BY s.`customerId`, s.`hasToday`
), ),
`continuous_stat` AS ( `continuous_stat` AS (
SELECT SELECT
@@ -374,7 +361,7 @@
ELSE COUNT(1) ELSE COUNT(1)
END AS `recentContinuousEventDays` END AS `recentContinuousEventDays`
FROM `continuous_ordered` o FROM `continuous_ordered` o
JOIN `anchor_date` a ON o.`customerId` = a.`customerId` JOIN `anchor_date` a
WHERE WHERE
o.`openIndex` + o.`dayIndex` = a.`anchorOpenIndex` + 1 o.`openIndex` + o.`dayIndex` = a.`anchorOpenIndex` + 1
<if test="eventIdList == null or eventIdList.size() != 1"> <if test="eventIdList == null or eventIdList.size() != 1">
@@ -53,7 +53,7 @@
INNER JOIN `gao_customer` `c` ON `c`.`id` = `a`.`customer_id` AND `c`.`store_id` = `a`.`store_id` INNER JOIN `gao_customer` `c` ON `c`.`id` = `a`.`customer_id` AND `c`.`store_id` = `a`.`store_id`
WHERE WHERE
<include refid="queryWhere"/> <include refid="queryWhere"/>
ORDER BY `a`.`total_earned` DESC, `a`.`created_at` DESC, `a`.`id` DESC ORDER BY `a`.`created_at` DESC, `a`.`id` DESC
LIMIT #{offset}, #{limit} LIMIT #{offset}, #{limit}
</select> </select>