日志记录提交
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
package com.baiyue.firstleg.controller;
|
||||
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.common.audit.BusinessAudit;
|
||||
import com.baiyue.common.core.api.PageResult;
|
||||
import com.baiyue.common.security.AuthenticatedUser;
|
||||
import com.baiyue.firstleg.dto.FeeItemDtos.SaveRequest;
|
||||
import com.baiyue.firstleg.dto.FeeItemDtos.StatusRequest;
|
||||
import com.baiyue.firstleg.entity.FeeItemEntity;
|
||||
import com.baiyue.firstleg.service.FeeItemService;
|
||||
import com.baiyue.firstleg.service.FirstlegScopeGuard;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PatchMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/** 平台端费用项目维护接口。 */
|
||||
@Tag(name = "费用项目")
|
||||
@RestController
|
||||
@RequestMapping("/api/platform/v1/fee-items")
|
||||
public class PlatformFeeItemController {
|
||||
@Resource
|
||||
private FeeItemService service;
|
||||
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAuthority('firstleg:fee-item:view')")
|
||||
@Operation(summary = "查询费用项目")
|
||||
public ApiResponse<PageResult<FeeItemEntity>> list(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam long accountId,
|
||||
@RequestParam(required = false) String category,
|
||||
@RequestParam(required = false) String tradeScope,
|
||||
@RequestParam(required = false) String keyword,
|
||||
@RequestParam(required = false) String status,
|
||||
@RequestParam(defaultValue = "1") @Min(1) long page,
|
||||
@RequestParam(defaultValue = "20") @Min(1) @Max(200) long size) {
|
||||
return ApiResponse.success(service.list(FirstlegScopeGuard.platform(user, accountId),
|
||||
category, tradeScope, keyword, status, page, size));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('firstleg:fee-item:view')")
|
||||
public ApiResponse<FeeItemEntity> detail(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam long accountId,
|
||||
@PathVariable long id) {
|
||||
return ApiResponse.success(service.detail(FirstlegScopeGuard.platform(user, accountId), id));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@BusinessAudit(event = "FEE_ITEM_CREATE", resourceType = "FEE_ITEM", description = "新增费用项目", captureBefore = false)
|
||||
@PreAuthorize("hasAuthority('firstleg:fee-item:create')")
|
||||
public ApiResponse<FeeItemEntity> create(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam long accountId,
|
||||
@Valid @RequestBody SaveRequest request) {
|
||||
long scope = FirstlegScopeGuard.platform(user, accountId);
|
||||
return ApiResponse.success(service.create(scope, user.userId(), user.username(), request));
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
@BusinessAudit(event = "FEE_ITEM_UPDATE", resourceType = "FEE_ITEM", resourceId = "#id", description = "编辑费用项目")
|
||||
@PreAuthorize("hasAuthority('firstleg:fee-item:edit')")
|
||||
public ApiResponse<FeeItemEntity> update(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam long accountId,
|
||||
@PathVariable long id,
|
||||
@Valid @RequestBody SaveRequest request) {
|
||||
long scope = FirstlegScopeGuard.platform(user, accountId);
|
||||
return ApiResponse.success(service.update(scope, id, user.userId(), user.username(), request));
|
||||
}
|
||||
|
||||
@PatchMapping("/{id}/status")
|
||||
@BusinessAudit(event = "FEE_ITEM_STATUS_CHANGE", resourceType = "FEE_ITEM", resourceId = "#id", description = "启停费用项目")
|
||||
@PreAuthorize("hasAuthority('firstleg:fee-item:edit')")
|
||||
public ApiResponse<Void> changeStatus(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam long accountId,
|
||||
@PathVariable long id,
|
||||
@Valid @RequestBody StatusRequest request) {
|
||||
service.changeStatus(FirstlegScopeGuard.platform(user, accountId), id,
|
||||
user.userId(), user.username(), request.status());
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
@BusinessAudit(event = "FEE_ITEM_DELETE", resourceType = "FEE_ITEM", resourceId = "#id", description = "删除费用项目")
|
||||
@PreAuthorize("hasAuthority('firstleg:fee-item:delete')")
|
||||
public ApiResponse<Void> delete(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam long accountId,
|
||||
@PathVariable long id) {
|
||||
service.delete(FirstlegScopeGuard.platform(user, accountId), id);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
package com.baiyue.firstleg.controller;
|
||||
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.common.audit.BusinessAudit;
|
||||
import com.baiyue.common.security.AuthenticatedUser;
|
||||
import com.baiyue.firstleg.dto.InboundFeeDetailDtos.RejectRequest;
|
||||
import com.baiyue.firstleg.dto.InboundFeeDetailDtos.SaveRequest;
|
||||
import com.baiyue.firstleg.entity.InboundFeeDetailEntity;
|
||||
import com.baiyue.firstleg.service.FirstlegScopeGuard;
|
||||
import com.baiyue.firstleg.service.InboundFeeDetailService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** 平台端入库单费用明细接口。 */
|
||||
@Tag(name = "入库单费用明细")
|
||||
@RestController
|
||||
@RequestMapping("/api/platform/v1/inbounds/{inboundId}/fee-details")
|
||||
public class PlatformInboundFeeDetailController {
|
||||
@Resource
|
||||
private InboundFeeDetailService service;
|
||||
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAuthority('firstleg:inbound:fee-detail:view')")
|
||||
@Operation(summary = "查询入库单费用明细")
|
||||
public ApiResponse<List<InboundFeeDetailEntity>> list(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam long accountId,
|
||||
@PathVariable long inboundId) {
|
||||
return ApiResponse.success(service.list(FirstlegScopeGuard.platform(user, accountId), inboundId));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@BusinessAudit(event = "INBOUND_FEE_DETAIL_CREATE", resourceType = "INBOUND", resourceId = "#inboundId", description = "新增入库费用明细", captureBefore = false)
|
||||
@PreAuthorize("hasAuthority('firstleg:inbound:fee-detail:edit')")
|
||||
public ApiResponse<InboundFeeDetailEntity> create(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam long accountId,
|
||||
@PathVariable long inboundId,
|
||||
@Valid @RequestBody SaveRequest request) {
|
||||
long scope = FirstlegScopeGuard.platform(user, accountId);
|
||||
return ApiResponse.success(service.create(scope, inboundId, user.userId(), request));
|
||||
}
|
||||
|
||||
@PutMapping("/{detailId}")
|
||||
@BusinessAudit(event = "INBOUND_FEE_DETAIL_UPDATE", resourceType = "INBOUND_FEE_DETAIL", resourceId = "#detailId", description = "编辑入库费用明细")
|
||||
@PreAuthorize("hasAuthority('firstleg:inbound:fee-detail:edit')")
|
||||
public ApiResponse<InboundFeeDetailEntity> update(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam long accountId,
|
||||
@PathVariable long inboundId,
|
||||
@PathVariable long detailId,
|
||||
@Valid @RequestBody SaveRequest request) {
|
||||
long scope = FirstlegScopeGuard.platform(user, accountId);
|
||||
return ApiResponse.success(service.update(scope, inboundId, detailId, user.userId(), request));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{detailId}")
|
||||
@BusinessAudit(event = "INBOUND_FEE_DETAIL_DELETE", resourceType = "INBOUND_FEE_DETAIL", resourceId = "#detailId", description = "删除入库费用明细")
|
||||
@PreAuthorize("hasAuthority('firstleg:inbound:fee-detail:delete')")
|
||||
public ApiResponse<Void> delete(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam long accountId,
|
||||
@PathVariable long inboundId,
|
||||
@PathVariable long detailId) {
|
||||
service.delete(FirstlegScopeGuard.platform(user, accountId), inboundId, detailId);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@PostMapping("/{detailId}/approve")
|
||||
@BusinessAudit(event = "INBOUND_FEE_DETAIL_APPROVE", resourceType = "INBOUND_FEE_DETAIL", resourceId = "#detailId", description = "审核通过入库费用明细")
|
||||
@PreAuthorize("hasAuthority('firstleg:inbound:fee-detail:review')")
|
||||
public ApiResponse<Void> approve(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam long accountId,
|
||||
@PathVariable long inboundId,
|
||||
@PathVariable long detailId) {
|
||||
service.approve(FirstlegScopeGuard.platform(user, accountId), inboundId, detailId,
|
||||
user.userId(), user.username());
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@PostMapping("/{detailId}/reject")
|
||||
@BusinessAudit(event = "INBOUND_FEE_DETAIL_REJECT", resourceType = "INBOUND_FEE_DETAIL", resourceId = "#detailId", description = "驳回入库费用明细")
|
||||
@PreAuthorize("hasAuthority('firstleg:inbound:fee-detail:review')")
|
||||
public ApiResponse<Void> reject(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam long accountId,
|
||||
@PathVariable long inboundId,
|
||||
@PathVariable long detailId,
|
||||
@Valid @RequestBody RejectRequest request) {
|
||||
service.reject(FirstlegScopeGuard.platform(user, accountId), inboundId, detailId,
|
||||
user.userId(), user.username(), request.reason());
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.baiyue.firstleg.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
/** 费用项目请求模型。 */
|
||||
public final class FeeItemDtos {
|
||||
private FeeItemDtos() {
|
||||
}
|
||||
|
||||
public record SaveRequest(
|
||||
@NotBlank @Size(max = 64) String category,
|
||||
@NotBlank @Pattern(regexp = "DOMESTIC|INTERNATIONAL") String tradeScope,
|
||||
@NotBlank @Size(max = 128) String feeTypeName,
|
||||
@NotNull Boolean pickupPayable,
|
||||
@NotNull Boolean pickupReceivable,
|
||||
@NotNull Boolean warehousePayable,
|
||||
@NotNull Boolean warehouseReceivable,
|
||||
@NotNull Boolean allocationFee,
|
||||
@Size(max = 500) String description
|
||||
) {
|
||||
}
|
||||
|
||||
public record StatusRequest(
|
||||
@NotBlank @Pattern(regexp = "ACTIVE|DISABLED") String status
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.baiyue.firstleg.dto;
|
||||
|
||||
import jakarta.validation.constraints.DecimalMin;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
|
||||
/** 入库单费用明细请求模型。 */
|
||||
public final class InboundFeeDetailDtos {
|
||||
private InboundFeeDetailDtos() {
|
||||
}
|
||||
|
||||
public record SaveRequest(
|
||||
@NotNull Long feeItemId,
|
||||
@NotBlank @Pattern(regexp = "RECEIVABLE|PAYABLE") String direction,
|
||||
@NotNull LocalDate businessDate,
|
||||
@NotNull @DecimalMin("0.00") BigDecimal unitPrice,
|
||||
@NotBlank @Pattern(regexp = "[A-Z]{3}") String currency,
|
||||
@NotNull @DecimalMin("0.00") BigDecimal amount,
|
||||
@Size(max = 500) String remark
|
||||
) {
|
||||
}
|
||||
|
||||
public record RejectRequest(@NotBlank @Size(max = 500) String reason) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.baiyue.firstleg.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baiyue.common.core.model.BaseEntity;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/** 费用项目主数据。 */
|
||||
@Getter
|
||||
@Setter
|
||||
@TableName("fl_fee_item")
|
||||
public class FeeItemEntity extends BaseEntity {
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
private Long tenantId;
|
||||
private String category;
|
||||
private String tradeScope;
|
||||
private String feeTypeName;
|
||||
private Boolean pickupPayable;
|
||||
private Boolean pickupReceivable;
|
||||
private Boolean warehousePayable;
|
||||
private Boolean warehouseReceivable;
|
||||
private Boolean allocationFee;
|
||||
private String description;
|
||||
private String status;
|
||||
private String createdByName;
|
||||
private String updatedByName;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.baiyue.firstleg.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baiyue.common.core.model.BaseEntity;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/** 入库单人工费用明细及业务快照。 */
|
||||
@Getter
|
||||
@Setter
|
||||
@TableName("fl_inbound_fee_detail")
|
||||
public class InboundFeeDetailEntity extends BaseEntity {
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
private Long tenantId;
|
||||
private Long inboundId;
|
||||
private Long feeItemId;
|
||||
private String inboundCode;
|
||||
private String direction;
|
||||
private LocalDate businessDate;
|
||||
private String feeCategorySnapshot;
|
||||
private String tradeScopeSnapshot;
|
||||
private String feeTypeNameSnapshot;
|
||||
private String channelCode;
|
||||
private String channelName;
|
||||
private String destinationCode;
|
||||
private String destinationName;
|
||||
private Integer packageCount;
|
||||
private BigDecimal actualGrossWeightKg;
|
||||
private BigDecimal actualVolumeCbm;
|
||||
private BigDecimal volumeWeightKg;
|
||||
private BigDecimal chargeableWeightKg;
|
||||
private BigDecimal chargeableVolumeCbm;
|
||||
private BigDecimal unitPrice;
|
||||
private String currency;
|
||||
private BigDecimal amount;
|
||||
private String remark;
|
||||
private String reviewStatus;
|
||||
private Long reviewerId;
|
||||
private String reviewerName;
|
||||
private LocalDateTime reviewedAt;
|
||||
private String reviewReason;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.baiyue.firstleg.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baiyue.firstleg.entity.FeeItemEntity;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface FeeItemMapper extends BaseMapper<FeeItemEntity> {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.baiyue.firstleg.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baiyue.firstleg.entity.InboundFeeDetailEntity;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface InboundFeeDetailMapper extends BaseMapper<InboundFeeDetailEntity> {
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package com.baiyue.firstleg.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baiyue.common.core.api.PageResult;
|
||||
import com.baiyue.firstleg.entity.FeeItemEntity;
|
||||
import com.baiyue.firstleg.entity.InboundFeeDetailEntity;
|
||||
import com.baiyue.firstleg.entity.InboundOrderEntity;
|
||||
import com.baiyue.firstleg.mapper.FeeItemMapper;
|
||||
import com.baiyue.firstleg.mapper.InboundFeeDetailMapper;
|
||||
import com.baiyue.firstleg.mapper.InboundOrderMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/** 费用项目与入库费用明细仓储。 */
|
||||
@Repository
|
||||
public class FeeItemRepository {
|
||||
@Resource
|
||||
private FeeItemMapper feeItems;
|
||||
@Resource
|
||||
private InboundFeeDetailMapper feeDetails;
|
||||
@Resource
|
||||
private InboundOrderMapper inboundOrders;
|
||||
|
||||
public PageResult<FeeItemEntity> listItems(long tenantId, String category, String tradeScope,
|
||||
String keyword, String status, long page, long size) {
|
||||
return PageResult.from(feeItems.selectPage(new Page<>(page, size),
|
||||
new LambdaQueryWrapper<FeeItemEntity>()
|
||||
.eq(FeeItemEntity::getTenantId, tenantId)
|
||||
.eq(StringUtils.hasText(category), FeeItemEntity::getCategory, category)
|
||||
.eq(StringUtils.hasText(tradeScope), FeeItemEntity::getTradeScope, tradeScope)
|
||||
.like(StringUtils.hasText(keyword), FeeItemEntity::getFeeTypeName, keyword)
|
||||
.eq(StringUtils.hasText(status), FeeItemEntity::getStatus, status)
|
||||
.orderByAsc(FeeItemEntity::getCategory)
|
||||
.orderByAsc(FeeItemEntity::getFeeTypeName)
|
||||
.orderByDesc(FeeItemEntity::getId)));
|
||||
}
|
||||
|
||||
public FeeItemEntity findItem(long tenantId, long id) {
|
||||
return feeItems.selectOne(new LambdaQueryWrapper<FeeItemEntity>()
|
||||
.eq(FeeItemEntity::getTenantId, tenantId)
|
||||
.eq(FeeItemEntity::getId, id));
|
||||
}
|
||||
|
||||
public boolean itemNameExists(long tenantId, String tradeScope, String feeTypeName, Long excludedId) {
|
||||
return feeItems.selectCount(new LambdaQueryWrapper<FeeItemEntity>()
|
||||
.eq(FeeItemEntity::getTenantId, tenantId)
|
||||
.eq(FeeItemEntity::getTradeScope, tradeScope)
|
||||
.eq(FeeItemEntity::getFeeTypeName, feeTypeName)
|
||||
.ne(excludedId != null, FeeItemEntity::getId, excludedId)) > 0;
|
||||
}
|
||||
|
||||
public void insertItem(FeeItemEntity item) {
|
||||
feeItems.insert(item);
|
||||
}
|
||||
|
||||
public int updateItem(FeeItemEntity item) {
|
||||
return feeItems.updateById(item);
|
||||
}
|
||||
|
||||
public int deleteItem(long id) {
|
||||
return feeItems.deleteById(id);
|
||||
}
|
||||
|
||||
public long countBindings(long tenantId, long feeItemId) {
|
||||
return feeDetails.selectCount(new LambdaQueryWrapper<InboundFeeDetailEntity>()
|
||||
.eq(InboundFeeDetailEntity::getTenantId, tenantId)
|
||||
.eq(InboundFeeDetailEntity::getFeeItemId, feeItemId));
|
||||
}
|
||||
|
||||
public InboundOrderEntity findInbound(long tenantId, long inboundId) {
|
||||
return inboundOrders.selectOne(new LambdaQueryWrapper<InboundOrderEntity>()
|
||||
.eq(InboundOrderEntity::getTenantId, tenantId)
|
||||
.eq(InboundOrderEntity::getId, inboundId));
|
||||
}
|
||||
|
||||
public List<InboundFeeDetailEntity> listDetails(long tenantId, long inboundId) {
|
||||
return feeDetails.selectList(new LambdaQueryWrapper<InboundFeeDetailEntity>()
|
||||
.eq(InboundFeeDetailEntity::getTenantId, tenantId)
|
||||
.eq(InboundFeeDetailEntity::getInboundId, inboundId)
|
||||
.orderByDesc(InboundFeeDetailEntity::getBusinessDate)
|
||||
.orderByDesc(InboundFeeDetailEntity::getId));
|
||||
}
|
||||
|
||||
public InboundFeeDetailEntity findDetail(long tenantId, long inboundId, long detailId) {
|
||||
return feeDetails.selectOne(new LambdaQueryWrapper<InboundFeeDetailEntity>()
|
||||
.eq(InboundFeeDetailEntity::getTenantId, tenantId)
|
||||
.eq(InboundFeeDetailEntity::getInboundId, inboundId)
|
||||
.eq(InboundFeeDetailEntity::getId, detailId));
|
||||
}
|
||||
|
||||
public void insertDetail(InboundFeeDetailEntity detail) {
|
||||
feeDetails.insert(detail);
|
||||
}
|
||||
|
||||
public int updateDetail(InboundFeeDetailEntity detail) {
|
||||
return feeDetails.updateById(detail);
|
||||
}
|
||||
|
||||
public int deleteDetail(long detailId) {
|
||||
return feeDetails.deleteById(detailId);
|
||||
}
|
||||
|
||||
public int review(long tenantId, long inboundId, long detailId, String targetStatus,
|
||||
long reviewerId, String reviewerName, String reason) {
|
||||
return feeDetails.update(null, new LambdaUpdateWrapper<InboundFeeDetailEntity>()
|
||||
.eq(InboundFeeDetailEntity::getTenantId, tenantId)
|
||||
.eq(InboundFeeDetailEntity::getInboundId, inboundId)
|
||||
.eq(InboundFeeDetailEntity::getId, detailId)
|
||||
.eq(InboundFeeDetailEntity::getReviewStatus, "PENDING")
|
||||
.set(InboundFeeDetailEntity::getReviewStatus, targetStatus)
|
||||
.set(InboundFeeDetailEntity::getReviewerId, reviewerId)
|
||||
.set(InboundFeeDetailEntity::getReviewerName, reviewerName)
|
||||
.set(InboundFeeDetailEntity::getReviewedAt, LocalDateTime.now())
|
||||
.set(InboundFeeDetailEntity::getReviewReason, reason)
|
||||
.set(InboundFeeDetailEntity::getUpdatedBy, reviewerId));
|
||||
}
|
||||
}
|
||||
+3
@@ -2,6 +2,7 @@ package com.baiyue.firstleg.service;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import com.baiyue.common.audit.BusinessAudit;
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.firstleg.dto.FbaPricingDtos.FeeSnapshotResponse;
|
||||
@@ -18,6 +19,8 @@ public class AutomaticInboundCompletionService {
|
||||
@Resource
|
||||
private InboundCompletionOrchestrator completion;
|
||||
|
||||
@BusinessAudit(event = "INBOUND_AUTO_COMPLETE", resourceType = "INBOUND", resourceId = "#inboundId",
|
||||
description = "测量审核通过后自动计费、发送费用并完成入库", captureBefore = false)
|
||||
public FeeSnapshotResponse completeApproved(long tenantId, long inboundId, long operatorId,
|
||||
String operatorName, long measurementId,
|
||||
int reviewedOrderVersion) {
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
package com.baiyue.firstleg.service;
|
||||
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.api.PageResult;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.firstleg.dto.FeeItemDtos.SaveRequest;
|
||||
import com.baiyue.firstleg.entity.FeeItemEntity;
|
||||
import com.baiyue.firstleg.repository.FeeItemRepository;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/** 费用项目主数据应用服务。 */
|
||||
@Service
|
||||
public class FeeItemService {
|
||||
@Resource
|
||||
private FeeItemRepository repository;
|
||||
|
||||
public PageResult<FeeItemEntity> list(long tenantId, String category, String tradeScope,
|
||||
String keyword, String status, long page, long size) {
|
||||
return repository.listItems(tenantId, category, tradeScope, keyword, status, page, size);
|
||||
}
|
||||
|
||||
public FeeItemEntity detail(long tenantId, long id) {
|
||||
return required(tenantId, id);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public FeeItemEntity create(long tenantId, long operatorId, String operatorName, SaveRequest request) {
|
||||
ensureUnique(tenantId, request, null);
|
||||
FeeItemEntity value = new FeeItemEntity();
|
||||
value.setTenantId(tenantId);
|
||||
copy(value, request);
|
||||
value.setStatus("ACTIVE");
|
||||
value.setCreatedBy(operatorId);
|
||||
value.setUpdatedBy(operatorId);
|
||||
value.setCreatedByName(operatorName);
|
||||
value.setUpdatedByName(operatorName);
|
||||
repository.insertItem(value);
|
||||
return value;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public FeeItemEntity update(long tenantId, long id, long operatorId, String operatorName,
|
||||
SaveRequest request) {
|
||||
FeeItemEntity value = required(tenantId, id);
|
||||
ensureUnique(tenantId, request, id);
|
||||
copy(value, request);
|
||||
value.setUpdatedBy(operatorId);
|
||||
value.setUpdatedByName(operatorName);
|
||||
if (repository.updateItem(value) != 1) throw conflict("费用项目已被其他人修改,请刷新后重试");
|
||||
return required(tenantId, id);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void changeStatus(long tenantId, long id, long operatorId, String operatorName, String status) {
|
||||
FeeItemEntity value = required(tenantId, id);
|
||||
value.setStatus(status);
|
||||
value.setUpdatedBy(operatorId);
|
||||
value.setUpdatedByName(operatorName);
|
||||
if (repository.updateItem(value) != 1) throw conflict("费用项目状态更新冲突");
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void delete(long tenantId, long id) {
|
||||
required(tenantId, id);
|
||||
if (repository.countBindings(tenantId, id) > 0) {
|
||||
throw conflict("费用项目已被入库费用明细使用,只能停用,不能删除");
|
||||
}
|
||||
if (repository.deleteItem(id) != 1) throw conflict("费用项目删除冲突");
|
||||
}
|
||||
|
||||
private FeeItemEntity required(long tenantId, long id) {
|
||||
FeeItemEntity value = repository.findItem(tenantId, id);
|
||||
if (value == null) throw new BizException(ResultCode.NOT_FOUND, "费用项目不存在");
|
||||
return value;
|
||||
}
|
||||
|
||||
private void ensureUnique(long tenantId, SaveRequest request, Long excludedId) {
|
||||
if (repository.itemNameExists(tenantId, request.tradeScope(), request.feeTypeName(), excludedId)) {
|
||||
throw conflict("相同国内国际范围下的费用类型名称已存在");
|
||||
}
|
||||
}
|
||||
|
||||
private static void copy(FeeItemEntity value, SaveRequest request) {
|
||||
value.setCategory(request.category().trim());
|
||||
value.setTradeScope(request.tradeScope());
|
||||
value.setFeeTypeName(request.feeTypeName().trim());
|
||||
value.setPickupPayable(request.pickupPayable());
|
||||
value.setPickupReceivable(request.pickupReceivable());
|
||||
value.setWarehousePayable(request.warehousePayable());
|
||||
value.setWarehouseReceivable(request.warehouseReceivable());
|
||||
value.setAllocationFee(request.allocationFee());
|
||||
value.setDescription(request.description() == null ? null : request.description().trim());
|
||||
}
|
||||
|
||||
private static BizException conflict(String message) {
|
||||
return new BizException(ResultCode.CONFLICT, message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package com.baiyue.firstleg.service;
|
||||
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.firstleg.dto.InboundFeeDetailDtos.SaveRequest;
|
||||
import com.baiyue.firstleg.entity.FeeItemEntity;
|
||||
import com.baiyue.firstleg.entity.InboundFeeDetailEntity;
|
||||
import com.baiyue.firstleg.entity.InboundOrderEntity;
|
||||
import com.baiyue.firstleg.repository.FeeItemRepository;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.RoundingMode;
|
||||
import java.util.List;
|
||||
|
||||
/** 入库单人工费用明细应用服务。 */
|
||||
@Service
|
||||
public class InboundFeeDetailService {
|
||||
@Resource
|
||||
private FeeItemRepository repository;
|
||||
|
||||
public List<InboundFeeDetailEntity> list(long tenantId, long inboundId) {
|
||||
requiredInbound(tenantId, inboundId);
|
||||
return repository.listDetails(tenantId, inboundId);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public InboundFeeDetailEntity create(long tenantId, long inboundId, long operatorId,
|
||||
SaveRequest request) {
|
||||
InboundOrderEntity inbound = requiredInbound(tenantId, inboundId);
|
||||
FeeItemEntity item = requiredActiveItem(tenantId, request.feeItemId());
|
||||
InboundFeeDetailEntity value = new InboundFeeDetailEntity();
|
||||
value.setTenantId(tenantId);
|
||||
value.setInboundId(inboundId);
|
||||
value.setCreatedBy(operatorId);
|
||||
apply(value, inbound, item, request, operatorId);
|
||||
value.setReviewStatus("PENDING");
|
||||
repository.insertDetail(value);
|
||||
return value;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public InboundFeeDetailEntity update(long tenantId, long inboundId, long detailId,
|
||||
long operatorId, SaveRequest request) {
|
||||
InboundFeeDetailEntity value = requiredEditable(tenantId, inboundId, detailId);
|
||||
InboundOrderEntity inbound = requiredInbound(tenantId, inboundId);
|
||||
FeeItemEntity item = requiredActiveItem(tenantId, request.feeItemId());
|
||||
apply(value, inbound, item, request, operatorId);
|
||||
value.setReviewStatus("PENDING");
|
||||
value.setReviewerId(null);
|
||||
value.setReviewerName(null);
|
||||
value.setReviewedAt(null);
|
||||
value.setReviewReason(null);
|
||||
if (repository.updateDetail(value) != 1) throw conflict("费用明细已被其他人修改,请刷新后重试");
|
||||
return required(tenantId, inboundId, detailId);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void delete(long tenantId, long inboundId, long detailId) {
|
||||
requiredEditable(tenantId, inboundId, detailId);
|
||||
if (repository.deleteDetail(detailId) != 1) throw conflict("费用明细删除冲突");
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void approve(long tenantId, long inboundId, long detailId,
|
||||
long reviewerId, String reviewerName) {
|
||||
required(tenantId, inboundId, detailId);
|
||||
if (repository.review(tenantId, inboundId, detailId, "APPROVED",
|
||||
reviewerId, reviewerName, null) != 1) {
|
||||
throw conflict("只有待审核费用明细可以审核通过");
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void reject(long tenantId, long inboundId, long detailId, long reviewerId,
|
||||
String reviewerName, String reason) {
|
||||
required(tenantId, inboundId, detailId);
|
||||
if (repository.review(tenantId, inboundId, detailId, "REJECTED",
|
||||
reviewerId, reviewerName, reason.trim()) != 1) {
|
||||
throw conflict("只有待审核费用明细可以驳回");
|
||||
}
|
||||
}
|
||||
|
||||
private InboundOrderEntity requiredInbound(long tenantId, long inboundId) {
|
||||
InboundOrderEntity value = repository.findInbound(tenantId, inboundId);
|
||||
if (value == null) throw new BizException(ResultCode.NOT_FOUND, "入库单不存在");
|
||||
return value;
|
||||
}
|
||||
|
||||
private FeeItemEntity requiredActiveItem(long tenantId, long itemId) {
|
||||
FeeItemEntity value = repository.findItem(tenantId, itemId);
|
||||
if (value == null) throw new BizException(ResultCode.NOT_FOUND, "费用项目不存在");
|
||||
if (!"ACTIVE".equals(value.getStatus())) throw conflict("费用项目已停用,不能绑定");
|
||||
return value;
|
||||
}
|
||||
|
||||
private static void ensureWarehouseApplicable(FeeItemEntity item, String direction) {
|
||||
boolean applicable = "RECEIVABLE".equals(direction)
|
||||
? Boolean.TRUE.equals(item.getWarehouseReceivable())
|
||||
: Boolean.TRUE.equals(item.getWarehousePayable());
|
||||
if (!applicable) {
|
||||
throw conflict("所选费用项目未启用对应的仓储应收/应付,不能绑定入库单");
|
||||
}
|
||||
}
|
||||
|
||||
private InboundFeeDetailEntity required(long tenantId, long inboundId, long detailId) {
|
||||
InboundFeeDetailEntity value = repository.findDetail(tenantId, inboundId, detailId);
|
||||
if (value == null) throw new BizException(ResultCode.NOT_FOUND, "入库费用明细不存在");
|
||||
return value;
|
||||
}
|
||||
|
||||
private InboundFeeDetailEntity requiredEditable(long tenantId, long inboundId, long detailId) {
|
||||
InboundFeeDetailEntity value = required(tenantId, inboundId, detailId);
|
||||
if ("APPROVED".equals(value.getReviewStatus())) throw conflict("审核通过的费用明细不能修改或删除");
|
||||
return value;
|
||||
}
|
||||
|
||||
private static void apply(InboundFeeDetailEntity value, InboundOrderEntity inbound,
|
||||
FeeItemEntity item, SaveRequest request, long operatorId) {
|
||||
ensureWarehouseApplicable(item, request.direction());
|
||||
value.setFeeItemId(item.getId());
|
||||
value.setInboundCode(inbound.getInboundCode());
|
||||
value.setDirection(request.direction());
|
||||
value.setBusinessDate(request.businessDate());
|
||||
value.setFeeCategorySnapshot(item.getCategory());
|
||||
value.setTradeScopeSnapshot(item.getTradeScope());
|
||||
value.setFeeTypeNameSnapshot(item.getFeeTypeName());
|
||||
value.setChannelCode(inbound.getChannelCode());
|
||||
value.setChannelName(inbound.getChannelName());
|
||||
value.setDestinationCode(inbound.getDestinationCode());
|
||||
value.setDestinationName(inbound.getDestinationName());
|
||||
value.setPackageCount(inbound.getTotalPackageCount());
|
||||
value.setActualGrossWeightKg(inbound.getActualGrossWeightKg());
|
||||
value.setActualVolumeCbm(inbound.getActualVolumeCbm());
|
||||
value.setVolumeWeightKg(inbound.getVolumeWeightKg());
|
||||
value.setChargeableWeightKg(inbound.getChargeableWeightKg());
|
||||
value.setChargeableVolumeCbm(inbound.getChargeableVolumeCbm());
|
||||
value.setUnitPrice(request.unitPrice().setScale(4, RoundingMode.HALF_UP));
|
||||
value.setCurrency(request.currency());
|
||||
value.setAmount(request.amount().setScale(2, RoundingMode.HALF_UP));
|
||||
value.setRemark(request.remark() == null ? null : request.remark().trim());
|
||||
value.setUpdatedBy(operatorId);
|
||||
}
|
||||
|
||||
private static BizException conflict(String message) {
|
||||
return new BizException(ResultCode.CONFLICT, message);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user