优化
This commit is contained in:
+34
-9
@@ -6,10 +6,13 @@ import com.baiyue.common.security.AuthenticatedUser;
|
||||
import com.baiyue.lastleg.procurement.dto.OverseasProcurementRequests;
|
||||
import com.baiyue.lastleg.procurement.dto.OverseasProcurementResponses;
|
||||
import com.baiyue.lastleg.procurement.service.OverseasProcurementService;
|
||||
import com.baiyue.lastleg.security.OverseasPermissions;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PatchMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
@@ -20,12 +23,16 @@ import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/platform/v1/last-leg/overseas-procurements")
|
||||
public class PlatformOverseasProcurementController {
|
||||
@Resource private OverseasProcurementService service;
|
||||
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAuthority('" + OverseasPermissions.PURCHASE_LIST + "')")
|
||||
@Operation(summary = "Page overseas procurement orders", description = "Required authority: " + OverseasPermissions.PURCHASE_LIST)
|
||||
public ApiResponse<PageResult<OverseasProcurementResponses.Summary>> page(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam(required = false) String customerId,
|
||||
@@ -34,7 +41,29 @@ public class PlatformOverseasProcurementController {
|
||||
return ApiResponse.success(service.page(trim(customerId), query));
|
||||
}
|
||||
|
||||
@GetMapping("/customer-options")
|
||||
@PreAuthorize("hasAuthority('" + OverseasPermissions.PURCHASE_LIST + "')")
|
||||
@Operation(summary = "List procurement customer options", description = "Required authority: " + OverseasPermissions.PURCHASE_LIST)
|
||||
public ApiResponse<List<OverseasProcurementResponses.CustomerOption>> customerOptions(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam(required = false) String keyword,
|
||||
@RequestParam(defaultValue = "50") int limit) {
|
||||
requirePlatform(user);
|
||||
return ApiResponse.success(service.customerOptions(keyword, limit));
|
||||
}
|
||||
|
||||
@GetMapping("/warehouse-options")
|
||||
@PreAuthorize("hasAuthority('" + OverseasPermissions.PURCHASE_LIST + "')")
|
||||
@Operation(summary = "List procurement warehouse options", description = "Required authority: " + OverseasPermissions.PURCHASE_LIST)
|
||||
public ApiResponse<List<OverseasProcurementResponses.WarehouseOption>> warehouseOptions(
|
||||
@AuthenticationPrincipal AuthenticatedUser user) {
|
||||
requirePlatform(user);
|
||||
return ApiResponse.success(service.warehouseOptions());
|
||||
}
|
||||
|
||||
@GetMapping("/{orderNo}")
|
||||
@PreAuthorize("hasAuthority('" + OverseasPermissions.PURCHASE_LIST + "')")
|
||||
@Operation(summary = "Get an overseas procurement order", description = "Required authority: " + OverseasPermissions.PURCHASE_LIST)
|
||||
public ApiResponse<OverseasProcurementResponses.Detail> detail(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @RequestParam String customerId,
|
||||
@PathVariable String orderNo) {
|
||||
@@ -43,6 +72,8 @@ public class PlatformOverseasProcurementController {
|
||||
}
|
||||
|
||||
@PostMapping("/{orderNo}/review")
|
||||
@PreAuthorize("hasAuthority('" + OverseasPermissions.PURCHASE_REVIEW + "')")
|
||||
@Operation(summary = "Review an overseas procurement order", description = "Required authority: " + OverseasPermissions.PURCHASE_REVIEW)
|
||||
public ApiResponse<OverseasProcurementResponses.Detail> review(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @RequestParam String customerId,
|
||||
@PathVariable String orderNo, @Valid @RequestBody OverseasProcurementRequests.Review request) {
|
||||
@@ -51,19 +82,13 @@ public class PlatformOverseasProcurementController {
|
||||
}
|
||||
|
||||
@PatchMapping("/{orderNo}")
|
||||
@PreAuthorize("hasAuthority('" + OverseasPermissions.PURCHASE_EDIT + "')")
|
||||
@Operation(summary = "Update an overseas procurement order", description = "Required authority: " + OverseasPermissions.PURCHASE_EDIT)
|
||||
public ApiResponse<OverseasProcurementResponses.Detail> update(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @RequestParam String customerId,
|
||||
@PathVariable String orderNo, @Valid @RequestBody OverseasProcurementRequests.Update request) {
|
||||
requirePermission(user, OverseasProcurementService.MANAGE_PERMISSION);
|
||||
return ApiResponse.success(service.update(requiredCustomer(customerId), orderNo, request));
|
||||
}
|
||||
|
||||
@PostMapping("/{orderNo}/outbounds")
|
||||
public ApiResponse<OverseasProcurementResponses.Detail> addOutbound(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @RequestParam String customerId,
|
||||
@PathVariable String orderNo, @Valid @RequestBody OverseasProcurementRequests.AddOutbound request) {
|
||||
requirePermission(user, OverseasProcurementService.MANAGE_PERMISSION);
|
||||
return ApiResponse.success(service.addOutbound(requiredCustomer(customerId), orderNo, operator(user), request));
|
||||
return ApiResponse.success(service.update(requiredCustomer(customerId), orderNo, operator(user), request));
|
||||
}
|
||||
|
||||
private void requirePlatform(AuthenticatedUser user) {
|
||||
|
||||
+11
-18
@@ -1,5 +1,6 @@
|
||||
package com.baiyue.lastleg.procurement.dto;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import jakarta.validation.constraints.DecimalMin;
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.Min;
|
||||
@@ -41,23 +42,15 @@ public final class OverseasProcurementRequests {
|
||||
@NotNull @Min(0) Long version,
|
||||
@Size(max = 128) String purchaseCategory,
|
||||
@Size(max = 255) String productName,
|
||||
@Size(max = 500) String productSpecification,
|
||||
@Size(max = 2000) String productUrl,
|
||||
JsonNode productSpecification,
|
||||
JsonNode productUrl,
|
||||
@Min(0) Long inboundQuantity,
|
||||
@Size(max = 128) String purchaseWarehouseId,
|
||||
@Size(max = 255) String purchaseWarehouseName,
|
||||
@Size(max = 128) String domesticCarrier,
|
||||
@Size(max = 128) String domesticTrackingNo,
|
||||
@Size(max = 128) String overseasCarrier,
|
||||
@Size(max = 128) String overseasTrackingNo,
|
||||
LocalDateTime estimatedArrivalAt,
|
||||
@Size(max = 1000) String remark) {}
|
||||
|
||||
public record AddOutbound(
|
||||
@NotNull @Positive Long quantity,
|
||||
@NotBlank @Size(max = 128) String outboundNo,
|
||||
@Size(max = 128) String carrier,
|
||||
@Size(max = 128) String trackingNo,
|
||||
LocalDateTime outboundAt,
|
||||
@Size(max = 1000) String remark) {}
|
||||
JsonNode purchaseWarehouseId,
|
||||
JsonNode purchaseWarehouseName,
|
||||
JsonNode domesticCarrier,
|
||||
JsonNode domesticTrackingNo,
|
||||
JsonNode overseasCarrier,
|
||||
JsonNode overseasTrackingNo,
|
||||
JsonNode estimatedArrivalAt,
|
||||
JsonNode remark) {}
|
||||
}
|
||||
|
||||
+8
-6
@@ -12,15 +12,17 @@ public final class OverseasProcurementResponses {
|
||||
String purchaseCategory, String productName, String productSpecification,
|
||||
long purchaseQuantity, BigDecimal budgetUnitPrice, String currency,
|
||||
String purchaseWarehouseId, String purchaseWarehouseName,
|
||||
long inboundQuantity, long outboundQuantity, long inventoryQuantity,
|
||||
long inboundQuantity, long inventoryQuantity,
|
||||
String status, long version, LocalDateTime createdAt, LocalDateTime updatedAt) {}
|
||||
|
||||
public record Outbound(
|
||||
Long id, long quantity, String outboundNo, String carrier, String trackingNo,
|
||||
LocalDateTime outboundAt, String remark, String createdBy, LocalDateTime createdAt) {}
|
||||
public record CustomerOption(String customerId, String customerName) {}
|
||||
|
||||
public record WarehouseOption(
|
||||
String warehouseId, String warehouseCode, String warehouseName, String countryCode) {}
|
||||
|
||||
public record History(
|
||||
String fromStatus, String toStatus, String operatorId, String remark,
|
||||
String operationType, String fromStatus, String toStatus, String operatorId,
|
||||
String changeDetail, String remark,
|
||||
LocalDateTime createdAt) {}
|
||||
|
||||
public record Detail(
|
||||
@@ -30,5 +32,5 @@ public final class OverseasProcurementResponses {
|
||||
String overseasCarrier, String overseasTrackingNo,
|
||||
LocalDateTime estimatedArrivalAt, String remark,
|
||||
String createdBy, String reviewedBy, String reviewRemark, LocalDateTime reviewedAt,
|
||||
List<Outbound> outbounds, List<History> histories) {}
|
||||
List<History> histories) {}
|
||||
}
|
||||
|
||||
-1
@@ -28,7 +28,6 @@ public class OverseasProcurementOrder {
|
||||
private String purchaseWarehouseId;
|
||||
private String purchaseWarehouseName;
|
||||
private Long inboundQuantity;
|
||||
private Long outboundQuantity;
|
||||
private String domesticCarrier;
|
||||
private String domesticTrackingNo;
|
||||
private String overseasCarrier;
|
||||
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
package com.baiyue.lastleg.procurement.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@TableName("ll_overseas_procurement_outbound")
|
||||
public class OverseasProcurementOutbound {
|
||||
@TableId(type = IdType.INPUT) private Long id;
|
||||
private Long orderId;
|
||||
private String customerId;
|
||||
private Long quantity;
|
||||
private String outboundNo;
|
||||
private String carrier;
|
||||
private String trackingNo;
|
||||
private LocalDateTime outboundAt;
|
||||
private String remark;
|
||||
private String createdBy;
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
+2
@@ -13,9 +13,11 @@ public class OverseasProcurementStatusHistory {
|
||||
@TableId(type = IdType.INPUT) private Long id;
|
||||
private Long orderId;
|
||||
private String customerId;
|
||||
private String operationType;
|
||||
private String fromStatus;
|
||||
private String toStatus;
|
||||
private String operatorId;
|
||||
private String changeDetail;
|
||||
private String remark;
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
|
||||
+8
-5
@@ -5,7 +5,8 @@ import com.baiyue.lastleg.procurement.entity.OverseasProcurementOrder;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface OverseasProcurementOrderMapper extends BaseMapper<OverseasProcurementOrder> {
|
||||
@@ -18,8 +19,10 @@ public interface OverseasProcurementOrderMapper extends BaseMapper<OverseasProcu
|
||||
@Select("SELECT * FROM ll_overseas_procurement_order WHERE customer_id=#{customerId} AND order_no=#{orderNo} LIMIT 1 FOR UPDATE")
|
||||
OverseasProcurementOrder lockNo(@Param("customerId") String customerId, @Param("orderNo") String orderNo);
|
||||
|
||||
@Update("UPDATE ll_overseas_procurement_order SET outbound_quantity=outbound_quantity+#{quantity},version=version+1," +
|
||||
"updated_at=CURRENT_TIMESTAMP(3) WHERE id=#{id} AND customer_id=#{customerId} " +
|
||||
"AND inbound_quantity-outbound_quantity>=#{quantity}")
|
||||
int addOutbound(@Param("id") Long id, @Param("customerId") String customerId, @Param("quantity") long quantity);
|
||||
@Select("<script>SELECT customer_id,MAX(customer_name) AS customer_name " +
|
||||
"FROM ll_overseas_procurement_order " +
|
||||
"<if test='keyword != null and keyword != ""'>" +
|
||||
"WHERE customer_id LIKE CONCAT('%',#{keyword},'%') OR customer_name LIKE CONCAT('%',#{keyword},'%')" +
|
||||
"</if> GROUP BY customer_id ORDER BY MAX(customer_name),customer_id LIMIT #{limit}</script>")
|
||||
List<OverseasProcurementOrder> customerOptions(@Param("keyword") String keyword, @Param("limit") int limit);
|
||||
}
|
||||
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
package com.baiyue.lastleg.procurement.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baiyue.lastleg.procurement.entity.OverseasProcurementOutbound;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface OverseasProcurementOutboundMapper extends BaseMapper<OverseasProcurementOutbound> {
|
||||
@Select("SELECT * FROM ll_overseas_procurement_outbound WHERE customer_id=#{customerId} AND order_id=#{orderId} ORDER BY outbound_at DESC,id DESC")
|
||||
List<OverseasProcurementOutbound> listOrder(@Param("customerId") String customerId,
|
||||
@Param("orderId") Long orderId);
|
||||
}
|
||||
+139
-68
@@ -4,15 +4,16 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baiyue.common.core.api.PageResult;
|
||||
import com.baiyue.lastleg.masterdata.quote.service.PartnerWarehouseDirectory;
|
||||
import com.baiyue.lastleg.procurement.dto.OverseasProcurementRequests;
|
||||
import com.baiyue.lastleg.procurement.dto.OverseasProcurementResponses;
|
||||
import com.baiyue.lastleg.procurement.entity.OverseasProcurementOrder;
|
||||
import com.baiyue.lastleg.procurement.entity.OverseasProcurementOutbound;
|
||||
import com.baiyue.lastleg.procurement.entity.OverseasProcurementStatusHistory;
|
||||
import com.baiyue.lastleg.procurement.mapper.OverseasProcurementOrderMapper;
|
||||
import com.baiyue.lastleg.procurement.mapper.OverseasProcurementOutboundMapper;
|
||||
import com.baiyue.lastleg.procurement.mapper.OverseasProcurementStatusHistoryMapper;
|
||||
import com.baiyue.lastleg.security.OverseasPermissions;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
@@ -26,21 +27,26 @@ import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.HexFormat;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
@Service
|
||||
public class OverseasProcurementService {
|
||||
public static final String REVIEW_PERMISSION = "lastleg:overseas-procurement:review";
|
||||
public static final String MANAGE_PERMISSION = "lastleg:overseas-procurement:manage";
|
||||
public static final String REVIEW_PERMISSION = OverseasPermissions.PURCHASE_REVIEW;
|
||||
public static final String MANAGE_PERMISSION = OverseasPermissions.PURCHASE_EDIT;
|
||||
public static final String PENDING_REVIEW = "PENDING_REVIEW";
|
||||
public static final String APPROVED = "APPROVED";
|
||||
public static final String REJECTED = "REJECTED";
|
||||
private static final Set<String> STATUSES = Set.of(PENDING_REVIEW, APPROVED, REJECTED);
|
||||
|
||||
@Resource private OverseasProcurementOrderMapper orders;
|
||||
@Resource private OverseasProcurementOutboundMapper outbounds;
|
||||
@Resource private OverseasProcurementStatusHistoryMapper histories;
|
||||
@Resource private PartnerWarehouseDirectory warehouseDirectory;
|
||||
@Resource private ObjectMapper objectMapper;
|
||||
|
||||
@Transactional
|
||||
@@ -68,7 +74,6 @@ public class OverseasProcurementService {
|
||||
order.setContactName(request.contactName().trim());
|
||||
order.setContactPhone(request.contactPhone().trim());
|
||||
order.setInboundQuantity(0L);
|
||||
order.setOutboundQuantity(0L);
|
||||
order.setStatus(PENDING_REVIEW);
|
||||
order.setRemark(trim(request.remark()));
|
||||
order.setIdempotencyKey(key);
|
||||
@@ -84,10 +89,25 @@ public class OverseasProcurementService {
|
||||
if (concurrent != null) return replay(customerId, concurrent, requestHash);
|
||||
throw exception;
|
||||
}
|
||||
history(order, null, PENDING_REVIEW, operatorId, request.remark(), now);
|
||||
history(order, "CREATE", null, PENDING_REVIEW, operatorId, null, request.remark(), now);
|
||||
return detail(customerId, order.getOrderNo());
|
||||
}
|
||||
|
||||
public List<OverseasProcurementResponses.CustomerOption> customerOptions(String keyword, int limit) {
|
||||
if (limit < 1 || limit > 200) throw badRequest("limit must be between 1 and 200");
|
||||
return orders.customerOptions(trim(keyword), limit).stream()
|
||||
.map(value -> new OverseasProcurementResponses.CustomerOption(value.getCustomerId(),
|
||||
Objects.requireNonNullElse(trim(value.getCustomerName()), value.getCustomerId())))
|
||||
.toList();
|
||||
}
|
||||
|
||||
public List<OverseasProcurementResponses.WarehouseOption> warehouseOptions() {
|
||||
return warehouseDirectory.options().stream()
|
||||
.map(value -> new OverseasProcurementResponses.WarehouseOption(value.warehouseId(),
|
||||
value.warehouseCode(), value.warehouseName(), value.countryCode()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
public PageResult<OverseasProcurementResponses.Summary> page(
|
||||
String customerId, OverseasProcurementRequests.Query query) {
|
||||
validateQuery(query);
|
||||
@@ -122,10 +142,10 @@ public class OverseasProcurementService {
|
||||
order.getDomesticCarrier(), order.getDomesticTrackingNo(), order.getOverseasCarrier(),
|
||||
order.getOverseasTrackingNo(), order.getEstimatedArrivalAt(), order.getRemark(),
|
||||
order.getCreatedBy(), order.getReviewedBy(), order.getReviewRemark(), order.getReviewedAt(),
|
||||
outbounds.listOrder(customerId, order.getId()).stream().map(this::outbound).toList(),
|
||||
histories.listOrder(customerId, order.getId()).stream().map(value ->
|
||||
new OverseasProcurementResponses.History(value.getFromStatus(), value.getToStatus(),
|
||||
value.getOperatorId(), value.getRemark(), value.getCreatedAt())).toList());
|
||||
new OverseasProcurementResponses.History(value.getOperationType(), value.getFromStatus(),
|
||||
value.getToStatus(), value.getOperatorId(), value.getChangeDetail(), value.getRemark(),
|
||||
value.getCreatedAt())).toList());
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@@ -146,64 +166,38 @@ public class OverseasProcurementService {
|
||||
order.setReviewedAt(now);
|
||||
touch(order, now);
|
||||
orders.updateById(order);
|
||||
history(order, PENDING_REVIEW, next, operatorId, request.remark(), now);
|
||||
history(order, "REVIEW", PENDING_REVIEW, next, operatorId, null, request.remark(), now);
|
||||
return detail(customerId, orderNo);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public OverseasProcurementResponses.Detail update(String customerId, String orderNo,
|
||||
public OverseasProcurementResponses.Detail update(String customerId, String orderNo, String operatorId,
|
||||
OverseasProcurementRequests.Update request) {
|
||||
OverseasProcurementOrder order = lock(customerId, orderNo);
|
||||
if (!request.version().equals(order.getVersion())) throw conflict("Order was changed; refresh and retry");
|
||||
Map<String, Object> before = editableSnapshot(order);
|
||||
if (request.inboundQuantity() != null) {
|
||||
if (!APPROVED.equals(order.getStatus())) throw conflict("Only an approved procurement order can receive stock");
|
||||
if (request.inboundQuantity() < value(order.getOutboundQuantity())) {
|
||||
throw conflict("Inbound quantity cannot be lower than the recorded outbound quantity");
|
||||
}
|
||||
order.setInboundQuantity(request.inboundQuantity());
|
||||
}
|
||||
setIfText(request.purchaseCategory(), order::setPurchaseCategory);
|
||||
setIfText(request.productName(), order::setProductName);
|
||||
setIfPresent(request.productSpecification(), order::setProductSpecification);
|
||||
setIfPresent(request.productUrl(), order::setProductUrl);
|
||||
setIfPresent(request.purchaseWarehouseId(), order::setPurchaseWarehouseId);
|
||||
setIfPresent(request.purchaseWarehouseName(), order::setPurchaseWarehouseName);
|
||||
setIfPresent(request.domesticCarrier(), order::setDomesticCarrier);
|
||||
setIfPresent(request.domesticTrackingNo(), order::setDomesticTrackingNo);
|
||||
setIfPresent(request.overseasCarrier(), order::setOverseasCarrier);
|
||||
setIfPresent(request.overseasTrackingNo(), order::setOverseasTrackingNo);
|
||||
if (request.estimatedArrivalAt() != null) order.setEstimatedArrivalAt(request.estimatedArrivalAt());
|
||||
setIfPresent(request.remark(), order::setRemark);
|
||||
touch(order, LocalDateTime.now());
|
||||
patchText(request.productSpecification(), 500, "productSpecification", order::setProductSpecification);
|
||||
patchText(request.productUrl(), 2000, "productUrl", order::setProductUrl);
|
||||
patchWarehouse(order, request.purchaseWarehouseId(), request.purchaseWarehouseName());
|
||||
patchText(request.domesticCarrier(), 128, "domesticCarrier", order::setDomesticCarrier);
|
||||
patchText(request.domesticTrackingNo(), 128, "domesticTrackingNo", order::setDomesticTrackingNo);
|
||||
patchText(request.overseasCarrier(), 128, "overseasCarrier", order::setOverseasCarrier);
|
||||
patchText(request.overseasTrackingNo(), 128, "overseasTrackingNo", order::setOverseasTrackingNo);
|
||||
patchDateTime(request.estimatedArrivalAt(), order::setEstimatedArrivalAt);
|
||||
patchText(request.remark(), 1000, "remark", order::setRemark);
|
||||
Map<String, Object> after = editableSnapshot(order);
|
||||
if (before.equals(after)) return detail(customerId, orderNo);
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
touch(order, now);
|
||||
orders.updateById(order);
|
||||
return detail(customerId, orderNo);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public OverseasProcurementResponses.Detail addOutbound(String customerId, String orderNo, String operatorId,
|
||||
OverseasProcurementRequests.AddOutbound request) {
|
||||
OverseasProcurementOrder order = lock(customerId, orderNo);
|
||||
if (!APPROVED.equals(order.getStatus())) throw conflict("Only an approved procurement order can ship stock");
|
||||
if (orders.addOutbound(order.getId(), customerId, request.quantity()) != 1) {
|
||||
throw conflict("Outbound quantity exceeds current procurement inventory");
|
||||
}
|
||||
OverseasProcurementOutbound outbound = new OverseasProcurementOutbound();
|
||||
outbound.setId(IdWorker.getId());
|
||||
outbound.setOrderId(order.getId());
|
||||
outbound.setCustomerId(customerId);
|
||||
outbound.setQuantity(request.quantity());
|
||||
outbound.setOutboundNo(request.outboundNo().trim());
|
||||
outbound.setCarrier(trim(request.carrier()));
|
||||
outbound.setTrackingNo(trim(request.trackingNo()));
|
||||
outbound.setOutboundAt(request.outboundAt() == null ? LocalDateTime.now() : request.outboundAt());
|
||||
outbound.setRemark(trim(request.remark()));
|
||||
outbound.setCreatedBy(operatorId);
|
||||
outbound.setCreatedAt(LocalDateTime.now());
|
||||
try {
|
||||
outbounds.insert(outbound);
|
||||
} catch (DuplicateKeyException exception) {
|
||||
throw conflict("Outbound number already exists for this procurement order");
|
||||
}
|
||||
history(order, "UPDATE", order.getStatus(), order.getStatus(), operatorId,
|
||||
changeDetail(before, after), null, now);
|
||||
return detail(customerId, orderNo);
|
||||
}
|
||||
|
||||
@@ -222,29 +216,24 @@ public class OverseasProcurementService {
|
||||
|
||||
private OverseasProcurementResponses.Summary summary(OverseasProcurementOrder order) {
|
||||
long inbound = value(order.getInboundQuantity());
|
||||
long outbound = value(order.getOutboundQuantity());
|
||||
return new OverseasProcurementResponses.Summary(order.getId(), order.getOrderNo(), order.getCustomerId(),
|
||||
order.getCustomerName(), order.getPurchaseCategory(), order.getProductName(),
|
||||
order.getProductSpecification(), value(order.getPurchaseQuantity()), order.getBudgetUnitPrice(),
|
||||
order.getCurrency(), order.getPurchaseWarehouseId(), order.getPurchaseWarehouseName(), inbound, outbound,
|
||||
inbound - outbound, order.getStatus(), value(order.getVersion()), order.getCreatedAt(), order.getUpdatedAt());
|
||||
order.getCurrency(), order.getPurchaseWarehouseId(), order.getPurchaseWarehouseName(), inbound, inbound,
|
||||
order.getStatus(), value(order.getVersion()), order.getCreatedAt(), order.getUpdatedAt());
|
||||
}
|
||||
|
||||
private OverseasProcurementResponses.Outbound outbound(OverseasProcurementOutbound value) {
|
||||
return new OverseasProcurementResponses.Outbound(value.getId(), value(value.getQuantity()), value.getOutboundNo(),
|
||||
value.getCarrier(), value.getTrackingNo(), value.getOutboundAt(), value.getRemark(), value.getCreatedBy(),
|
||||
value.getCreatedAt());
|
||||
}
|
||||
|
||||
private void history(OverseasProcurementOrder order, String from, String to, String operator,
|
||||
String remark, LocalDateTime now) {
|
||||
private void history(OverseasProcurementOrder order, String operationType, String from, String to,
|
||||
String operator, String changeDetail, String remark, LocalDateTime now) {
|
||||
OverseasProcurementStatusHistory history = new OverseasProcurementStatusHistory();
|
||||
history.setId(IdWorker.getId());
|
||||
history.setOrderId(order.getId());
|
||||
history.setCustomerId(order.getCustomerId());
|
||||
history.setOperationType(operationType);
|
||||
history.setFromStatus(from);
|
||||
history.setToStatus(to);
|
||||
history.setOperatorId(operator);
|
||||
history.setChangeDetail(changeDetail);
|
||||
history.setRemark(trim(remark));
|
||||
history.setCreatedAt(now);
|
||||
histories.insert(history);
|
||||
@@ -267,6 +256,91 @@ public class OverseasProcurementService {
|
||||
}
|
||||
}
|
||||
|
||||
private void patchWarehouse(OverseasProcurementOrder order, JsonNode warehouseId, JsonNode warehouseName) {
|
||||
if (warehouseId == null) {
|
||||
if (warehouseName != null) throw badRequest("purchaseWarehouseName cannot be changed without purchaseWarehouseId");
|
||||
return;
|
||||
}
|
||||
String id = nullableText(warehouseId, 128, "purchaseWarehouseId");
|
||||
if (id == null) {
|
||||
order.setPurchaseWarehouseId(null);
|
||||
order.setPurchaseWarehouseName(null);
|
||||
return;
|
||||
}
|
||||
long numericId;
|
||||
try {
|
||||
numericId = Long.parseLong(id);
|
||||
} catch (NumberFormatException exception) {
|
||||
throw badRequest("purchaseWarehouseId must be a numeric overseas partner warehouse ID");
|
||||
}
|
||||
var option = warehouseDirectory.required(numericId);
|
||||
order.setPurchaseWarehouseId(option.warehouseId());
|
||||
order.setPurchaseWarehouseName(option.warehouseName());
|
||||
}
|
||||
|
||||
private static void patchText(JsonNode node, int maxLength, String field,
|
||||
java.util.function.Consumer<String> setter) {
|
||||
if (node != null) setter.accept(nullableText(node, maxLength, field));
|
||||
}
|
||||
|
||||
private static String nullableText(JsonNode node, int maxLength, String field) {
|
||||
if (node.isNull()) return null;
|
||||
if (!node.isTextual()) throw badRequest(field + " must be a string or null");
|
||||
String value = trim(node.textValue());
|
||||
if (value != null && value.length() > maxLength) throw badRequest(field + " exceeds max length " + maxLength);
|
||||
return value;
|
||||
}
|
||||
|
||||
private static void patchDateTime(JsonNode node, java.util.function.Consumer<LocalDateTime> setter) {
|
||||
if (node == null) return;
|
||||
String value = nullableText(node, 64, "estimatedArrivalAt");
|
||||
if (value == null) {
|
||||
setter.accept(null);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setter.accept(LocalDateTime.parse(value));
|
||||
} catch (DateTimeParseException exception) {
|
||||
throw badRequest("estimatedArrivalAt must be an ISO local date-time or null");
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<String, Object> editableSnapshot(OverseasProcurementOrder order) {
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("purchaseCategory", order.getPurchaseCategory());
|
||||
result.put("productName", order.getProductName());
|
||||
result.put("productSpecification", order.getProductSpecification());
|
||||
result.put("productUrl", order.getProductUrl());
|
||||
result.put("inboundQuantity", value(order.getInboundQuantity()));
|
||||
result.put("purchaseWarehouseId", order.getPurchaseWarehouseId());
|
||||
result.put("purchaseWarehouseName", order.getPurchaseWarehouseName());
|
||||
result.put("domesticCarrier", order.getDomesticCarrier());
|
||||
result.put("domesticTrackingNo", order.getDomesticTrackingNo());
|
||||
result.put("overseasCarrier", order.getOverseasCarrier());
|
||||
result.put("overseasTrackingNo", order.getOverseasTrackingNo());
|
||||
result.put("estimatedArrivalAt", order.getEstimatedArrivalAt());
|
||||
result.put("remark", order.getRemark());
|
||||
return result;
|
||||
}
|
||||
|
||||
private String changeDetail(Map<String, Object> before, Map<String, Object> after) {
|
||||
Map<String, Object> changes = new LinkedHashMap<>();
|
||||
before.forEach((field, oldValue) -> {
|
||||
Object newValue = after.get(field);
|
||||
if (!Objects.equals(oldValue, newValue)) {
|
||||
Map<String, Object> pair = new LinkedHashMap<>();
|
||||
pair.put("before", oldValue);
|
||||
pair.put("after", newValue);
|
||||
changes.put(field, pair);
|
||||
}
|
||||
});
|
||||
try {
|
||||
return objectMapper.writeValueAsString(changes);
|
||||
} catch (JsonProcessingException exception) {
|
||||
throw new IllegalStateException("Cannot serialize procurement change detail", exception);
|
||||
}
|
||||
}
|
||||
|
||||
private static void touch(OverseasProcurementOrder order, LocalDateTime now) {
|
||||
order.setVersion(value(order.getVersion()) + 1);
|
||||
order.setUpdatedAt(now);
|
||||
@@ -278,9 +352,6 @@ public class OverseasProcurementService {
|
||||
private static void setIfText(String value, java.util.function.Consumer<String> setter) {
|
||||
if (StringUtils.hasText(value)) setter.accept(value.trim());
|
||||
}
|
||||
private static void setIfPresent(String value, java.util.function.Consumer<String> setter) {
|
||||
if (value != null) setter.accept(trim(value));
|
||||
}
|
||||
private static ResponseStatusException badRequest(String message) {
|
||||
return new ResponseStatusException(HttpStatus.BAD_REQUEST, message);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user