feat: add product catalog and goods orders
This commit is contained in:
@@ -0,0 +1,61 @@
|
|||||||
|
package com.linhelp.merchant;
|
||||||
|
|
||||||
|
import com.linhelp.common.api.ApiResponse;
|
||||||
|
import com.linhelp.common.security.AuthService;
|
||||||
|
import com.linhelp.common.security.CurrentUser;
|
||||||
|
import com.linhelp.common.security.RequireRole;
|
||||||
|
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.RestController;
|
||||||
|
|
||||||
|
import javax.validation.Valid;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/admin/merchants")
|
||||||
|
@RequireRole({"PLATFORM_ADMIN", "COMMUNITY_ADMIN"})
|
||||||
|
public class MerchantController {
|
||||||
|
private final MerchantService merchantService;
|
||||||
|
private final AuthService authService;
|
||||||
|
|
||||||
|
public MerchantController(MerchantService merchantService, AuthService authService) {
|
||||||
|
this.merchantService = merchantService;
|
||||||
|
this.authService = authService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public ApiResponse<List<MerchantResponse>> list() {
|
||||||
|
CurrentUser user = authService.currentUser();
|
||||||
|
return ApiResponse.ok(merchantService.list(user.getCommunityId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public ApiResponse<MerchantResponse> create(@Valid @RequestBody MerchantRequest request) {
|
||||||
|
CurrentUser user = authService.currentUser();
|
||||||
|
request.setTenantId(user.getTenantId());
|
||||||
|
request.setCommunityId(user.getCommunityId());
|
||||||
|
return ApiResponse.ok(merchantService.create(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
public ApiResponse<MerchantResponse> update(@PathVariable Long id, @Valid @RequestBody MerchantRequest request) {
|
||||||
|
CurrentUser user = authService.currentUser();
|
||||||
|
request.setTenantId(user.getTenantId());
|
||||||
|
request.setCommunityId(user.getCommunityId());
|
||||||
|
return ApiResponse.ok(merchantService.update(id, request));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}/enable")
|
||||||
|
public ApiResponse<MerchantResponse> enable(@PathVariable Long id) {
|
||||||
|
return ApiResponse.ok(merchantService.enable(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}/disable")
|
||||||
|
public ApiResponse<MerchantResponse> disable(@PathVariable Long id) {
|
||||||
|
return ApiResponse.ok(merchantService.disable(id));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package com.linhelp.merchant;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotBlank;
|
||||||
|
|
||||||
|
public class MerchantRequest {
|
||||||
|
private Long tenantId;
|
||||||
|
private Long communityId;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
private String merchantType;
|
||||||
|
|
||||||
|
private String contactName;
|
||||||
|
private String contactPhone;
|
||||||
|
|
||||||
|
public Long getTenantId() {
|
||||||
|
return tenantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTenantId(Long tenantId) {
|
||||||
|
this.tenantId = tenantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getCommunityId() {
|
||||||
|
return communityId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCommunityId(Long communityId) {
|
||||||
|
this.communityId = communityId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMerchantType() {
|
||||||
|
return merchantType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMerchantType(String merchantType) {
|
||||||
|
this.merchantType = merchantType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getContactName() {
|
||||||
|
return contactName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContactName(String contactName) {
|
||||||
|
this.contactName = contactName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getContactPhone() {
|
||||||
|
return contactPhone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContactPhone(String contactPhone) {
|
||||||
|
this.contactPhone = contactPhone;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package com.linhelp.merchant;
|
||||||
|
|
||||||
|
public class MerchantResponse {
|
||||||
|
private Long id;
|
||||||
|
private Long tenantId;
|
||||||
|
private Long communityId;
|
||||||
|
private String name;
|
||||||
|
private String merchantType;
|
||||||
|
private String contactName;
|
||||||
|
private String contactPhone;
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getTenantId() {
|
||||||
|
return tenantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTenantId(Long tenantId) {
|
||||||
|
this.tenantId = tenantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getCommunityId() {
|
||||||
|
return communityId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCommunityId(Long communityId) {
|
||||||
|
this.communityId = communityId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMerchantType() {
|
||||||
|
return merchantType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMerchantType(String merchantType) {
|
||||||
|
this.merchantType = merchantType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getContactName() {
|
||||||
|
return contactName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContactName(String contactName) {
|
||||||
|
this.contactName = contactName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getContactPhone() {
|
||||||
|
return contactPhone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContactPhone(String contactPhone) {
|
||||||
|
this.contactPhone = contactPhone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(String status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package com.linhelp.merchant;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class MerchantService {
|
||||||
|
private final AtomicLong ids = new AtomicLong(1L);
|
||||||
|
private final Map<Long, MerchantResponse> merchants = new LinkedHashMap<Long, MerchantResponse>();
|
||||||
|
|
||||||
|
public synchronized MerchantResponse create(MerchantRequest request) {
|
||||||
|
MerchantResponse merchant = new MerchantResponse();
|
||||||
|
merchant.setId(ids.getAndIncrement());
|
||||||
|
copy(request, merchant);
|
||||||
|
merchant.setStatus("ENABLED");
|
||||||
|
merchants.put(merchant.getId(), merchant);
|
||||||
|
return merchant;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized List<MerchantResponse> list(Long communityId) {
|
||||||
|
List<MerchantResponse> result = new ArrayList<MerchantResponse>();
|
||||||
|
for (MerchantResponse merchant : merchants.values()) {
|
||||||
|
if (communityId.equals(merchant.getCommunityId())) {
|
||||||
|
result.add(merchant);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized MerchantResponse update(Long id, MerchantRequest request) {
|
||||||
|
MerchantResponse merchant = merchants.get(id);
|
||||||
|
if (merchant == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
copy(request, merchant);
|
||||||
|
return merchant;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized MerchantResponse enable(Long id) {
|
||||||
|
MerchantResponse merchant = merchants.get(id);
|
||||||
|
if (merchant != null) {
|
||||||
|
merchant.setStatus("ENABLED");
|
||||||
|
}
|
||||||
|
return merchant;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized MerchantResponse disable(Long id) {
|
||||||
|
MerchantResponse merchant = merchants.get(id);
|
||||||
|
if (merchant != null) {
|
||||||
|
merchant.setStatus("DISABLED");
|
||||||
|
}
|
||||||
|
return merchant;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copy(MerchantRequest request, MerchantResponse merchant) {
|
||||||
|
merchant.setTenantId(request.getTenantId());
|
||||||
|
merchant.setCommunityId(request.getCommunityId());
|
||||||
|
merchant.setName(request.getName());
|
||||||
|
merchant.setMerchantType(request.getMerchantType());
|
||||||
|
merchant.setContactName(request.getContactName());
|
||||||
|
merchant.setContactPhone(request.getContactPhone());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package com.linhelp.order;
|
||||||
|
|
||||||
|
import javax.validation.constraints.Min;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
|
||||||
|
public class CreateGoodsOrderItemRequest {
|
||||||
|
@NotNull
|
||||||
|
private Long productId;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private Long skuId;
|
||||||
|
|
||||||
|
@Min(1)
|
||||||
|
private int quantity;
|
||||||
|
|
||||||
|
public CreateGoodsOrderItemRequest() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public CreateGoodsOrderItemRequest(Long productId, Long skuId, int quantity) {
|
||||||
|
this.productId = productId;
|
||||||
|
this.skuId = skuId;
|
||||||
|
this.quantity = quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getProductId() {
|
||||||
|
return productId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProductId(Long productId) {
|
||||||
|
this.productId = productId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getSkuId() {
|
||||||
|
return skuId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSkuId(Long skuId) {
|
||||||
|
this.skuId = skuId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getQuantity() {
|
||||||
|
return quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setQuantity(int quantity) {
|
||||||
|
this.quantity = quantity;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
package com.linhelp.order;
|
||||||
|
|
||||||
|
import com.linhelp.common.enums.DeliveryMethod;
|
||||||
|
|
||||||
|
import javax.validation.Valid;
|
||||||
|
import javax.validation.constraints.NotEmpty;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class CreateGoodsOrderRequest {
|
||||||
|
private Long tenantId;
|
||||||
|
private Long communityId;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private Long merchantId;
|
||||||
|
|
||||||
|
private Long addressId;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private DeliveryMethod deliveryMethod = DeliveryMethod.IMMEDIATE;
|
||||||
|
|
||||||
|
private LocalDateTime scheduledTime;
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
@Valid
|
||||||
|
@NotEmpty
|
||||||
|
private List<CreateGoodsOrderItemRequest> items = new ArrayList<CreateGoodsOrderItemRequest>();
|
||||||
|
|
||||||
|
public Long getTenantId() {
|
||||||
|
return tenantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTenantId(Long tenantId) {
|
||||||
|
this.tenantId = tenantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getCommunityId() {
|
||||||
|
return communityId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCommunityId(Long communityId) {
|
||||||
|
this.communityId = communityId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getMerchantId() {
|
||||||
|
return merchantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMerchantId(Long merchantId) {
|
||||||
|
this.merchantId = merchantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getAddressId() {
|
||||||
|
return addressId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAddressId(Long addressId) {
|
||||||
|
this.addressId = addressId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DeliveryMethod getDeliveryMethod() {
|
||||||
|
return deliveryMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeliveryMethod(DeliveryMethod deliveryMethod) {
|
||||||
|
this.deliveryMethod = deliveryMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getScheduledTime() {
|
||||||
|
return scheduledTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScheduledTime(LocalDateTime scheduledTime) {
|
||||||
|
this.scheduledTime = scheduledTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRemark() {
|
||||||
|
return remark;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRemark(String remark) {
|
||||||
|
this.remark = remark;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<CreateGoodsOrderItemRequest> getItems() {
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setItems(List<CreateGoodsOrderItemRequest> items) {
|
||||||
|
this.items = items;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package com.linhelp.order;
|
||||||
|
|
||||||
|
import com.linhelp.common.api.ApiResponse;
|
||||||
|
import com.linhelp.common.enums.GoodsOrderStatus;
|
||||||
|
import com.linhelp.common.security.AuthService;
|
||||||
|
import com.linhelp.common.security.CurrentUser;
|
||||||
|
import com.linhelp.common.security.RequireRole;
|
||||||
|
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.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import javax.validation.Valid;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class GoodsOrderController {
|
||||||
|
private final GoodsOrderService goodsOrderService;
|
||||||
|
private final AuthService authService;
|
||||||
|
|
||||||
|
public GoodsOrderController(GoodsOrderService goodsOrderService, AuthService authService) {
|
||||||
|
this.goodsOrderService = goodsOrderService;
|
||||||
|
this.authService = authService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/api/mini/goods-orders")
|
||||||
|
public ApiResponse<GoodsOrderResponse> create(@Valid @RequestBody CreateGoodsOrderRequest request) {
|
||||||
|
CurrentUser user = authService.currentUser();
|
||||||
|
request.setTenantId(user.getTenantId());
|
||||||
|
request.setCommunityId(user.getCommunityId());
|
||||||
|
return ApiResponse.ok(goodsOrderService.create(user.getUserId(), request));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/api/mini/goods-orders")
|
||||||
|
public ApiResponse<List<GoodsOrderResponse>> listMine() {
|
||||||
|
CurrentUser user = authService.currentUser();
|
||||||
|
return ApiResponse.ok(goodsOrderService.listMine(user.getUserId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/api/mini/goods-orders/{id}")
|
||||||
|
public ApiResponse<GoodsOrderResponse> miniDetail(@PathVariable Long id) {
|
||||||
|
return ApiResponse.ok(goodsOrderService.detail(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/api/mini/goods-orders/{id}/cancel")
|
||||||
|
public ApiResponse<GoodsOrderResponse> cancel(@PathVariable Long id) {
|
||||||
|
return ApiResponse.ok(goodsOrderService.cancel(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/api/admin/goods-orders")
|
||||||
|
@RequireRole({"PLATFORM_ADMIN", "COMMUNITY_ADMIN", "MERCHANT"})
|
||||||
|
public ApiResponse<List<GoodsOrderResponse>> listAdmin(@RequestParam(required = false) GoodsOrderStatus status) {
|
||||||
|
CurrentUser user = authService.currentUser();
|
||||||
|
return ApiResponse.ok(goodsOrderService.listAdmin(user.getCommunityId(), status));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/api/admin/goods-orders/{id}")
|
||||||
|
@RequireRole({"PLATFORM_ADMIN", "COMMUNITY_ADMIN", "MERCHANT"})
|
||||||
|
public ApiResponse<GoodsOrderResponse> adminDetail(@PathVariable Long id) {
|
||||||
|
return ApiResponse.ok(goodsOrderService.detail(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/api/admin/goods-orders/{id}/confirm")
|
||||||
|
@RequireRole({"PLATFORM_ADMIN", "COMMUNITY_ADMIN", "MERCHANT"})
|
||||||
|
public ApiResponse<GoodsOrderResponse> confirm(@PathVariable Long id) {
|
||||||
|
return ApiResponse.ok(goodsOrderService.confirm(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/api/admin/goods-orders/{id}/prepared")
|
||||||
|
@RequireRole({"PLATFORM_ADMIN", "COMMUNITY_ADMIN", "MERCHANT"})
|
||||||
|
public ApiResponse<GoodsOrderResponse> markPrepared(@PathVariable Long id) {
|
||||||
|
return ApiResponse.ok(goodsOrderService.markPrepared(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/api/admin/goods-orders/{id}/delivering")
|
||||||
|
@RequireRole({"PLATFORM_ADMIN", "COMMUNITY_ADMIN", "MERCHANT"})
|
||||||
|
public ApiResponse<GoodsOrderResponse> markDelivering(@PathVariable Long id) {
|
||||||
|
return ApiResponse.ok(goodsOrderService.markDelivering(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/api/admin/goods-orders/{id}/complete")
|
||||||
|
@RequireRole({"PLATFORM_ADMIN", "COMMUNITY_ADMIN", "MERCHANT"})
|
||||||
|
public ApiResponse<GoodsOrderResponse> complete(@PathVariable Long id) {
|
||||||
|
return ApiResponse.ok(goodsOrderService.complete(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/api/admin/goods-orders/{id}/paid")
|
||||||
|
@RequireRole({"PLATFORM_ADMIN", "COMMUNITY_ADMIN", "MERCHANT"})
|
||||||
|
public ApiResponse<GoodsOrderResponse> markPaid(@PathVariable Long id) {
|
||||||
|
return ApiResponse.ok(goodsOrderService.markPaid(id));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package com.linhelp.order;
|
||||||
|
|
||||||
|
public class GoodsOrderItemResponse {
|
||||||
|
private Long id;
|
||||||
|
private Long orderId;
|
||||||
|
private Long productId;
|
||||||
|
private Long skuId;
|
||||||
|
private String productName;
|
||||||
|
private String skuName;
|
||||||
|
private int quantity;
|
||||||
|
private int priceCent;
|
||||||
|
private int amountCent;
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getOrderId() {
|
||||||
|
return orderId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOrderId(Long orderId) {
|
||||||
|
this.orderId = orderId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getProductId() {
|
||||||
|
return productId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProductId(Long productId) {
|
||||||
|
this.productId = productId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getSkuId() {
|
||||||
|
return skuId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSkuId(Long skuId) {
|
||||||
|
this.skuId = skuId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProductName() {
|
||||||
|
return productName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProductName(String productName) {
|
||||||
|
this.productName = productName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSkuName() {
|
||||||
|
return skuName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSkuName(String skuName) {
|
||||||
|
this.skuName = skuName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getQuantity() {
|
||||||
|
return quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setQuantity(int quantity) {
|
||||||
|
this.quantity = quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPriceCent() {
|
||||||
|
return priceCent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPriceCent(int priceCent) {
|
||||||
|
this.priceCent = priceCent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAmountCent() {
|
||||||
|
return amountCent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAmountCent(int amountCent) {
|
||||||
|
this.amountCent = amountCent;
|
||||||
|
}
|
||||||
|
}
|
||||||
174
backend/src/main/java/com/linhelp/order/GoodsOrderResponse.java
Normal file
174
backend/src/main/java/com/linhelp/order/GoodsOrderResponse.java
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
package com.linhelp.order;
|
||||||
|
|
||||||
|
import com.linhelp.common.enums.DeliveryMethod;
|
||||||
|
import com.linhelp.common.enums.GoodsOrderStatus;
|
||||||
|
import com.linhelp.common.enums.OfflinePayStatus;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class GoodsOrderResponse {
|
||||||
|
private Long id;
|
||||||
|
private String orderNo;
|
||||||
|
private Long tenantId;
|
||||||
|
private Long communityId;
|
||||||
|
private Long userId;
|
||||||
|
private Long merchantId;
|
||||||
|
private Long addressId;
|
||||||
|
private DeliveryMethod deliveryMethod;
|
||||||
|
private GoodsOrderStatus status;
|
||||||
|
private OfflinePayStatus offlinePayStatus;
|
||||||
|
private LocalDateTime scheduledTime;
|
||||||
|
private String remark;
|
||||||
|
private int totalAmountCent;
|
||||||
|
private int deliveryFeeCent;
|
||||||
|
private int payableAmountCent;
|
||||||
|
private List<GoodsOrderItemResponse> items = new ArrayList<GoodsOrderItemResponse>();
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOrderNo() {
|
||||||
|
return orderNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOrderNo(String orderNo) {
|
||||||
|
this.orderNo = orderNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getTenantId() {
|
||||||
|
return tenantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTenantId(Long tenantId) {
|
||||||
|
this.tenantId = tenantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getCommunityId() {
|
||||||
|
return communityId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCommunityId(Long communityId) {
|
||||||
|
this.communityId = communityId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getUserId() {
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserId(Long userId) {
|
||||||
|
this.userId = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getMerchantId() {
|
||||||
|
return merchantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMerchantId(Long merchantId) {
|
||||||
|
this.merchantId = merchantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getAddressId() {
|
||||||
|
return addressId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAddressId(Long addressId) {
|
||||||
|
this.addressId = addressId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DeliveryMethod getDeliveryMethod() {
|
||||||
|
return deliveryMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeliveryMethod(DeliveryMethod deliveryMethod) {
|
||||||
|
this.deliveryMethod = deliveryMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GoodsOrderStatus getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(GoodsOrderStatus status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OfflinePayStatus getOfflinePayStatus() {
|
||||||
|
return offlinePayStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOfflinePayStatus(OfflinePayStatus offlinePayStatus) {
|
||||||
|
this.offlinePayStatus = offlinePayStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getScheduledTime() {
|
||||||
|
return scheduledTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScheduledTime(LocalDateTime scheduledTime) {
|
||||||
|
this.scheduledTime = scheduledTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRemark() {
|
||||||
|
return remark;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRemark(String remark) {
|
||||||
|
this.remark = remark;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTotalAmountCent() {
|
||||||
|
return totalAmountCent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTotalAmountCent(int totalAmountCent) {
|
||||||
|
this.totalAmountCent = totalAmountCent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDeliveryFeeCent() {
|
||||||
|
return deliveryFeeCent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeliveryFeeCent(int deliveryFeeCent) {
|
||||||
|
this.deliveryFeeCent = deliveryFeeCent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPayableAmountCent() {
|
||||||
|
return payableAmountCent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPayableAmountCent(int payableAmountCent) {
|
||||||
|
this.payableAmountCent = payableAmountCent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<GoodsOrderItemResponse> getItems() {
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setItems(List<GoodsOrderItemResponse> items) {
|
||||||
|
this.items = items;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getCreatedAt() {
|
||||||
|
return createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreatedAt(LocalDateTime createdAt) {
|
||||||
|
this.createdAt = createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getUpdatedAt() {
|
||||||
|
return updatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUpdatedAt(LocalDateTime updatedAt) {
|
||||||
|
this.updatedAt = updatedAt;
|
||||||
|
}
|
||||||
|
}
|
||||||
257
backend/src/main/java/com/linhelp/order/GoodsOrderService.java
Normal file
257
backend/src/main/java/com/linhelp/order/GoodsOrderService.java
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
package com.linhelp.order;
|
||||||
|
|
||||||
|
import com.linhelp.common.enums.DeliveryMethod;
|
||||||
|
import com.linhelp.common.enums.GoodsOrderStatus;
|
||||||
|
import com.linhelp.common.enums.OfflinePayStatus;
|
||||||
|
import com.linhelp.common.exception.BizException;
|
||||||
|
import com.linhelp.product.ProductService;
|
||||||
|
import com.linhelp.product.ProductSkuResponse;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class GoodsOrderService {
|
||||||
|
private final ProductService productService;
|
||||||
|
private final AtomicLong orderIds = new AtomicLong(1L);
|
||||||
|
private final AtomicLong itemIds = new AtomicLong(1L);
|
||||||
|
private final Map<Long, GoodsOrderResponse> orders = new LinkedHashMap<Long, GoodsOrderResponse>();
|
||||||
|
|
||||||
|
public GoodsOrderService(ProductService productService) {
|
||||||
|
this.productService = productService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized GoodsOrderResponse create(Long userId, CreateGoodsOrderRequest request) {
|
||||||
|
if (request.getItems() == null || request.getItems().isEmpty()) {
|
||||||
|
throw new BizException("订单商品不能为空");
|
||||||
|
}
|
||||||
|
if (request.getDeliveryMethod() != DeliveryMethod.SELF_PICKUP && request.getAddressId() == null) {
|
||||||
|
throw new BizException("配送地址不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<Long, Integer> skuQuantities = collectSkuQuantities(request);
|
||||||
|
Map<Long, ProductSkuResponse> skuSnapshots = loadAndCheckStock(skuQuantities);
|
||||||
|
for (Map.Entry<Long, Integer> entry : skuQuantities.entrySet()) {
|
||||||
|
productService.deductStock(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
Long orderId = orderIds.getAndIncrement();
|
||||||
|
GoodsOrderResponse order = new GoodsOrderResponse();
|
||||||
|
order.setId(orderId);
|
||||||
|
order.setOrderNo("GO" + String.format("%08d", orderId));
|
||||||
|
order.setTenantId(request.getTenantId());
|
||||||
|
order.setCommunityId(request.getCommunityId());
|
||||||
|
order.setUserId(userId);
|
||||||
|
order.setMerchantId(request.getMerchantId());
|
||||||
|
order.setAddressId(request.getAddressId());
|
||||||
|
order.setDeliveryMethod(request.getDeliveryMethod());
|
||||||
|
order.setStatus(GoodsOrderStatus.PENDING_CONFIRM);
|
||||||
|
order.setOfflinePayStatus(OfflinePayStatus.UNPAID);
|
||||||
|
order.setScheduledTime(request.getScheduledTime());
|
||||||
|
order.setRemark(request.getRemark());
|
||||||
|
order.setItems(buildItems(orderId, request, skuSnapshots));
|
||||||
|
order.setTotalAmountCent(sumAmount(order.getItems()));
|
||||||
|
order.setDeliveryFeeCent(resolveDeliveryFee(request.getDeliveryMethod()));
|
||||||
|
order.setPayableAmountCent(order.getTotalAmountCent() + order.getDeliveryFeeCent());
|
||||||
|
order.setCreatedAt(LocalDateTime.now());
|
||||||
|
order.setUpdatedAt(order.getCreatedAt());
|
||||||
|
orders.put(order.getId(), order);
|
||||||
|
return copyOrder(order);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized GoodsOrderResponse detail(Long id) {
|
||||||
|
return copyOrder(requireOrder(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized List<GoodsOrderResponse> listMine(Long userId) {
|
||||||
|
List<GoodsOrderResponse> result = new ArrayList<GoodsOrderResponse>();
|
||||||
|
for (GoodsOrderResponse order : orders.values()) {
|
||||||
|
if (userId.equals(order.getUserId())) {
|
||||||
|
result.add(copyOrder(order));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized List<GoodsOrderResponse> listAdmin(Long communityId, GoodsOrderStatus status) {
|
||||||
|
List<GoodsOrderResponse> result = new ArrayList<GoodsOrderResponse>();
|
||||||
|
for (GoodsOrderResponse order : orders.values()) {
|
||||||
|
if (!communityId.equals(order.getCommunityId())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (status != null && status != order.getStatus()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
result.add(copyOrder(order));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized GoodsOrderResponse confirm(Long id) {
|
||||||
|
GoodsOrderResponse order = requireOrder(id);
|
||||||
|
move(order, GoodsOrderStatus.PREPARING);
|
||||||
|
return copyOrder(order);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized GoodsOrderResponse markPrepared(Long id) {
|
||||||
|
GoodsOrderResponse order = requireOrder(id);
|
||||||
|
GoodsOrderStatus target = DeliveryMethod.SELF_PICKUP.equals(order.getDeliveryMethod())
|
||||||
|
? GoodsOrderStatus.PENDING_PICKUP
|
||||||
|
: GoodsOrderStatus.PENDING_DELIVERY;
|
||||||
|
move(order, target);
|
||||||
|
return copyOrder(order);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized GoodsOrderResponse markDelivering(Long id) {
|
||||||
|
GoodsOrderResponse order = requireOrder(id);
|
||||||
|
move(order, GoodsOrderStatus.DELIVERING);
|
||||||
|
return copyOrder(order);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized GoodsOrderResponse complete(Long id) {
|
||||||
|
GoodsOrderResponse order = requireOrder(id);
|
||||||
|
move(order, GoodsOrderStatus.COMPLETED);
|
||||||
|
return copyOrder(order);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized GoodsOrderResponse cancel(Long id) {
|
||||||
|
GoodsOrderResponse order = requireOrder(id);
|
||||||
|
move(order, GoodsOrderStatus.CANCELED);
|
||||||
|
return copyOrder(order);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized GoodsOrderResponse markPaid(Long id) {
|
||||||
|
GoodsOrderResponse order = requireOrder(id);
|
||||||
|
order.setOfflinePayStatus(OfflinePayStatus.PAID);
|
||||||
|
order.setUpdatedAt(LocalDateTime.now());
|
||||||
|
return copyOrder(order);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<Long, Integer> collectSkuQuantities(CreateGoodsOrderRequest request) {
|
||||||
|
Map<Long, Integer> skuQuantities = new LinkedHashMap<Long, Integer>();
|
||||||
|
for (CreateGoodsOrderItemRequest item : request.getItems()) {
|
||||||
|
if (item.getSkuId() == null || item.getProductId() == null) {
|
||||||
|
throw new BizException("订单商品信息不完整");
|
||||||
|
}
|
||||||
|
if (item.getQuantity() <= 0) {
|
||||||
|
throw new BizException("购买数量必须大于0");
|
||||||
|
}
|
||||||
|
Integer existing = skuQuantities.get(item.getSkuId());
|
||||||
|
skuQuantities.put(item.getSkuId(), (existing == null ? 0 : existing) + item.getQuantity());
|
||||||
|
}
|
||||||
|
return skuQuantities;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<Long, ProductSkuResponse> loadAndCheckStock(Map<Long, Integer> skuQuantities) {
|
||||||
|
Map<Long, ProductSkuResponse> snapshots = new LinkedHashMap<Long, ProductSkuResponse>();
|
||||||
|
for (Map.Entry<Long, Integer> entry : skuQuantities.entrySet()) {
|
||||||
|
ProductSkuResponse sku = productService.getSku(entry.getKey());
|
||||||
|
if (!sku.isEnabled()) {
|
||||||
|
throw new BizException("商品规格已下架");
|
||||||
|
}
|
||||||
|
if (sku.getStock() < entry.getValue()) {
|
||||||
|
throw new BizException("库存不足");
|
||||||
|
}
|
||||||
|
snapshots.put(entry.getKey(), sku);
|
||||||
|
}
|
||||||
|
return snapshots;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<GoodsOrderItemResponse> buildItems(Long orderId,
|
||||||
|
CreateGoodsOrderRequest request,
|
||||||
|
Map<Long, ProductSkuResponse> skuSnapshots) {
|
||||||
|
List<GoodsOrderItemResponse> items = new ArrayList<GoodsOrderItemResponse>();
|
||||||
|
for (CreateGoodsOrderItemRequest itemRequest : request.getItems()) {
|
||||||
|
ProductSkuResponse sku = skuSnapshots.get(itemRequest.getSkuId());
|
||||||
|
GoodsOrderItemResponse item = new GoodsOrderItemResponse();
|
||||||
|
item.setId(itemIds.getAndIncrement());
|
||||||
|
item.setOrderId(orderId);
|
||||||
|
item.setProductId(itemRequest.getProductId());
|
||||||
|
item.setSkuId(itemRequest.getSkuId());
|
||||||
|
item.setProductName(sku.getProductName());
|
||||||
|
item.setSkuName(sku.getSkuName());
|
||||||
|
item.setQuantity(itemRequest.getQuantity());
|
||||||
|
item.setPriceCent(sku.getPriceCent());
|
||||||
|
item.setAmountCent(sku.getPriceCent() * itemRequest.getQuantity());
|
||||||
|
items.add(item);
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int sumAmount(List<GoodsOrderItemResponse> items) {
|
||||||
|
int sum = 0;
|
||||||
|
for (GoodsOrderItemResponse item : items) {
|
||||||
|
sum += item.getAmountCent();
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int resolveDeliveryFee(DeliveryMethod method) {
|
||||||
|
if (DeliveryMethod.SELF_PICKUP.equals(method)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void move(GoodsOrderResponse order, GoodsOrderStatus target) {
|
||||||
|
if (!order.getStatus().canMoveTo(target)) {
|
||||||
|
throw new BizException("订单状态不允许操作");
|
||||||
|
}
|
||||||
|
order.setStatus(target);
|
||||||
|
order.setUpdatedAt(LocalDateTime.now());
|
||||||
|
}
|
||||||
|
|
||||||
|
private GoodsOrderResponse requireOrder(Long id) {
|
||||||
|
GoodsOrderResponse order = orders.get(id);
|
||||||
|
if (order == null) {
|
||||||
|
throw new BizException(404, "订单不存在");
|
||||||
|
}
|
||||||
|
return order;
|
||||||
|
}
|
||||||
|
|
||||||
|
private GoodsOrderResponse copyOrder(GoodsOrderResponse source) {
|
||||||
|
GoodsOrderResponse target = new GoodsOrderResponse();
|
||||||
|
target.setId(source.getId());
|
||||||
|
target.setOrderNo(source.getOrderNo());
|
||||||
|
target.setTenantId(source.getTenantId());
|
||||||
|
target.setCommunityId(source.getCommunityId());
|
||||||
|
target.setUserId(source.getUserId());
|
||||||
|
target.setMerchantId(source.getMerchantId());
|
||||||
|
target.setAddressId(source.getAddressId());
|
||||||
|
target.setDeliveryMethod(source.getDeliveryMethod());
|
||||||
|
target.setStatus(source.getStatus());
|
||||||
|
target.setOfflinePayStatus(source.getOfflinePayStatus());
|
||||||
|
target.setScheduledTime(source.getScheduledTime());
|
||||||
|
target.setRemark(source.getRemark());
|
||||||
|
target.setTotalAmountCent(source.getTotalAmountCent());
|
||||||
|
target.setDeliveryFeeCent(source.getDeliveryFeeCent());
|
||||||
|
target.setPayableAmountCent(source.getPayableAmountCent());
|
||||||
|
target.setCreatedAt(source.getCreatedAt());
|
||||||
|
target.setUpdatedAt(source.getUpdatedAt());
|
||||||
|
target.setItems(copyItems(source.getItems()));
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<GoodsOrderItemResponse> copyItems(List<GoodsOrderItemResponse> sourceItems) {
|
||||||
|
List<GoodsOrderItemResponse> items = new ArrayList<GoodsOrderItemResponse>();
|
||||||
|
for (GoodsOrderItemResponse source : sourceItems) {
|
||||||
|
GoodsOrderItemResponse target = new GoodsOrderItemResponse();
|
||||||
|
target.setId(source.getId());
|
||||||
|
target.setOrderId(source.getOrderId());
|
||||||
|
target.setProductId(source.getProductId());
|
||||||
|
target.setSkuId(source.getSkuId());
|
||||||
|
target.setProductName(source.getProductName());
|
||||||
|
target.setSkuName(source.getSkuName());
|
||||||
|
target.setQuantity(source.getQuantity());
|
||||||
|
target.setPriceCent(source.getPriceCent());
|
||||||
|
target.setAmountCent(source.getAmountCent());
|
||||||
|
items.add(target);
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package com.linhelp.product;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotBlank;
|
||||||
|
|
||||||
|
public class ProductCategoryRequest {
|
||||||
|
private Long tenantId;
|
||||||
|
private Long communityId;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
private int sortNo;
|
||||||
|
private boolean enabled = true;
|
||||||
|
|
||||||
|
public Long getTenantId() {
|
||||||
|
return tenantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTenantId(Long tenantId) {
|
||||||
|
this.tenantId = tenantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getCommunityId() {
|
||||||
|
return communityId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCommunityId(Long communityId) {
|
||||||
|
this.communityId = communityId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSortNo() {
|
||||||
|
return sortNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSortNo(int sortNo) {
|
||||||
|
this.sortNo = sortNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnabled(boolean enabled) {
|
||||||
|
this.enabled = enabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package com.linhelp.product;
|
||||||
|
|
||||||
|
public class ProductCategoryResponse {
|
||||||
|
private Long id;
|
||||||
|
private Long tenantId;
|
||||||
|
private Long communityId;
|
||||||
|
private String name;
|
||||||
|
private int sortNo;
|
||||||
|
private boolean enabled;
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getTenantId() {
|
||||||
|
return tenantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTenantId(Long tenantId) {
|
||||||
|
this.tenantId = tenantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getCommunityId() {
|
||||||
|
return communityId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCommunityId(Long communityId) {
|
||||||
|
this.communityId = communityId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSortNo() {
|
||||||
|
return sortNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSortNo(int sortNo) {
|
||||||
|
this.sortNo = sortNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnabled(boolean enabled) {
|
||||||
|
this.enabled = enabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
151
backend/src/main/java/com/linhelp/product/ProductController.java
Normal file
151
backend/src/main/java/com/linhelp/product/ProductController.java
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
package com.linhelp.product;
|
||||||
|
|
||||||
|
import com.linhelp.common.api.ApiResponse;
|
||||||
|
import com.linhelp.common.security.AuthService;
|
||||||
|
import com.linhelp.common.security.CurrentUser;
|
||||||
|
import com.linhelp.common.security.RequireRole;
|
||||||
|
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.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import javax.validation.Valid;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class ProductController {
|
||||||
|
private final ProductService productService;
|
||||||
|
private final AuthService authService;
|
||||||
|
|
||||||
|
public ProductController(ProductService productService, AuthService authService) {
|
||||||
|
this.productService = productService;
|
||||||
|
this.authService = authService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/api/admin/product-categories")
|
||||||
|
@RequireRole({"PLATFORM_ADMIN", "COMMUNITY_ADMIN", "MERCHANT"})
|
||||||
|
public ApiResponse<List<ProductCategoryResponse>> listAdminCategories() {
|
||||||
|
CurrentUser user = authService.currentUser();
|
||||||
|
return ApiResponse.ok(productService.listCategories(user.getCommunityId(), false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/api/admin/product-categories")
|
||||||
|
@RequireRole({"PLATFORM_ADMIN", "COMMUNITY_ADMIN", "MERCHANT"})
|
||||||
|
public ApiResponse<ProductCategoryResponse> createCategory(@Valid @RequestBody ProductCategoryRequest request) {
|
||||||
|
CurrentUser user = authService.currentUser();
|
||||||
|
request.setTenantId(user.getTenantId());
|
||||||
|
request.setCommunityId(user.getCommunityId());
|
||||||
|
return ApiResponse.ok(productService.createCategory(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/api/admin/product-categories/{id}")
|
||||||
|
@RequireRole({"PLATFORM_ADMIN", "COMMUNITY_ADMIN", "MERCHANT"})
|
||||||
|
public ApiResponse<ProductCategoryResponse> updateCategory(@PathVariable Long id, @Valid @RequestBody ProductCategoryRequest request) {
|
||||||
|
CurrentUser user = authService.currentUser();
|
||||||
|
request.setTenantId(user.getTenantId());
|
||||||
|
request.setCommunityId(user.getCommunityId());
|
||||||
|
return ApiResponse.ok(productService.updateCategory(id, request));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/api/admin/product-categories/{id}/enable")
|
||||||
|
@RequireRole({"PLATFORM_ADMIN", "COMMUNITY_ADMIN", "MERCHANT"})
|
||||||
|
public ApiResponse<ProductCategoryResponse> enableCategory(@PathVariable Long id) {
|
||||||
|
return ApiResponse.ok(productService.enableCategory(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/api/admin/product-categories/{id}/disable")
|
||||||
|
@RequireRole({"PLATFORM_ADMIN", "COMMUNITY_ADMIN", "MERCHANT"})
|
||||||
|
public ApiResponse<ProductCategoryResponse> disableCategory(@PathVariable Long id) {
|
||||||
|
return ApiResponse.ok(productService.disableCategory(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/api/admin/products")
|
||||||
|
@RequireRole({"PLATFORM_ADMIN", "COMMUNITY_ADMIN", "MERCHANT"})
|
||||||
|
public ApiResponse<List<ProductResponse>> listAdminProducts(@RequestParam(required = false) Long merchantId,
|
||||||
|
@RequestParam(required = false) Long categoryId) {
|
||||||
|
CurrentUser user = authService.currentUser();
|
||||||
|
return ApiResponse.ok(productService.listAdmin(user.getCommunityId(), merchantId, categoryId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/api/admin/products")
|
||||||
|
@RequireRole({"PLATFORM_ADMIN", "COMMUNITY_ADMIN", "MERCHANT"})
|
||||||
|
public ApiResponse<ProductResponse> createProduct(@Valid @RequestBody ProductRequest request) {
|
||||||
|
CurrentUser user = authService.currentUser();
|
||||||
|
request.setTenantId(user.getTenantId());
|
||||||
|
request.setCommunityId(user.getCommunityId());
|
||||||
|
return ApiResponse.ok(productService.createProduct(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/api/admin/products/{id}")
|
||||||
|
@RequireRole({"PLATFORM_ADMIN", "COMMUNITY_ADMIN", "MERCHANT"})
|
||||||
|
public ApiResponse<ProductResponse> updateProduct(@PathVariable Long id, @Valid @RequestBody ProductRequest request) {
|
||||||
|
CurrentUser user = authService.currentUser();
|
||||||
|
request.setTenantId(user.getTenantId());
|
||||||
|
request.setCommunityId(user.getCommunityId());
|
||||||
|
return ApiResponse.ok(productService.updateProduct(id, request));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/api/admin/products/{id}/on-shelf")
|
||||||
|
@RequireRole({"PLATFORM_ADMIN", "COMMUNITY_ADMIN", "MERCHANT"})
|
||||||
|
public ApiResponse<ProductResponse> onShelf(@PathVariable Long id) {
|
||||||
|
return ApiResponse.ok(productService.onShelf(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/api/admin/products/{id}/off-shelf")
|
||||||
|
@RequireRole({"PLATFORM_ADMIN", "COMMUNITY_ADMIN", "MERCHANT"})
|
||||||
|
public ApiResponse<ProductResponse> offShelf(@PathVariable Long id) {
|
||||||
|
return ApiResponse.ok(productService.offShelf(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/api/admin/products/{id}/skus")
|
||||||
|
@RequireRole({"PLATFORM_ADMIN", "COMMUNITY_ADMIN", "MERCHANT"})
|
||||||
|
public ApiResponse<ProductSkuResponse> addSku(@PathVariable Long id, @Valid @RequestBody ProductSkuRequest request) {
|
||||||
|
CurrentUser user = authService.currentUser();
|
||||||
|
request.setTenantId(user.getTenantId());
|
||||||
|
request.setCommunityId(user.getCommunityId());
|
||||||
|
return ApiResponse.ok(productService.addSku(id, request));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/api/admin/products/{id}/skus/{skuId}")
|
||||||
|
@RequireRole({"PLATFORM_ADMIN", "COMMUNITY_ADMIN", "MERCHANT"})
|
||||||
|
public ApiResponse<ProductSkuResponse> updateSku(@PathVariable Long id,
|
||||||
|
@PathVariable Long skuId,
|
||||||
|
@Valid @RequestBody ProductSkuRequest request) {
|
||||||
|
CurrentUser user = authService.currentUser();
|
||||||
|
request.setTenantId(user.getTenantId());
|
||||||
|
request.setCommunityId(user.getCommunityId());
|
||||||
|
return ApiResponse.ok(productService.updateSku(id, skuId, request));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/api/admin/products/{id}/skus/{skuId}/enable")
|
||||||
|
@RequireRole({"PLATFORM_ADMIN", "COMMUNITY_ADMIN", "MERCHANT"})
|
||||||
|
public ApiResponse<ProductSkuResponse> enableSku(@PathVariable Long id, @PathVariable Long skuId) {
|
||||||
|
return ApiResponse.ok(productService.enableSku(id, skuId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/api/admin/products/{id}/skus/{skuId}/disable")
|
||||||
|
@RequireRole({"PLATFORM_ADMIN", "COMMUNITY_ADMIN", "MERCHANT"})
|
||||||
|
public ApiResponse<ProductSkuResponse> disableSku(@PathVariable Long id, @PathVariable Long skuId) {
|
||||||
|
return ApiResponse.ok(productService.disableSku(id, skuId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/api/mini/product-categories")
|
||||||
|
public ApiResponse<List<ProductCategoryResponse>> listMiniCategories() {
|
||||||
|
CurrentUser user = authService.currentUser();
|
||||||
|
return ApiResponse.ok(productService.listCategories(user.getCommunityId(), true));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/api/mini/products")
|
||||||
|
public ApiResponse<List<ProductResponse>> listMiniProducts(@RequestParam(required = false) Long categoryId) {
|
||||||
|
CurrentUser user = authService.currentUser();
|
||||||
|
return ApiResponse.ok(productService.listMini(user.getCommunityId(), categoryId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/api/mini/products/{id}")
|
||||||
|
public ApiResponse<ProductResponse> miniProductDetail(@PathVariable Long id) {
|
||||||
|
return ApiResponse.ok(productService.detail(id));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
package com.linhelp.product;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotBlank;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
|
||||||
|
public class ProductRequest {
|
||||||
|
private Long tenantId;
|
||||||
|
private Long communityId;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private Long merchantId;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private Long categoryId;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
private String coverUrl;
|
||||||
|
private String description;
|
||||||
|
private String unitName = "件";
|
||||||
|
|
||||||
|
public Long getTenantId() {
|
||||||
|
return tenantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTenantId(Long tenantId) {
|
||||||
|
this.tenantId = tenantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getCommunityId() {
|
||||||
|
return communityId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCommunityId(Long communityId) {
|
||||||
|
this.communityId = communityId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getMerchantId() {
|
||||||
|
return merchantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMerchantId(Long merchantId) {
|
||||||
|
this.merchantId = merchantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getCategoryId() {
|
||||||
|
return categoryId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCategoryId(Long categoryId) {
|
||||||
|
this.categoryId = categoryId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCoverUrl() {
|
||||||
|
return coverUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCoverUrl(String coverUrl) {
|
||||||
|
this.coverUrl = coverUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDescription(String description) {
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUnitName() {
|
||||||
|
return unitName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUnitName(String unitName) {
|
||||||
|
this.unitName = unitName;
|
||||||
|
}
|
||||||
|
}
|
||||||
106
backend/src/main/java/com/linhelp/product/ProductResponse.java
Normal file
106
backend/src/main/java/com/linhelp/product/ProductResponse.java
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
package com.linhelp.product;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ProductResponse {
|
||||||
|
private Long id;
|
||||||
|
private Long tenantId;
|
||||||
|
private Long communityId;
|
||||||
|
private Long merchantId;
|
||||||
|
private Long categoryId;
|
||||||
|
private String name;
|
||||||
|
private String coverUrl;
|
||||||
|
private String description;
|
||||||
|
private String unitName;
|
||||||
|
private String status;
|
||||||
|
private List<ProductSkuResponse> skus = new ArrayList<ProductSkuResponse>();
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getTenantId() {
|
||||||
|
return tenantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTenantId(Long tenantId) {
|
||||||
|
this.tenantId = tenantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getCommunityId() {
|
||||||
|
return communityId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCommunityId(Long communityId) {
|
||||||
|
this.communityId = communityId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getMerchantId() {
|
||||||
|
return merchantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMerchantId(Long merchantId) {
|
||||||
|
this.merchantId = merchantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getCategoryId() {
|
||||||
|
return categoryId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCategoryId(Long categoryId) {
|
||||||
|
this.categoryId = categoryId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCoverUrl() {
|
||||||
|
return coverUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCoverUrl(String coverUrl) {
|
||||||
|
this.coverUrl = coverUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDescription(String description) {
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUnitName() {
|
||||||
|
return unitName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUnitName(String unitName) {
|
||||||
|
this.unitName = unitName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(String status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ProductSkuResponse> getSkus() {
|
||||||
|
return skus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSkus(List<ProductSkuResponse> skus) {
|
||||||
|
this.skus = skus;
|
||||||
|
}
|
||||||
|
}
|
||||||
329
backend/src/main/java/com/linhelp/product/ProductService.java
Normal file
329
backend/src/main/java/com/linhelp/product/ProductService.java
Normal file
@@ -0,0 +1,329 @@
|
|||||||
|
package com.linhelp.product;
|
||||||
|
|
||||||
|
import com.linhelp.common.exception.BizException;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class ProductService {
|
||||||
|
public static final String STATUS_ON_SHELF = "ON_SHELF";
|
||||||
|
public static final String STATUS_OFF_SHELF = "OFF_SHELF";
|
||||||
|
|
||||||
|
private final AtomicLong categoryIds = new AtomicLong(1L);
|
||||||
|
private final AtomicLong productIds = new AtomicLong(1L);
|
||||||
|
private final AtomicLong skuIds = new AtomicLong(1L);
|
||||||
|
private final Map<Long, ProductCategoryResponse> categories = new LinkedHashMap<Long, ProductCategoryResponse>();
|
||||||
|
private final Map<Long, ProductResponse> products = new LinkedHashMap<Long, ProductResponse>();
|
||||||
|
private final Map<Long, ProductSkuResponse> skus = new LinkedHashMap<Long, ProductSkuResponse>();
|
||||||
|
|
||||||
|
public synchronized ProductCategoryResponse createCategory(ProductCategoryRequest request) {
|
||||||
|
ProductCategoryResponse category = new ProductCategoryResponse();
|
||||||
|
category.setId(categoryIds.getAndIncrement());
|
||||||
|
category.setTenantId(request.getTenantId());
|
||||||
|
category.setCommunityId(request.getCommunityId());
|
||||||
|
category.setName(request.getName());
|
||||||
|
category.setSortNo(request.getSortNo());
|
||||||
|
category.setEnabled(request.isEnabled());
|
||||||
|
categories.put(category.getId(), category);
|
||||||
|
return copyCategory(category);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized ProductCategoryResponse updateCategory(Long id, ProductCategoryRequest request) {
|
||||||
|
ProductCategoryResponse category = requireCategory(id);
|
||||||
|
category.setName(request.getName());
|
||||||
|
category.setSortNo(request.getSortNo());
|
||||||
|
category.setEnabled(request.isEnabled());
|
||||||
|
if (request.getTenantId() != null) {
|
||||||
|
category.setTenantId(request.getTenantId());
|
||||||
|
}
|
||||||
|
if (request.getCommunityId() != null) {
|
||||||
|
category.setCommunityId(request.getCommunityId());
|
||||||
|
}
|
||||||
|
return copyCategory(category);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized ProductCategoryResponse enableCategory(Long id) {
|
||||||
|
ProductCategoryResponse category = requireCategory(id);
|
||||||
|
category.setEnabled(true);
|
||||||
|
return copyCategory(category);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized ProductCategoryResponse disableCategory(Long id) {
|
||||||
|
ProductCategoryResponse category = requireCategory(id);
|
||||||
|
category.setEnabled(false);
|
||||||
|
return copyCategory(category);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized List<ProductCategoryResponse> listCategories(Long communityId, boolean onlyEnabled) {
|
||||||
|
List<ProductCategoryResponse> result = new ArrayList<ProductCategoryResponse>();
|
||||||
|
for (ProductCategoryResponse category : categories.values()) {
|
||||||
|
if (communityId.equals(category.getCommunityId()) && (!onlyEnabled || category.isEnabled())) {
|
||||||
|
result.add(copyCategory(category));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Collections.sort(result, new Comparator<ProductCategoryResponse>() {
|
||||||
|
@Override
|
||||||
|
public int compare(ProductCategoryResponse left, ProductCategoryResponse right) {
|
||||||
|
return Integer.valueOf(left.getSortNo()).compareTo(right.getSortNo());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized ProductResponse createProduct(ProductRequest request) {
|
||||||
|
ProductResponse product = new ProductResponse();
|
||||||
|
product.setId(productIds.getAndIncrement());
|
||||||
|
product.setTenantId(request.getTenantId());
|
||||||
|
product.setCommunityId(request.getCommunityId());
|
||||||
|
copyProductFields(request, product);
|
||||||
|
product.setStatus(STATUS_OFF_SHELF);
|
||||||
|
products.put(product.getId(), product);
|
||||||
|
return copyProduct(product);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized ProductResponse updateProduct(Long id, ProductRequest request) {
|
||||||
|
ProductResponse product = requireProduct(id);
|
||||||
|
copyProductFields(request, product);
|
||||||
|
if (request.getTenantId() != null) {
|
||||||
|
product.setTenantId(request.getTenantId());
|
||||||
|
}
|
||||||
|
if (request.getCommunityId() != null) {
|
||||||
|
product.setCommunityId(request.getCommunityId());
|
||||||
|
}
|
||||||
|
return copyProduct(product);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized ProductResponse onShelf(Long id) {
|
||||||
|
ProductResponse product = requireProduct(id);
|
||||||
|
product.setStatus(STATUS_ON_SHELF);
|
||||||
|
return copyProduct(product);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized ProductResponse offShelf(Long id) {
|
||||||
|
ProductResponse product = requireProduct(id);
|
||||||
|
product.setStatus(STATUS_OFF_SHELF);
|
||||||
|
return copyProduct(product);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized ProductResponse detail(Long id) {
|
||||||
|
return copyProduct(requireProduct(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized List<ProductResponse> listAdmin(Long communityId, Long merchantId, Long categoryId) {
|
||||||
|
return listProducts(communityId, merchantId, categoryId, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized List<ProductResponse> listMini(Long communityId, Long categoryId) {
|
||||||
|
return listProducts(communityId, null, categoryId, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized ProductSkuResponse addSku(Long productId, ProductSkuRequest request) {
|
||||||
|
ProductResponse product = requireProduct(productId);
|
||||||
|
ProductSkuResponse sku = new ProductSkuResponse();
|
||||||
|
sku.setId(skuIds.getAndIncrement());
|
||||||
|
sku.setTenantId(request.getTenantId() == null ? product.getTenantId() : request.getTenantId());
|
||||||
|
sku.setCommunityId(request.getCommunityId() == null ? product.getCommunityId() : request.getCommunityId());
|
||||||
|
sku.setProductId(productId);
|
||||||
|
sku.setProductName(product.getName());
|
||||||
|
copySkuFields(request, sku);
|
||||||
|
skus.put(sku.getId(), sku);
|
||||||
|
return copySku(sku);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized ProductSkuResponse updateSku(Long productId, Long skuId, ProductSkuRequest request) {
|
||||||
|
requireProduct(productId);
|
||||||
|
ProductSkuResponse sku = requireSku(skuId);
|
||||||
|
if (!productId.equals(sku.getProductId())) {
|
||||||
|
throw new BizException(404, "商品规格不存在");
|
||||||
|
}
|
||||||
|
copySkuFields(request, sku);
|
||||||
|
if (request.getTenantId() != null) {
|
||||||
|
sku.setTenantId(request.getTenantId());
|
||||||
|
}
|
||||||
|
if (request.getCommunityId() != null) {
|
||||||
|
sku.setCommunityId(request.getCommunityId());
|
||||||
|
}
|
||||||
|
ProductResponse product = products.get(productId);
|
||||||
|
sku.setProductName(product.getName());
|
||||||
|
return copySku(sku);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized ProductSkuResponse enableSku(Long productId, Long skuId) {
|
||||||
|
return setSkuEnabled(productId, skuId, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized ProductSkuResponse disableSku(Long productId, Long skuId) {
|
||||||
|
return setSkuEnabled(productId, skuId, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized ProductSkuResponse createDemoSku(Long tenantId, Long communityId, Long productId, String skuName, int priceCent, int stock) {
|
||||||
|
ProductSkuResponse sku = new ProductSkuResponse();
|
||||||
|
sku.setId(skuIds.getAndIncrement());
|
||||||
|
sku.setTenantId(tenantId);
|
||||||
|
sku.setCommunityId(communityId);
|
||||||
|
sku.setProductId(productId);
|
||||||
|
sku.setProductName(resolveProductName(productId));
|
||||||
|
sku.setSkuName(skuName);
|
||||||
|
sku.setPriceCent(priceCent);
|
||||||
|
sku.setStock(stock);
|
||||||
|
sku.setEnabled(true);
|
||||||
|
skus.put(sku.getId(), sku);
|
||||||
|
return copySku(sku);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized ProductSkuResponse getSku(Long skuId) {
|
||||||
|
return copySku(requireSku(skuId));
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void deductStock(Long skuId, int quantity) {
|
||||||
|
if (quantity <= 0) {
|
||||||
|
throw new BizException("购买数量必须大于0");
|
||||||
|
}
|
||||||
|
ProductSkuResponse sku = requireSku(skuId);
|
||||||
|
if (!sku.isEnabled()) {
|
||||||
|
throw new BizException("商品规格已下架");
|
||||||
|
}
|
||||||
|
if (sku.getStock() < quantity) {
|
||||||
|
throw new BizException("库存不足");
|
||||||
|
}
|
||||||
|
sku.setStock(sku.getStock() - quantity);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ProductSkuResponse setSkuEnabled(Long productId, Long skuId, boolean enabled) {
|
||||||
|
requireProduct(productId);
|
||||||
|
ProductSkuResponse sku = requireSku(skuId);
|
||||||
|
if (!productId.equals(sku.getProductId())) {
|
||||||
|
throw new BizException(404, "商品规格不存在");
|
||||||
|
}
|
||||||
|
sku.setEnabled(enabled);
|
||||||
|
return copySku(sku);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ProductResponse> listProducts(Long communityId, Long merchantId, Long categoryId, boolean onlyOnShelf) {
|
||||||
|
List<ProductResponse> result = new ArrayList<ProductResponse>();
|
||||||
|
for (ProductResponse product : products.values()) {
|
||||||
|
if (!communityId.equals(product.getCommunityId())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (merchantId != null && !merchantId.equals(product.getMerchantId())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (categoryId != null && !categoryId.equals(product.getCategoryId())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (onlyOnShelf && !STATUS_ON_SHELF.equals(product.getStatus())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
result.add(copyProduct(product));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ProductCategoryResponse requireCategory(Long id) {
|
||||||
|
ProductCategoryResponse category = categories.get(id);
|
||||||
|
if (category == null) {
|
||||||
|
throw new BizException(404, "商品分类不存在");
|
||||||
|
}
|
||||||
|
return category;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ProductResponse requireProduct(Long id) {
|
||||||
|
ProductResponse product = products.get(id);
|
||||||
|
if (product == null) {
|
||||||
|
throw new BizException(404, "商品不存在");
|
||||||
|
}
|
||||||
|
return product;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ProductSkuResponse requireSku(Long id) {
|
||||||
|
ProductSkuResponse sku = skus.get(id);
|
||||||
|
if (sku == null) {
|
||||||
|
throw new BizException(404, "商品规格不存在");
|
||||||
|
}
|
||||||
|
return sku;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copyProductFields(ProductRequest request, ProductResponse product) {
|
||||||
|
product.setMerchantId(request.getMerchantId());
|
||||||
|
product.setCategoryId(request.getCategoryId());
|
||||||
|
product.setName(request.getName());
|
||||||
|
product.setCoverUrl(request.getCoverUrl());
|
||||||
|
product.setDescription(request.getDescription());
|
||||||
|
product.setUnitName(request.getUnitName());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copySkuFields(ProductSkuRequest request, ProductSkuResponse sku) {
|
||||||
|
sku.setSkuName(request.getSkuName());
|
||||||
|
sku.setPriceCent(request.getPriceCent());
|
||||||
|
sku.setOriginPriceCent(request.getOriginPriceCent());
|
||||||
|
sku.setStock(request.getStock());
|
||||||
|
sku.setEnabled(request.isEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveProductName(Long productId) {
|
||||||
|
ProductResponse product = products.get(productId);
|
||||||
|
if (product == null) {
|
||||||
|
return "Demo Product " + productId;
|
||||||
|
}
|
||||||
|
return product.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ProductCategoryResponse copyCategory(ProductCategoryResponse source) {
|
||||||
|
ProductCategoryResponse target = new ProductCategoryResponse();
|
||||||
|
target.setId(source.getId());
|
||||||
|
target.setTenantId(source.getTenantId());
|
||||||
|
target.setCommunityId(source.getCommunityId());
|
||||||
|
target.setName(source.getName());
|
||||||
|
target.setSortNo(source.getSortNo());
|
||||||
|
target.setEnabled(source.isEnabled());
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ProductResponse copyProduct(ProductResponse source) {
|
||||||
|
ProductResponse target = new ProductResponse();
|
||||||
|
target.setId(source.getId());
|
||||||
|
target.setTenantId(source.getTenantId());
|
||||||
|
target.setCommunityId(source.getCommunityId());
|
||||||
|
target.setMerchantId(source.getMerchantId());
|
||||||
|
target.setCategoryId(source.getCategoryId());
|
||||||
|
target.setName(source.getName());
|
||||||
|
target.setCoverUrl(source.getCoverUrl());
|
||||||
|
target.setDescription(source.getDescription());
|
||||||
|
target.setUnitName(source.getUnitName());
|
||||||
|
target.setStatus(source.getStatus());
|
||||||
|
target.setSkus(listSkusByProduct(source.getId()));
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ProductSkuResponse> listSkusByProduct(Long productId) {
|
||||||
|
List<ProductSkuResponse> result = new ArrayList<ProductSkuResponse>();
|
||||||
|
for (ProductSkuResponse sku : skus.values()) {
|
||||||
|
if (productId.equals(sku.getProductId())) {
|
||||||
|
result.add(copySku(sku));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ProductSkuResponse copySku(ProductSkuResponse source) {
|
||||||
|
ProductSkuResponse target = new ProductSkuResponse();
|
||||||
|
target.setId(source.getId());
|
||||||
|
target.setTenantId(source.getTenantId());
|
||||||
|
target.setCommunityId(source.getCommunityId());
|
||||||
|
target.setProductId(source.getProductId());
|
||||||
|
target.setProductName(source.getProductName());
|
||||||
|
target.setSkuName(source.getSkuName());
|
||||||
|
target.setPriceCent(source.getPriceCent());
|
||||||
|
target.setOriginPriceCent(source.getOriginPriceCent());
|
||||||
|
target.setStock(source.getStock());
|
||||||
|
target.setEnabled(source.isEnabled());
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package com.linhelp.product;
|
||||||
|
|
||||||
|
import javax.validation.constraints.Min;
|
||||||
|
import javax.validation.constraints.NotBlank;
|
||||||
|
|
||||||
|
public class ProductSkuRequest {
|
||||||
|
private Long tenantId;
|
||||||
|
private Long communityId;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
private String skuName;
|
||||||
|
|
||||||
|
@Min(0)
|
||||||
|
private int priceCent;
|
||||||
|
|
||||||
|
private Integer originPriceCent;
|
||||||
|
|
||||||
|
@Min(0)
|
||||||
|
private int stock;
|
||||||
|
|
||||||
|
private boolean enabled = true;
|
||||||
|
|
||||||
|
public Long getTenantId() {
|
||||||
|
return tenantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTenantId(Long tenantId) {
|
||||||
|
this.tenantId = tenantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getCommunityId() {
|
||||||
|
return communityId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCommunityId(Long communityId) {
|
||||||
|
this.communityId = communityId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSkuName() {
|
||||||
|
return skuName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSkuName(String skuName) {
|
||||||
|
this.skuName = skuName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPriceCent() {
|
||||||
|
return priceCent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPriceCent(int priceCent) {
|
||||||
|
this.priceCent = priceCent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getOriginPriceCent() {
|
||||||
|
return originPriceCent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOriginPriceCent(Integer originPriceCent) {
|
||||||
|
this.originPriceCent = originPriceCent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStock() {
|
||||||
|
return stock;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStock(int stock) {
|
||||||
|
this.stock = stock;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnabled(boolean enabled) {
|
||||||
|
this.enabled = enabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
package com.linhelp.product;
|
||||||
|
|
||||||
|
public class ProductSkuResponse {
|
||||||
|
private Long id;
|
||||||
|
private Long tenantId;
|
||||||
|
private Long communityId;
|
||||||
|
private Long productId;
|
||||||
|
private String productName;
|
||||||
|
private String skuName;
|
||||||
|
private int priceCent;
|
||||||
|
private Integer originPriceCent;
|
||||||
|
private int stock;
|
||||||
|
private boolean enabled;
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getTenantId() {
|
||||||
|
return tenantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTenantId(Long tenantId) {
|
||||||
|
this.tenantId = tenantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getCommunityId() {
|
||||||
|
return communityId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCommunityId(Long communityId) {
|
||||||
|
this.communityId = communityId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getProductId() {
|
||||||
|
return productId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProductId(Long productId) {
|
||||||
|
this.productId = productId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProductName() {
|
||||||
|
return productName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProductName(String productName) {
|
||||||
|
this.productName = productName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSkuName() {
|
||||||
|
return skuName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSkuName(String skuName) {
|
||||||
|
this.skuName = skuName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPriceCent() {
|
||||||
|
return priceCent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPriceCent(int priceCent) {
|
||||||
|
this.priceCent = priceCent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getOriginPriceCent() {
|
||||||
|
return originPriceCent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOriginPriceCent(Integer originPriceCent) {
|
||||||
|
this.originPriceCent = originPriceCent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStock() {
|
||||||
|
return stock;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStock(int stock) {
|
||||||
|
this.stock = stock;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnabled(boolean enabled) {
|
||||||
|
this.enabled = enabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package com.linhelp.order;
|
||||||
|
|
||||||
|
import com.linhelp.common.enums.DeliveryMethod;
|
||||||
|
import com.linhelp.common.enums.GoodsOrderStatus;
|
||||||
|
import com.linhelp.product.ProductService;
|
||||||
|
import com.linhelp.product.ProductSkuResponse;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
|
||||||
|
class GoodsOrderServiceTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void cannotCreateGoodsOrderWhenSkuStockIsInsufficient() {
|
||||||
|
ProductService productService = new ProductService();
|
||||||
|
ProductSkuResponse sku = productService.createDemoSku(1L, 1L, 1L, "500g", 990, 2);
|
||||||
|
GoodsOrderService goodsOrderService = new GoodsOrderService(productService);
|
||||||
|
|
||||||
|
CreateGoodsOrderRequest request = new CreateGoodsOrderRequest();
|
||||||
|
request.setTenantId(1L);
|
||||||
|
request.setCommunityId(1L);
|
||||||
|
request.setMerchantId(1L);
|
||||||
|
request.setAddressId(1L);
|
||||||
|
request.setDeliveryMethod(DeliveryMethod.IMMEDIATE);
|
||||||
|
request.setItems(Collections.singletonList(new CreateGoodsOrderItemRequest(1L, sku.getId(), 99)));
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> goodsOrderService.create(1L, request))
|
||||||
|
.hasMessageContaining("库存不足");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void selfPickupOrderMovesToPendingPickupAfterPreparing() {
|
||||||
|
ProductService productService = new ProductService();
|
||||||
|
ProductSkuResponse sku = productService.createDemoSku(1L, 1L, 1L, "500g", 990, 5);
|
||||||
|
GoodsOrderService goodsOrderService = new GoodsOrderService(productService);
|
||||||
|
|
||||||
|
CreateGoodsOrderRequest request = new CreateGoodsOrderRequest();
|
||||||
|
request.setTenantId(1L);
|
||||||
|
request.setCommunityId(1L);
|
||||||
|
request.setMerchantId(1L);
|
||||||
|
request.setDeliveryMethod(DeliveryMethod.SELF_PICKUP);
|
||||||
|
request.setItems(Collections.singletonList(new CreateGoodsOrderItemRequest(1L, sku.getId(), 1)));
|
||||||
|
GoodsOrderResponse order = goodsOrderService.create(1L, request);
|
||||||
|
|
||||||
|
goodsOrderService.confirm(order.getId());
|
||||||
|
goodsOrderService.markPrepared(order.getId());
|
||||||
|
|
||||||
|
assertThat(goodsOrderService.detail(order.getId()).getStatus()).isEqualTo(GoodsOrderStatus.PENDING_PICKUP);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.linhelp.product;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class ProductInventoryTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void deductStockReducesSkuStock() {
|
||||||
|
ProductService productService = new ProductService();
|
||||||
|
ProductSkuResponse sku = productService.createDemoSku(1L, 1L, 1L, "500g", 990, 5);
|
||||||
|
|
||||||
|
productService.deductStock(sku.getId(), 2);
|
||||||
|
|
||||||
|
assertThat(productService.getSku(sku.getId()).getStock()).isEqualTo(3);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user