add sales order

This commit is contained in:
Timi
2026-09-06 12:09:58 +08:00
parent 41f692aad9
commit f3166f0413
43 changed files with 560 additions and 73 deletions
@@ -81,6 +81,8 @@ public enum GaoPermissionCode implements BuiltinPermissionCode {
SALES_ORDER_READ("SALES_ORDER:READ", "销售订单读取", true, true),
SALES_ORDER_UPDATE("SALES_ORDER:UPDATE", "销售订单修改", true, true),
SALES_ORDER_CANCEL("SALES_ORDER:CANCEL", "销售订单取消", true, true),
SALES_ORDER_PRICE_OVERRIDE("SALES_ORDER:PRICE_OVERRIDE", "销售订单价格修改", true, true),
@@ -8,6 +8,7 @@ import com.imyeyu.api.modules.gao.entity.GaoCustomer;
import com.imyeyu.api.modules.gao.entity.GaoCustomerPackage;
import com.imyeyu.api.modules.gao.entity.GaoCustomerPackageItem;
import com.imyeyu.api.modules.gao.entity.GaoOrderFulfillment;
import com.imyeyu.api.modules.gao.entity.GaoProduct;
import com.imyeyu.api.modules.gao.service.GaoCustomerPackageService;
import com.imyeyu.api.modules.gao.service.GaoCustomerService;
import com.imyeyu.api.modules.gao.service.GaoOrderFulfillmentService;
@@ -127,11 +128,34 @@ public class GaoCustomerPackageController {
Map<String, User> userMap = userIdList.isEmpty() ? Map.of() : userService.mapByIdList(userIdList);
for (GaoCustomerPackage packageEntity : packageList) {
List<GaoCustomerPackageItem> itemList = itemMap.getOrDefault(packageEntity.getId(), List.of());
fillItemAttachments(itemList);
for (GaoCustomerPackageItem item : itemList) {
fillDerived(item);
}
packageEntity.setItemList(itemList);
packageEntity.setCancelOperator(userMap.get(packageEntity.getCancelOperatorUserId()));
String cancelOperatorUserId = packageEntity.getCancelOperatorUserId();
packageEntity.setCancelOperator(cancelOperatorUserId == null ? null : userMap.get(cancelOperatorUserId));
}
}
private void fillItemAttachments(List<GaoCustomerPackageItem> itemList) {
if (itemList == null || itemList.isEmpty()) {
return;
}
List<String> productIdList = itemList.stream()
.map(GaoCustomerPackageItem::getProductId)
.filter(value -> value != null && !value.isBlank())
.distinct()
.toList();
if (productIdList.isEmpty()) {
return;
}
Map<String, List<Attachment>> attachmentMap = attachmentService.mapByBizIdList(
Attachment.BizType.GAO_PRODUCT,
productIdList,
GaoProduct.AttachType.PHOTO.name());
for (GaoCustomerPackageItem item : itemList) {
item.setAttachmentList(attachmentMap.getOrDefault(item.getProductId(), List.of()));
}
}
@@ -60,8 +60,7 @@ public class GaoFundRecordController {
@RequireGaoPermission(GaoPermissionCode.FUND_RECORD_READ)
@PostMapping("/detail")
public GaoFundRecord detail(@RequestParam String id) {
GaoFundRecord result = service.get(id);
TimiException.required(result, "not found fund record");
GaoFundRecord result = service.detail(id);
storeScopeService.checkStoreId(result.getStoreId());
fillTransient(List.of(result));
return result;
@@ -4,7 +4,9 @@ import com.imyeyu.api.modules.common.entity.Attachment;
import com.imyeyu.api.modules.common.service.AttachmentService;
import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission;
import com.imyeyu.api.modules.gao.bean.GaoPermissionCode;
import com.imyeyu.api.modules.gao.entity.GaoCustomer;
import com.imyeyu.api.modules.gao.entity.GaoOrderFulfillment;
import com.imyeyu.api.modules.gao.service.GaoCustomerService;
import com.imyeyu.api.modules.gao.service.GaoOrderFulfillmentService;
import com.imyeyu.api.modules.gao.service.GaoStoreScopeService;
import com.imyeyu.api.modules.gao.vo.GaoOrderFulfillmentActionRequest;
@@ -41,6 +43,7 @@ public class GaoOrderFulfillmentController {
private final GaoOrderFulfillmentService service;
private final GaoStoreScopeService storeScopeService;
private final AttachmentService attachmentService;
private final GaoCustomerService customerService;
private final UserService userService;
@AOPLog
@@ -131,6 +134,15 @@ public class GaoOrderFulfillmentController {
}
List<String> idList = fulfillmentList.stream().map(GaoOrderFulfillment::getId).toList();
Map<String, List<Attachment>> attachmentMap = attachmentService.mapByBizIdList(Attachment.BizType.GAO_ORDER_FULFILLMENT, idList);
List<String> customerIdList = fulfillmentList.stream()
.map(GaoOrderFulfillment::getCustomerId)
.filter(value -> value != null && !value.isBlank())
.distinct()
.toList();
Map<String, GaoCustomer> customerMap = customerIdList.isEmpty() ? Map.of() : customerService.mapByIdList(customerIdList);
Map<String, List<Attachment>> customerAttachmentMap = customerIdList.isEmpty()
? Map.of()
: attachmentService.mapByBizIdList(Attachment.BizType.GAO_CUSTOMER, customerIdList, GaoCustomer.AttachType.PHOTO.name());
List<String> userIdList = fulfillmentList.stream()
.flatMap(item -> Stream.of(item.getOperatorUserId(), item.getVerifierUserId()))
.filter(value -> value != null && !value.isBlank())
@@ -139,6 +151,11 @@ public class GaoOrderFulfillmentController {
Map<String, User> userMap = userIdList.isEmpty() ? Map.of() : userService.mapByIdList(userIdList);
for (GaoOrderFulfillment fulfillment : fulfillmentList) {
fulfillment.setAttachmentList(attachmentMap.getOrDefault(fulfillment.getId(), List.of()));
GaoCustomer customer = customerMap.get(fulfillment.getCustomerId());
if (customer != null) {
customer.setAttachmentList(customerAttachmentMap.getOrDefault(customer.getId(), List.of()));
}
fulfillment.setCustomer(customer);
fulfillment.setOperator(userMap.get(fulfillment.getOperatorUserId()));
fulfillment.setVerifier(userMap.get(fulfillment.getVerifierUserId()));
}
@@ -4,8 +4,10 @@ import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission;
import com.imyeyu.api.modules.gao.bean.GaoPermissionCode;
import com.imyeyu.api.modules.gao.entity.GaoPackageConfig;
import com.imyeyu.api.modules.gao.entity.GaoPackageConfigItem;
import com.imyeyu.api.modules.gao.entity.GaoProduct;
import com.imyeyu.api.modules.gao.service.GaoEventService;
import com.imyeyu.api.modules.gao.service.GaoPackageConfigService;
import com.imyeyu.api.modules.gao.service.GaoProductService;
import com.imyeyu.api.modules.gao.service.GaoStoreScopeService;
import com.imyeyu.api.modules.gao.vo.GaoPackageConfigPage;
import com.imyeyu.api.modules.user.entity.User;
@@ -35,6 +37,7 @@ public class GaoPackageConfigController {
private final GaoPackageConfigService service;
private final GaoEventService eventService;
private final GaoProductService productService;
private final GaoStoreScopeService storeScopeService;
private final UserService userService;
@@ -46,7 +49,7 @@ public class GaoPackageConfigController {
TimiException.required(page, "not found page");
page.setStoreId(storeScopeService.resolveStoreId(page.getStoreId()));
PageResult<GaoPackageConfig> result = service.pageByQuery(page);
fillTransient(result.getList());
fillTransient(result.getList(), false);
return result;
}
@@ -56,7 +59,7 @@ public class GaoPackageConfigController {
@PostMapping("/list/valid")
public List<GaoPackageConfig> listValid(@RequestParam String storeId) {
List<GaoPackageConfig> result = service.listValid(storeScopeService.resolveStoreId(storeId));
fillTransient(result);
fillTransient(result, false);
return result;
}
@@ -68,11 +71,11 @@ public class GaoPackageConfigController {
GaoPackageConfig config = service.get(id);
TimiException.required(config, "not found package config");
storeScopeService.checkStoreId(config.getStoreId());
fillTransient(List.of(config));
fillTransient(List.of(config), true);
return config;
}
private void fillTransient(List<GaoPackageConfig> configList) {
private void fillTransient(List<GaoPackageConfig> configList, boolean includeProductDetail) {
if (configList == null || configList.isEmpty()) {
return;
}
@@ -83,6 +86,28 @@ public class GaoPackageConfigController {
config.setItemList(itemMap.getOrDefault(config.getId(), List.of()));
config.setUpdater(updaterMap.get(config.getUpdatedBy()));
}
if (includeProductDetail) {
fillProductDetails(configList);
}
}
private void fillProductDetails(List<GaoPackageConfig> configList) {
List<String> productIdList = configList.stream()
.flatMap(config -> config.getItemList().stream())
.map(GaoPackageConfigItem::getProductId)
.filter(value -> value != null && !value.isBlank())
.distinct()
.toList();
if (productIdList.isEmpty()) {
return;
}
Map<String, GaoProduct> productMap = productService.listByIdList(productIdList).stream()
.collect(java.util.stream.Collectors.toMap(GaoProduct::getId, item -> item));
for (GaoPackageConfig config : configList) {
for (GaoPackageConfigItem item : config.getItemList()) {
item.setProduct(productMap.get(item.getProductId()));
}
}
}
@AOPLog
@@ -15,6 +15,7 @@ import com.imyeyu.api.modules.gao.vo.GaoOrderPaymentRefundRequest;
import com.imyeyu.api.modules.gao.vo.GaoOrderPaymentRevokeRequest;
import com.imyeyu.api.modules.gao.vo.GaoSalesOrderCancelRequest;
import com.imyeyu.api.modules.gao.vo.GaoSalesOrderPage;
import com.imyeyu.api.modules.gao.vo.GaoSalesOrderUpdateRequest;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.spring.annotation.AOPLog;
import com.imyeyu.spring.annotation.RequestRateLimit;
@@ -67,6 +68,16 @@ public class GaoSalesOrderController {
return service.createDirectProductOrder(request);
}
@AOPLog
@RequestRateLimit
@RequireGaoPermission(GaoPermissionCode.SALES_ORDER_UPDATE)
@PostMapping("/update")
public GaoSalesOrder update(@RequestBody GaoSalesOrderUpdateRequest request) {
TimiException.required(request, "not found sales order update request");
TimiException.required(request.getId(), "not found sales order.id");
return service.updateRemarkAndAttachments(request);
}
@AOPLog
@RequestRateLimit
@RequireGaoPermission(GaoPermissionCode.SALES_ORDER_CANCEL)
@@ -1,11 +1,13 @@
package com.imyeyu.api.modules.gao.entity;
import com.imyeyu.api.modules.common.entity.Attachment;
import com.imyeyu.spring.annotation.table.Transient;
import com.imyeyu.spring.entity.UUIDEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.util.List;
/// GAO 客户套餐产品权益快照
///
@@ -24,6 +26,10 @@ public class GaoCustomerPackageItem extends UUIDEntity {
/// 产品单位 ID
private String productUnitId;
/// 产品图片附件
@Transient
private List<Attachment> attachmentList;
/// 产品名称快照
private String productNameSnapshot;
@@ -101,6 +101,17 @@ public class GaoFundRecord extends UUIDEntity {
/// 备注
private String remark;
/// 关联的冲正流水 ID
@Transient
private String revokeRecordId;
/// 关联销售订单总价,单位分
@Transient
private Long salesOrderAmount;
/// 流水发生时关联销售订单剩余待收款金额快照,单位分
private Long salesOrderUnpaidAmountSnapshot;
/// 操作人
@Transient
private User operator;
@@ -2,8 +2,13 @@ package com.imyeyu.api.modules.gao.entity;
import com.imyeyu.api.modules.common.entity.Attachment;
import com.imyeyu.api.modules.user.entity.User;
import com.imyeyu.spring.annotation.table.AutoUUID;
import com.imyeyu.spring.annotation.table.Id;
import com.imyeyu.spring.annotation.table.Transient;
import com.imyeyu.spring.entity.Creatable;
import com.imyeyu.spring.entity.IDEntity;
import com.imyeyu.spring.entity.UUIDEntity;
import com.imyeyu.spring.entity.Updatable;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -14,8 +19,7 @@ import java.util.List;
/// @author Codex
/// @since 2026-09-02
@Data
@EqualsAndHashCode(callSuper = true)
public class GaoOrderFulfillment extends UUIDEntity {
public class GaoOrderFulfillment implements IDEntity<String>, Creatable, Updatable {
/// 履约来源类型
public enum FulfillmentType {
@@ -49,6 +53,10 @@ public class GaoOrderFulfillment extends UUIDEntity {
REVOKED
}
@Id
@AutoUUID
private String id;
/// 销售订单 ID
private String salesOrderId;
@@ -88,10 +96,18 @@ public class GaoOrderFulfillment extends UUIDEntity {
/// 备注
private String remark;
private Long createdAt;
private Long updatedAt;
/// 提交人
@Transient
private User operator;
/// 客户
@Transient
private GaoCustomer customer;
/// 核销或拒绝操作人
@Transient
private User verifier;
@@ -1,18 +1,26 @@
package com.imyeyu.api.modules.gao.entity;
import com.imyeyu.spring.entity.UUIDEntity;
import com.imyeyu.api.modules.common.entity.Attachment;
import com.imyeyu.spring.annotation.table.AutoUUID;
import com.imyeyu.spring.annotation.table.Id;
import com.imyeyu.spring.annotation.table.Transient;
import com.imyeyu.spring.entity.Creatable;
import com.imyeyu.spring.entity.IDEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.util.List;
/// GAO 统一履约明细
///
/// @author Codex
/// @since 2026-09-02
@Data
@EqualsAndHashCode(callSuper = true)
public class GaoOrderFulfillmentItem extends UUIDEntity {
public class GaoOrderFulfillmentItem implements IDEntity<String>, Creatable {
@Id
@AutoUUID
private String id;
/// 履约单 ID
private String fulfillmentId;
@@ -23,6 +31,10 @@ public class GaoOrderFulfillmentItem extends UUIDEntity {
/// 产品单位 ID
private String productUnitId;
/// 产品图片附件
@Transient
private List<Attachment> attachmentList;
/// 直接订单明细 ID
private String sourceOrderItemId;
@@ -55,4 +67,6 @@ public class GaoOrderFulfillmentItem extends UUIDEntity {
/// 基础单位数量系数快照
private BigDecimal baseQuantityPerUnitSnapshot;
private Long createdAt;
}
@@ -1,5 +1,6 @@
package com.imyeyu.api.modules.gao.entity;
import com.imyeyu.spring.annotation.table.Transient;
import com.imyeyu.spring.entity.UUIDEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -40,4 +41,8 @@ public class GaoPackageConfigItem extends UUIDEntity {
/// 备注
private String remark;
/// 产品详情
@Transient
private GaoProduct product;
}
@@ -1,10 +1,13 @@
package com.imyeyu.api.modules.gao.entity;
import com.imyeyu.api.modules.common.entity.Attachment;
import com.imyeyu.spring.annotation.table.Transient;
import com.imyeyu.spring.entity.UUIDEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.util.List;
/// GAO 直接产品订单明细
///
@@ -23,6 +26,10 @@ public class GaoSalesOrderItem extends UUIDEntity {
/// 产品单位 ID
private String productUnitId;
/// 产品图片附件
@Transient
private List<Attachment> attachmentList;
/// 产品名称快照
private String productNameSnapshot;
@@ -19,6 +19,9 @@ public interface GaoFundRecordMapper extends BaseMapper<GaoFundRecord, String> {
/// 锁定资金记录
GaoFundRecord selectForUpdate(@Param("id") String id);
/// 查询资金记录及关联销售订单资金摘要
GaoFundRecord selectWithSalesOrderSummary(@Param("id") String id);
/// 查询资金冲正记录
GaoFundRecord selectRevokeBySourceRecordId(@Param("sourceRecordId") String sourceRecordId);
@@ -15,6 +15,9 @@ public interface GaoOrderFulfillmentItemMapper extends BaseMapper<GaoOrderFulfil
/// 按履约单查询明细
List<GaoOrderFulfillmentItem> selectByFulfillmentId(@Param("fulfillmentId") String fulfillmentId);
/// 批量查询履约明细
List<GaoOrderFulfillmentItem> selectByFulfillmentIdList(@Param("fulfillmentIdList") List<String> fulfillmentIdList);
/// 锁定履约明细
List<GaoOrderFulfillmentItem> selectByFulfillmentIdForUpdate(@Param("fulfillmentId") String fulfillmentId);
}
@@ -5,6 +5,7 @@ import com.imyeyu.api.modules.gao.vo.GaoProductPage;
import com.imyeyu.spring.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import java.util.Collection;
import java.util.List;
/// GAO 产品 Mapper
@@ -19,6 +20,9 @@ public interface GaoProductMapper extends BaseMapper<GaoProduct, String> {
/// 按条件查询产品
List<GaoProduct> selectByQuery(GaoProductPage page);
/// 批量查询产品
List<GaoProduct> selectByIdList(@Param("idList") Collection<String> idList);
/// 查询产品编码
GaoProduct selectByStoreIdAndCode(@Param("storeId") String storeId, @Param("code") String code, @Param("excludeId") String excludeId);
@@ -16,6 +16,9 @@ public interface GaoSalesOrderItemMapper extends BaseMapper<GaoSalesOrderItem, S
/// 查询订单明细
List<GaoSalesOrderItem> selectBySalesOrderId(@Param("salesOrderId") String salesOrderId);
/// 批量查询订单明细
List<GaoSalesOrderItem> selectBySalesOrderIdList(@Param("salesOrderIdList") List<String> salesOrderIdList);
/// 锁定订单明细
GaoSalesOrderItem selectForUpdate(@Param("id") String id);
@@ -37,4 +37,7 @@ public interface GaoSalesOrderMapper extends BaseMapper<GaoSalesOrder, String> {
/// 更新订单履约状态
int updateStatus(@Param("id") String id, @Param("status") GaoSalesOrder.Status status);
/// 更新订单备注
int updateRemark(@Param("id") String id, @Param("remark") String remark);
}
@@ -5,6 +5,7 @@ import com.imyeyu.api.modules.gao.entity.GaoPointRule;
import com.imyeyu.api.modules.gao.service.GaoCustomerConsumptionRecordService;
import com.imyeyu.api.modules.gao.service.GaoEventRecordService;
import com.imyeyu.java.bean.timi.TimiException;
import lombok.Getter;
import java.time.Instant;
import java.time.LocalDate;
@@ -28,11 +29,18 @@ public class GaoPointScriptContext {
private final GaoCustomerConsumptionRecordService consumptionService;
private final GaoEventRecordService eventRecordService;
private final GaoPointRule rule;
@Getter
private final String storeId;
@Getter
private final String customerId;
@Getter
private final String triggerSourceType;
@Getter
private final String triggerRefType;
@Getter
private final String triggerRefId;
@Getter
private final long occurredAt;
/// 参与过金额计算的消费事实,ID 到金额摘要
@@ -51,36 +59,6 @@ public class GaoPointScriptContext {
this.occurredAt = occurredAt;
}
/// 触发来源门店 ID
public String getStoreId() {
return storeId;
}
/// 触发来源客户 ID
public String getCustomerId() {
return customerId;
}
/// 直接触发来源类型
public String getTriggerSourceType() {
return triggerSourceType;
}
/// 触发对象筛选类型
public String getTriggerRefType() {
return triggerRefType;
}
/// 触发对象筛选 ID
public String getTriggerRefId() {
return triggerRefId;
}
/// 触发发生时间,毫秒
public long getOccurredAt() {
return occurredAt;
}
/// 查询触发时间往前滚动一年的有效消费金额,窗口按 Asia/Shanghai 自然日对齐
///
/// @return 窗口内 ACTIVE 消费事实金额合计,单位分
@@ -16,6 +16,9 @@ public interface GaoFundRecordService extends BaseService<GaoFundRecord, String>
/// 按条件查询资金流水
PageResult<GaoFundRecord> pageByQuery(GaoFundRecordPage page);
/// 查询资金流水详情
GaoFundRecord detail(String id);
/// 按销售订单查询资金流水
List<GaoFundRecord> listByBizId(String salesOrderId);
@@ -7,6 +7,7 @@ import com.imyeyu.api.modules.gao.vo.GaoProductUnitSelection;
import com.imyeyu.spring.bean.PageResult;
import com.imyeyu.spring.service.BaseService;
import java.util.Collection;
import java.util.List;
/// GAO 产品服务
@@ -21,6 +22,9 @@ public interface GaoProductService extends BaseService<GaoProduct, String> {
/// 查询产品详情单位
GaoProduct detail(String id);
/// 批量查询产品详情
List<GaoProduct> listByIdList(Collection<String> idList);
/// 查询启用且允许销售的产品单位
List<GaoProduct> listSellable(GaoProductPage page);
@@ -6,6 +6,7 @@ import com.imyeyu.api.modules.gao.vo.GaoDirectProductOrderCreateRequest;
import com.imyeyu.api.modules.gao.vo.GaoPackageOrderCreateData;
import com.imyeyu.api.modules.gao.vo.GaoSalesOrderCancelRequest;
import com.imyeyu.api.modules.gao.vo.GaoSalesOrderPage;
import com.imyeyu.api.modules.gao.vo.GaoSalesOrderUpdateRequest;
import com.imyeyu.spring.bean.PageResult;
import com.imyeyu.spring.service.BaseService;
@@ -27,6 +28,9 @@ public interface GaoSalesOrderService extends BaseService<GaoSalesOrder, String>
/// 创建直接产品订单
GaoSalesOrder createDirectProductOrder(GaoDirectProductOrderCreateRequest request);
/// 只更新订单备注和附件
GaoSalesOrder updateRemarkAndAttachments(GaoSalesOrderUpdateRequest request);
/// 按门店和幂等号查询订单
///
/// @param storeId 门店 ID
@@ -75,6 +75,16 @@ public class GaoFundRecordServiceImplement extends AbstractEntityService<GaoFund
return result;
}
@Override
public GaoFundRecord detail(String id) {
TimiException.required(id, "not found fund record id");
GaoFundRecord result = mapper.selectWithSalesOrderSummary(id);
TimiException.required(result, "not found fund record");
GaoFundRecord revoke = mapper.selectRevokeBySourceRecordId(result.getId());
result.setRevokeRecordId(revoke == null ? null : revoke.getId());
return result;
}
@Override
public List<GaoFundRecord> listByBizId(String salesOrderId) {
TimiException.required(salesOrderId, "not found salesOrderId");
@@ -126,7 +126,9 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
GaoOrderFulfillment fulfillment = mapper.selectById(id);
TimiException.required(fulfillment, "not found fulfillment");
storeScopeService.checkStoreId(fulfillment.getStoreId());
fulfillment.setItemList(itemMapper.selectByFulfillmentId(fulfillment.getId()));
List<GaoOrderFulfillmentItem> itemList = itemMapper.selectByFulfillmentId(fulfillment.getId());
fillItemAttachments(itemList);
fulfillment.setItemList(itemList);
fulfillment.setStockRecordList(stockRecordService.listByBiz(GaoProductStockRecord.BizType.ORDER_FULFILLMENT, fulfillment.getId()));
return fulfillment;
}
@@ -659,8 +661,52 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
}
private void fillItemList(List<GaoOrderFulfillment> list) {
for (GaoOrderFulfillment fulfillment : TimiJava.safeIterable(list)) {
fulfillment.setItemList(itemMapper.selectByFulfillmentId(fulfillment.getId()));
if (list == null || list.isEmpty()) {
return;
}
List<String> fulfillmentIdList = list.stream()
.map(GaoOrderFulfillment::getId)
.filter(value -> value != null && !value.isBlank())
.distinct()
.toList();
if (fulfillmentIdList.isEmpty()) {
for (GaoOrderFulfillment fulfillment : list) {
fulfillment.setItemList(List.of());
}
return;
}
Map<String, List<GaoOrderFulfillmentItem>> itemMap = new HashMap<>();
List<GaoOrderFulfillmentItem> queriedItemList = itemMapper.selectByFulfillmentIdList(fulfillmentIdList);
for (GaoOrderFulfillmentItem item : queriedItemList) {
itemMap.computeIfAbsent(item.getFulfillmentId(), key -> new ArrayList<>()).add(item);
}
List<GaoOrderFulfillmentItem> itemList = new ArrayList<>();
for (GaoOrderFulfillment fulfillment : list) {
List<GaoOrderFulfillmentItem> fulfillmentItemList = itemMap.getOrDefault(fulfillment.getId(), List.of());
fulfillment.setItemList(fulfillmentItemList);
itemList.addAll(fulfillmentItemList);
}
fillItemAttachments(itemList);
}
private void fillItemAttachments(List<GaoOrderFulfillmentItem> itemList) {
if (itemList == null || itemList.isEmpty()) {
return;
}
List<String> productIdList = itemList.stream()
.map(GaoOrderFulfillmentItem::getProductId)
.filter(value -> value != null && !value.isBlank())
.distinct()
.toList();
if (productIdList.isEmpty()) {
return;
}
Map<String, List<Attachment>> attachmentMap = attachmentService.mapByBizIdList(
Attachment.BizType.GAO_PRODUCT,
productIdList,
GaoProduct.AttachType.PHOTO.name());
for (GaoOrderFulfillmentItem item : itemList) {
item.setAttachmentList(attachmentMap.getOrDefault(item.getProductId(), List.of()));
}
}
@@ -256,6 +256,7 @@ public class GaoOrderPaymentServiceImplement implements GaoOrderPaymentService {
record.setBizId(order.getId());
record.setOperationType(operationType);
record.setAmount(amount);
record.setSalesOrderUnpaidAmountSnapshot(safe(order.getSaleAmount()) - safe(order.getPaidAmount()));
record.setRequestNo(requestNo);
record.setOperatorUserId(userLoginService.getRequireLoginUserId());
record.setOccurredAt(TimiJava.defaultIfNull(occurredAt, Time.now()));
@@ -29,6 +29,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -75,6 +76,16 @@ public class GaoProductServiceImplement extends AbstractEntityService<GaoProduct
return product;
}
@Override
public List<GaoProduct> listByIdList(Collection<String> idList) {
if (idList == null || idList.isEmpty()) {
return List.of();
}
List<GaoProduct> list = mapper.selectByIdList(idList);
fillTransient(list);
return list;
}
@Override
public List<GaoProduct> listSellable(GaoProductPage page) {
TimiException.required(page, "not found product page");
@@ -34,6 +34,7 @@ import com.imyeyu.api.modules.gao.vo.GaoProductUnitSelection;
import com.imyeyu.api.modules.gao.vo.GaoSalesOrderCancelRequest;
import com.imyeyu.api.modules.gao.vo.GaoSalesOrderItemRequest;
import com.imyeyu.api.modules.gao.vo.GaoSalesOrderPage;
import com.imyeyu.api.modules.gao.vo.GaoSalesOrderUpdateRequest;
import com.imyeyu.api.modules.system.entity.Logger;
import com.imyeyu.api.modules.system.service.LoggerService;
import com.imyeyu.api.modules.user.service.PermissionChecker;
@@ -55,8 +56,10 @@ import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
@@ -100,6 +103,7 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService<GaoSale
result.setTotal(mapper.countByQuery(page));
List<GaoSalesOrder> list = mapper.selectByQuery(page);
fillItemList(list);
fillCustomer(list);
result.setList(list);
return result;
}
@@ -109,7 +113,11 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService<GaoSale
GaoSalesOrder order = get(id);
TimiException.required(order, "not found sales order");
storeScopeService.checkStoreId(order.getStoreId());
order.setItemList(itemMapper.selectBySalesOrderId(order.getId()));
List<GaoSalesOrderItem> itemList = itemMapper.selectBySalesOrderId(order.getId());
fillItemAttachments(itemList);
order.setItemList(itemList);
fillCustomer(List.of(order));
order.setAttachmentList(attachmentService.listByBizId(Attachment.BizType.GAO_SALES_ORDER, order.getId()));
order.setFundRecordList(fundRecordService.listByBizId(order.getId()));
if (order.getSourceEventRecordId() != null) {
order.setSourceEventRecord(eventRecordService.get(order.getSourceEventRecordId()));
@@ -132,6 +140,19 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService<GaoSale
return loggerService.execute(Logger.Module.GAO, "GAO_SALES_ORDER_DIRECT_PRODUCT_CREATE", request, () -> createDirectInternal(request));
}
@Transactional(TimiServerDBConfig.ROLLBACKER)
@Override
public GaoSalesOrder updateRemarkAndAttachments(GaoSalesOrderUpdateRequest request) {
return loggerService.execute(Logger.Module.GAO, "GAO_SALES_ORDER_UPDATE", request, () -> {
TimiException.required(request, "not found sales order update request");
TimiException.required(request.getId(), "not found sales order.id");
GaoSalesOrder order = getForUpdate(request.getId());
TimiException.requiredTrue(1 == mapper.updateRemark(order.getId(), request.getRemark()), "订单备注更新失败");
attachmentService.updateByBizId(Attachment.BizType.GAO_SALES_ORDER, order.getId(), request.getAttachmentList());
return detail(order.getId());
});
}
private GaoSalesOrder createDirectInternal(GaoDirectProductOrderCreateRequest request) {
TimiException.required(request, "not found direct product order request");
String requestNo = requiredText(request.getRequestNo(), "not found requestNo");
@@ -436,10 +457,74 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService<GaoSale
}
private void fillItemList(List<GaoSalesOrder> orderList) {
if (orderList == null || orderList.isEmpty()) {
return;
}
List<String> orderIdList = orderList.stream()
.map(GaoSalesOrder::getId)
.filter(value -> value != null && !value.isBlank())
.distinct()
.toList();
Map<String, List<GaoSalesOrderItem>> itemMap = new HashMap<>();
if (!orderIdList.isEmpty()) {
List<GaoSalesOrderItem> queriedItemList = itemMapper.selectBySalesOrderIdList(orderIdList);
for (GaoSalesOrderItem item : TimiJava.safeIterable(queriedItemList)) {
itemMap.computeIfAbsent(item.getSalesOrderId(), key -> new ArrayList<>()).add(item);
}
}
List<GaoSalesOrderItem> itemList = new ArrayList<>();
for (GaoSalesOrder order : TimiJava.safeIterable(orderList)) {
order.setItemList(itemMapper.selectBySalesOrderId(order.getId()));
List<GaoSalesOrderItem> orderItemList = itemMap.getOrDefault(order.getId(), List.of());
order.setItemList(orderItemList);
itemList.addAll(orderItemList);
order.setUnpaidAmount(safe(order.getSaleAmount()) - safe(order.getPaidAmount()));
}
fillItemAttachments(itemList);
}
private void fillItemAttachments(List<GaoSalesOrderItem> itemList) {
if (itemList == null || itemList.isEmpty()) {
return;
}
List<String> productIdList = itemList.stream()
.map(GaoSalesOrderItem::getProductId)
.filter(value -> value != null && !value.isBlank())
.distinct()
.toList();
if (productIdList.isEmpty()) {
return;
}
Map<String, List<Attachment>> attachmentMap = attachmentService.mapByBizIdList(
Attachment.BizType.GAO_PRODUCT,
productIdList,
GaoProduct.AttachType.PHOTO.name());
for (GaoSalesOrderItem item : itemList) {
item.setAttachmentList(attachmentMap.getOrDefault(item.getProductId(), List.of()));
}
}
private void fillCustomer(List<GaoSalesOrder> orderList) {
Set<String> customerIdSet = new HashSet<>();
for (GaoSalesOrder order : TimiJava.safeIterable(orderList)) {
if (TimiJava.isNotEmpty(order.getCustomerId())) {
customerIdSet.add(order.getCustomerId());
}
}
if (customerIdSet.isEmpty()) {
return;
}
List<String> customerIdList = new ArrayList<>(customerIdSet);
Map<String, GaoCustomer> customerMap = customerService.mapByIdList(customerIdList);
Map<String, List<Attachment>> attachmentMap = attachmentService.mapByBizIdList(Attachment.BizType.GAO_CUSTOMER, customerIdList);
for (GaoSalesOrder order : TimiJava.safeIterable(orderList)) {
GaoCustomer customer = customerMap.get(order.getCustomerId());
if (customer == null) {
continue;
}
customer.setAttachmentList(attachmentMap.getOrDefault(customer.getId(), List.of()));
order.setCustomer(customer);
}
}
private BigDecimal validateQuantity(GaoProduct product, BigDecimal quantity) {
@@ -31,6 +31,18 @@ public class GaoProductStockRecordPage extends BasePage {
/// 操作类型
private GaoProductStockRecord.OperationType operationType;
/// 仅查询仍然生效的履约预占流水
private Boolean activeReservationOnly;
/// 库存变动方向,IN 为入库,OUT 为出库
public enum Direction {
IN,
OUT
}
/// 库存变动方向
private Direction direction;
/// 起始发生时间
private Long beginAt;
@@ -28,6 +28,12 @@ public class GaoSalesOrderPage extends BasePage {
/// 付款状态
private GaoSalesOrder.PaymentStatus paymentStatus;
/// 退款状态
private GaoSalesOrder.RefundStatus refundStatus;
/// true 为筛选可收款且未付清订单
private Boolean pendingReceive;
/// true 为筛选已履约但未付清订单
private Boolean fulfilledButUnpaid;
@@ -0,0 +1,25 @@
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-05
@Data
public class GaoSalesOrderUpdateRequest {
/// 订单 ID
private String id;
/// 订单附件
private List<Attachment> attachmentList;
/// 备注
private String remark;
}
@@ -49,10 +49,10 @@ public class RoleController {
@RequireCorePermission(CorePermissionCode.ROLE_READ)
@PostMapping("/list")
public PageResult<Role> list(@RequestBody Page<Role> page) {
Set<String> manageableRoleIdSet = listManageableRoleId();
Set<String> visibleRoleIdSet = listVisibleRoleId();
PageResult<Role> result = service.page(page);
if (result.getList() != null) {
result.setList(result.getList().stream().filter(role -> manageableRoleIdSet.contains(role.getId())).toList());
result.setList(result.getList().stream().filter(role -> visibleRoleIdSet.contains(role.getId())).toList());
result.getList().forEach(this::fillRoleDetail);
}
return result;
@@ -83,7 +83,7 @@ public class RoleController {
@PostMapping("/detail")
public Role detail(@RequestParam String id) {
Role role = service.get(id);
requireManageableRole(role.getId());
requireVisibleRole(role.getId());
fillRoleDetail(role);
return role;
}
@@ -105,7 +105,7 @@ public class RoleController {
@RequireCorePermission(CorePermissionCode.ROLE_PERMISSION_READ)
@PostMapping("/permission/grantable/list")
public List<Permission> listGrantableRolePermission(@RequestParam String roleId) {
requireManageableRole(roleId);
requireVisibleRole(roleId);
return service.listGrantableRolePermission(roleId);
}
@@ -137,6 +137,12 @@ public class RoleController {
return service.listGrantableParentRole(roleId);
}
@RequireCorePermission(CorePermissionCode.ROLE_CREATE)
@PostMapping("/parent/create/grantable/list")
public List<Role> listCreateGrantableParentRole(@RequestParam ModuleCode moduleCode) {
return service.listCreateGrantableParentRole(moduleCode);
}
@AOPLog
@RequireCorePermission(CorePermissionCode.ROLE_UPDATE)
@PostMapping("/parent/update")
@@ -197,6 +203,19 @@ public class RoleController {
.collect(Collectors.toSet());
}
private Set<String> listVisibleRoleId() {
Set<String> roleIdSet = listManageableRoleId();
String loginUserId = userLoginService.getRequireLoginUserId();
roleIdSet.addAll(userRoleService.listRoleByUserId(loginUserId).stream().map(Role::getId).toList());
return roleIdSet;
}
private void requireVisibleRole(String roleId) {
if (!listVisibleRoleId().contains(roleId)) {
throw new TimiException(TimiCode.PERMISSION_ERROR);
}
}
private void requireManageableRole(String roleId) {
if (!listManageableRoleId().contains(roleId)) {
throw new TimiException(TimiCode.PERMISSION_ERROR);
@@ -75,6 +75,12 @@ public interface RoleService extends BaseService<Role, String> {
/// @return 角色列表
List<Role> listGrantableParentRole(String roleId);
/// 查询创建模块角色时可选的管理父级角色
///
/// @param moduleCode 目标模块代码
/// @return 角色列表
List<Role> listCreateGrantableParentRole(ModuleCode moduleCode);
/// 修改角色管理父级
///
/// @param role 角色
@@ -83,11 +83,12 @@ public class AuthorizationBoundaryServiceImplement implements AuthorizationBound
@Override
public void checkCanCreateRole(String operatorUserId, Role role) {
checkCorePermission(operatorUserId, CorePermissionCode.ROLE_CREATE);
TimiException.required(role.getModuleCode(), "not found role.moduleCode");
TimiException.required(role.getOwnerType(), "not found role.ownerType");
checkOwnerDefinition(role.getModuleCode(), role.getOwnerType(), role.getOwnerId());
if (authorizationScopeService.isSystemAdmin(operatorUserId)) {
return;
}
TimiException.required(role.getModuleCode(), "not found role.moduleCode");
TimiException.required(role.getOwnerType(), "not found role.ownerType");
TimiException.requiredTrue(!Boolean.TRUE.equals(role.getBuiltin()), "不能创建内置角色");
TimiException.requiredTrue(!Boolean.TRUE.equals(role.getProtectedRole()), "不能创建受保护角色");
checkOwnerScope(operatorUserId, role.getModuleCode(), role.getOwnerType(), role.getOwnerId());
@@ -98,6 +99,11 @@ public class AuthorizationBoundaryServiceImplement implements AuthorizationBound
checkCorePermission(operatorUserId, CorePermissionCode.ROLE_UPDATE);
Role dbRole = roleMapper.selectById(role.getId());
TimiException.required(dbRole, "not found role");
if (role.getOwnerType() != null || role.getOwnerId() != null) {
AuthOwnerType ownerType = TimiJava.defaultIfNull(role.getOwnerType(), dbRole.getOwnerType());
String ownerId = TimiJava.defaultIfEmpty(role.getOwnerId(), dbRole.getOwnerId());
checkOwnerDefinition(dbRole.getModuleCode(), ownerType, ownerId);
}
if (authorizationScopeService.isSystemAdmin(operatorUserId)) {
return;
}
@@ -339,6 +345,12 @@ public class AuthorizationBoundaryServiceImplement implements AuthorizationBound
TimiException.requiredTrue(resolver == null || resolver.canUseOwnerScope(operatorUserId, ownerType, ownerId), "归属范围不在可管理范围内");
}
private void checkOwnerDefinition(ModuleCode moduleCode, AuthOwnerType ownerType, String ownerId) {
if (ownerType == AuthOwnerType.MODULE) {
TimiException.requiredTrue(moduleCode.name().equals(ownerId), "模块级归属 ID 必须等于模块代码");
}
}
private void checkCorePermission(String operatorUserId, CorePermissionCode permissionCode) {
TimiException.requiredTrue(hasPermission(operatorUserId, ModuleCode.CORE, permissionCode.getValue()), "权限不足:%s".formatted(permissionCode.getValue()));
}
@@ -355,6 +355,25 @@ public class RoleServiceImplement extends AbstractEntityService<Role, String> im
.toList();
}
@Override
public List<Role> listCreateGrantableParentRole(ModuleCode moduleCode) {
String loginUserId = userLoginService.getRequireLoginUserId();
if (authorizationScopeService.isSystemAdmin(loginUserId)) {
return mapper.selectAll();
}
Set<String> directOperatorRoleIdSet = userRoleMapper.selectByUserIdList(List.of(loginUserId))
.stream()
.map(UserRole::getRoleId)
.collect(Collectors.toSet());
if (TimiJava.isEmpty(directOperatorRoleIdSet)) {
return List.of();
}
return mapper.selectByIdList(directOperatorRoleIdSet)
.stream()
.filter(role -> role.getModuleCode() == moduleCode)
.toList();
}
@Transactional(TimiServerDBConfig.ROLLBACKER)
@Override
public void updateRoleParent(Role role) {