diff --git a/java_codebase/customer/src/main/java/com/yas/customer/service/UserAddressService.java b/java_codebase/customer/src/main/java/com/yas/customer/service/UserAddressService.java index 2d056f2..72545c2 100644 --- a/java_codebase/customer/src/main/java/com/yas/customer/service/UserAddressService.java +++ b/java_codebase/customer/src/main/java/com/yas/customer/service/UserAddressService.java @@ -39,13 +39,29 @@ public List getUserAddressList() { List addressVmList = locationService.getAddressesByIdList( userAddressList.stream().map(UserAddress::getAddressId).collect(Collectors.toList())); - List addressActiveVms = userAddressList.stream().flatMap(userAddress -> addressVmList.stream() - .filter(addressDetailVm -> userAddress.getAddressId().equals(addressDetailVm.id())).map( - addressDetailVm -> new ActiveAddressVm(addressDetailVm.id(), addressDetailVm.contactName(), - addressDetailVm.phone(), addressDetailVm.addressLine1(), addressDetailVm.city(), - addressDetailVm.zipCode(), addressDetailVm.districtId(), addressDetailVm.districtName(), - addressDetailVm.stateOrProvinceId(), addressDetailVm.stateOrProvinceName(), - addressDetailVm.countryId(), addressDetailVm.countryName(), userAddress.getIsActive()))) + List addressActiveVms = userAddressList.stream() + .flatMap(userAddress -> addressVmList.stream() + .filter(addressDetailVm -> userAddress.getAddressId().equals(addressDetailVm.id())) + .map( + addressDetailVm -> ActiveAddressVm.builder() + .id(addressDetailVm.id()) + .contactName(addressDetailVm.contactName()) + .phone(addressDetailVm.phone()) + .addressLine1(addressDetailVm.addressLine1()) + .city(addressDetailVm.city()) + .zipCode(addressDetailVm.zipCode()) + .districtId(addressDetailVm.districtId()) + .districtName(addressDetailVm.districtName()) + .stateOrProvinceId(addressDetailVm.stateOrProvinceId()) + .stateOrProvinceName(addressDetailVm.stateOrProvinceName()) + .stateOrProvinceCode(addressDetailVm.stateOrProvinceCode()) + .countryId(addressDetailVm.countryId()) + .countryName(addressDetailVm.countryName()) + .countryCode2(addressDetailVm.countryCode2()) + .countryCode3(addressDetailVm.countryCode3()) + .isActive(userAddress.getIsActive()) + .build() + )) .toList(); //sort by isActive @@ -97,4 +113,4 @@ public void chooseDefaultAddress(Long id) { } userAddressRepository.saveAll(userAddressList); } -} +} \ No newline at end of file diff --git a/java_codebase/customer/src/main/java/com/yas/customer/viewmodel/address/ActiveAddressVm.java b/java_codebase/customer/src/main/java/com/yas/customer/viewmodel/address/ActiveAddressVm.java index b3546e6..2ad21c6 100644 --- a/java_codebase/customer/src/main/java/com/yas/customer/viewmodel/address/ActiveAddressVm.java +++ b/java_codebase/customer/src/main/java/com/yas/customer/viewmodel/address/ActiveAddressVm.java @@ -1,5 +1,8 @@ package com.yas.customer.viewmodel.address; +import lombok.Builder; + +@Builder(toBuilder = true) public record ActiveAddressVm( Long id, String contactName, @@ -11,9 +14,12 @@ public record ActiveAddressVm( String districtName, Long stateOrProvinceId, String stateOrProvinceName, + String stateOrProvinceCode, Long countryId, String countryName, + String countryCode2, + String countryCode3, Boolean isActive ) { -} +} \ No newline at end of file diff --git a/java_codebase/customer/src/main/java/com/yas/customer/viewmodel/address/AddressDetailVm.java b/java_codebase/customer/src/main/java/com/yas/customer/viewmodel/address/AddressDetailVm.java index 61e6252..b14fe11 100644 --- a/java_codebase/customer/src/main/java/com/yas/customer/viewmodel/address/AddressDetailVm.java +++ b/java_codebase/customer/src/main/java/com/yas/customer/viewmodel/address/AddressDetailVm.java @@ -2,9 +2,20 @@ import lombok.Builder; -@Builder -public record AddressDetailVm(Long id, String contactName, String phone, - String addressLine1, String city, String zipCode, - Long districtId, String districtName, Long stateOrProvinceId, - String stateOrProvinceName, Long countryId, String countryName) { +@Builder(toBuilder = true) +public record AddressDetailVm(Long id, + String contactName, + String phone, + String addressLine1, + String city, + String zipCode, + Long districtId, + String districtName, + Long stateOrProvinceId, + String stateOrProvinceName, + String stateOrProvinceCode, + Long countryId, + String countryName, + String countryCode2, + String countryCode3) { } \ No newline at end of file diff --git a/java_codebase/customer/src/test/java/com/yas/customer/controller/UserAddressControllerTest.java b/java_codebase/customer/src/test/java/com/yas/customer/controller/UserAddressControllerTest.java index 87ad098..788c51e 100644 --- a/java_codebase/customer/src/test/java/com/yas/customer/controller/UserAddressControllerTest.java +++ b/java_codebase/customer/src/test/java/com/yas/customer/controller/UserAddressControllerTest.java @@ -106,36 +106,42 @@ void testChooseDefaultAddress_whenNormalCase_responseOk() throws Exception { private List getActiveAddressVms() { return List.of( - new ActiveAddressVm( - 1L, - "John Doe", - "123-456-7890", - "123 Elm Street", - "Springfield", - "12345", - 101L, - "District A", - 10L, - "State A", - 1L, - "Country A", - true - ), - new ActiveAddressVm( - 2L, - "Jane Smith", - "987-654-3210", - "456 Oak Avenue", - "Springfield", - "67890", - 102L, - "District B", - 11L, - "State B", - 2L, - "Country B", - false - ) + ActiveAddressVm.builder() + .id(1L) + .contactName("John Doe") + .phone("123-456-7890") + .addressLine1("123 Elm Street") + .city("Springfield") + .zipCode("12345") + .districtId(101L) + .districtName("District A") + .stateOrProvinceId(10L) + .stateOrProvinceName("State A") + .stateOrProvinceCode("ST") + .countryId(1L) + .countryName("Country A") + .countryCode2("CA") + .countryCode3("CAN") + .isActive(true) + .build(), + ActiveAddressVm.builder() + .id(2L) + .contactName("Jane Smith") + .phone("987-654-3210") + .addressLine1("456 Oak Avenue") + .city("Springfield") + .zipCode("67890") + .districtId(102L) + .districtName("District B") + .stateOrProvinceId(11L) + .stateOrProvinceName("State B") + .stateOrProvinceCode("SB") + .countryId(2L) + .countryName("Country B") + .countryCode2("CB") + .countryCode3("CBB") + .isActive(false) + .build() ); } diff --git a/java_codebase/customer/src/test/java/com/yas/customer/service/LocationServiceTest.java b/java_codebase/customer/src/test/java/com/yas/customer/service/LocationServiceTest.java index 7f6dd15..935974e 100644 --- a/java_codebase/customer/src/test/java/com/yas/customer/service/LocationServiceTest.java +++ b/java_codebase/customer/src/test/java/com/yas/customer/service/LocationServiceTest.java @@ -126,20 +126,20 @@ void testCreateAddress() { } private AddressDetailVm getAddressDetailVm() { - return new AddressDetailVm( - 1L, - "John Doe", - "+1234567890", - "123 Elm Street", - "Springfield", - "62701", - 101L, - "Downtown", - 201L, - "Illinois", - 301L, - "United States" - ); + return AddressDetailVm.builder() + .id(1L) + .contactName("John Doe") + .phone("+1234567890") + .addressLine1("123 Elm Street") + .city("Springfield") + .zipCode("62701") + .districtId(101L) + .districtName("Downtown") + .stateOrProvinceId(201L) + .stateOrProvinceName("Illinois") + .countryId(301L) + .countryName("United States") + .build(); } private AddressPostVm getAddressPostVm() { diff --git a/java_codebase/location/src/main/java/com/yas/location/viewmodel/address/AddressDetailVm.java b/java_codebase/location/src/main/java/com/yas/location/viewmodel/address/AddressDetailVm.java index 5e1dc3b..c35b384 100644 --- a/java_codebase/location/src/main/java/com/yas/location/viewmodel/address/AddressDetailVm.java +++ b/java_codebase/location/src/main/java/com/yas/location/viewmodel/address/AddressDetailVm.java @@ -3,11 +3,23 @@ import com.yas.location.model.Address; import lombok.Builder; -@Builder -public record AddressDetailVm(Long id, String contactName, String phone, - String addressLine1, String addressLine2, String city, String zipCode, - Long districtId, String districtName, Long stateOrProvinceId, - String stateOrProvinceName, Long countryId, String countryName) { +@Builder(toBuilder = true) +public record AddressDetailVm(Long id, + String contactName, + String phone, + String addressLine1, + String addressLine2, + String city, + String zipCode, + Long districtId, + String districtName, + Long stateOrProvinceId, + String stateOrProvinceName, + String stateOrProvinceCode, + Long countryId, + String countryName, + String countryCode2, + String countryCode3) { public static AddressDetailVm fromModel(Address address) { return AddressDetailVm.builder() .id(address.getId()) @@ -20,8 +32,11 @@ public static AddressDetailVm fromModel(Address address) { .districtName(address.getDistrict().getName()) .stateOrProvinceId(address.getStateOrProvince().getId()) .stateOrProvinceName(address.getStateOrProvince().getName()) + .stateOrProvinceCode(address.getStateOrProvince().getCode()) .countryId(address.getCountry().getId()) .countryName(address.getCountry().getName()) + .countryCode2(address.getCountry().getCode2()) + .countryCode3(address.getCountry().getCode3()) .zipCode(address.getZipCode()) .build(); } diff --git a/java_codebase/order/src/main/java/com/yas/order/OrderApplication.java b/java_codebase/order/src/main/java/com/yas/order/OrderApplication.java index 9cebd06..28e7a58 100644 --- a/java_codebase/order/src/main/java/com/yas/order/OrderApplication.java +++ b/java_codebase/order/src/main/java/com/yas/order/OrderApplication.java @@ -5,11 +5,11 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; -@SpringBootApplication +@SpringBootApplication(scanBasePackages = {"com.yas.order", "com.yas.commonlibrary"}) @EnableConfigurationProperties(ServiceUrlConfig.class) public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); } -} +} \ No newline at end of file diff --git a/java_codebase/order/src/main/java/com/yas/order/config/ServiceUrlConfig.java b/java_codebase/order/src/main/java/com/yas/order/config/ServiceUrlConfig.java index 9c5c794..218f719 100644 --- a/java_codebase/order/src/main/java/com/yas/order/config/ServiceUrlConfig.java +++ b/java_codebase/order/src/main/java/com/yas/order/config/ServiceUrlConfig.java @@ -4,5 +4,5 @@ @ConfigurationProperties(prefix = "yas.services") public record ServiceUrlConfig( - String cart, String customer, String product, String tax, String promotion) { -} + String cart, String customer, String product, String tax, String promotion, String payment) { +} \ No newline at end of file diff --git a/java_codebase/order/src/main/java/com/yas/order/controller/CheckoutController.java b/java_codebase/order/src/main/java/com/yas/order/controller/CheckoutController.java index fff00f7..2df5f39 100644 --- a/java_codebase/order/src/main/java/com/yas/order/controller/CheckoutController.java +++ b/java_codebase/order/src/main/java/com/yas/order/controller/CheckoutController.java @@ -3,6 +3,7 @@ import com.yas.commonlibrary.constants.ApiConstant; import com.yas.order.service.CheckoutService; import com.yas.order.viewmodel.ErrorVm; +import com.yas.order.viewmodel.checkout.CheckoutPatchVm; import com.yas.order.viewmodel.checkout.CheckoutPaymentMethodPutVm; import com.yas.order.viewmodel.checkout.CheckoutPostVm; import com.yas.order.viewmodel.checkout.CheckoutStatusPutVm; @@ -12,9 +13,11 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import jakarta.validation.Valid; +import java.util.Map; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; @@ -56,4 +59,18 @@ public ResponseEntity updatePaymentMethod(@PathVariable final String id, checkoutService.updateCheckoutPaymentMethod(id, checkoutPaymentMethodPutVm); return ResponseEntity.ok().build(); } + + @PatchMapping("/storefront/checkouts/{id}") + @ApiResponses(value = { + @ApiResponse(responseCode = ApiConstant.CODE_200, description = ApiConstant.OK, + content = @Content()), + @ApiResponse(responseCode = ApiConstant.CODE_404, description = ApiConstant.NOT_FOUND, + content = @Content(schema = @Schema(implementation = ErrorVm.class))), + @ApiResponse(responseCode = ApiConstant.CODE_400, description = ApiConstant.BAD_REQUEST, + content = @Content(schema = @Schema(implementation = ErrorVm.class)))}) + public ResponseEntity updateCheckout(@PathVariable final String id, + @Valid @RequestBody + CheckoutPatchVm checkoutPatchVm) { + return ResponseEntity.ok(checkoutService.updateCheckoutByFields(id, checkoutPatchVm)); + } } \ No newline at end of file diff --git a/java_codebase/order/src/main/java/com/yas/order/mapper/AddressMapper.java b/java_codebase/order/src/main/java/com/yas/order/mapper/AddressMapper.java new file mode 100644 index 0000000..8fe3506 --- /dev/null +++ b/java_codebase/order/src/main/java/com/yas/order/mapper/AddressMapper.java @@ -0,0 +1,20 @@ +package com.yas.order.mapper; + +import com.yas.order.model.CheckoutAddress; +import com.yas.order.viewmodel.customer.ActiveAddressVm; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.springframework.stereotype.Component; + +@Mapper(componentModel = "spring") +@Component +public interface AddressMapper { + @Mapping(target = "id", ignore = true) + CheckoutAddress toModel(ActiveAddressVm activeAddressVm); + + ActiveAddressVm toVm(CheckoutAddress checkoutAddress); + + @Mapping(target = "id", ignore = true) + CheckoutAddress updateModel(@MappingTarget CheckoutAddress checkoutAddress, ActiveAddressVm activeAddressVm); +} \ No newline at end of file diff --git a/java_codebase/order/src/main/java/com/yas/order/mapper/CheckoutMapper.java b/java_codebase/order/src/main/java/com/yas/order/mapper/CheckoutMapper.java index 59dbd11..d4240ee 100644 --- a/java_codebase/order/src/main/java/com/yas/order/mapper/CheckoutMapper.java +++ b/java_codebase/order/src/main/java/com/yas/order/mapper/CheckoutMapper.java @@ -27,9 +27,10 @@ public interface CheckoutMapper { CheckoutItemVm toVm(CheckoutItem checkoutItem); @Mapping(target = "checkoutItemVms", ignore = true) + @Mapping(target = "shippingAddressDetail", ignore = true) CheckoutVm toVm(Checkout checkout); default BigDecimal map(BigDecimal value) { return value != null ? value : BigDecimal.ZERO; } -} +} \ No newline at end of file diff --git a/java_codebase/order/src/main/java/com/yas/order/model/Checkout.java b/java_codebase/order/src/main/java/com/yas/order/model/Checkout.java index 48942fa..a86ce61 100644 --- a/java_codebase/order/src/main/java/com/yas/order/model/Checkout.java +++ b/java_codebase/order/src/main/java/com/yas/order/model/Checkout.java @@ -8,10 +8,12 @@ import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.OneToMany; +import jakarta.persistence.OneToOne; import jakarta.persistence.Table; import java.math.BigDecimal; import java.util.ArrayList; @@ -57,12 +59,18 @@ public class Checkout extends AbstractAuditEntity { @SuppressWarnings("unused") private String shipmentMethodId; + @SuppressWarnings("unused") + private String shipmentServiceId; + @Column(name = "payment_method_id") private String paymentMethodId; @SuppressWarnings("unused") private Long shippingAddressId; + @OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST) + private CheckoutAddress checkoutAddress; + @SuppressWarnings("unused") @JdbcTypeCode(SqlTypes.JSON) @Column(name = "last_error", columnDefinition = "jsonb") diff --git a/java_codebase/order/src/main/java/com/yas/order/model/CheckoutAddress.java b/java_codebase/order/src/main/java/com/yas/order/model/CheckoutAddress.java new file mode 100644 index 0000000..c1ca3f4 --- /dev/null +++ b/java_codebase/order/src/main/java/com/yas/order/model/CheckoutAddress.java @@ -0,0 +1,82 @@ +package com.yas.order.model; + +import com.fasterxml.jackson.annotation.JsonBackReference; +import com.yas.order.viewmodel.enumeration.CheckoutAddressType; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToOne; +import jakarta.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Entity +@Table(name = "checkout_address") +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class CheckoutAddress{ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @SuppressWarnings("unused") + private String contactName; + + @SuppressWarnings("unused") + private String phone; + + @SuppressWarnings("unused") + private String addressLine1; + + @SuppressWarnings("unused") + private String city; + + @SuppressWarnings("unused") + private String zipCode; + + @SuppressWarnings("unused") + private Long districtId; + + @SuppressWarnings("unused") + private String districtName; + + @SuppressWarnings("unused") + private Long stateOrProvinceId; + + @SuppressWarnings("unused") + private String stateOrProvinceName; + + @SuppressWarnings("unused") + private String stateOrProvinceCode; + + @SuppressWarnings("unused") + private Long countryId; + + @SuppressWarnings("unused") + private String countryName; + + @SuppressWarnings("unused") + private String countryCode2; + + @SuppressWarnings("unused") + private String countryCode3; + + @Enumerated(EnumType.STRING) + private CheckoutAddressType type; + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "checkout_id", updatable = false, nullable = false) + @JsonBackReference + private Checkout checkout; +} \ No newline at end of file diff --git a/java_codebase/order/src/main/java/com/yas/order/model/CheckoutItem.java b/java_codebase/order/src/main/java/com/yas/order/model/CheckoutItem.java index 84a775c..305d2b2 100644 --- a/java_codebase/order/src/main/java/com/yas/order/model/CheckoutItem.java +++ b/java_codebase/order/src/main/java/com/yas/order/model/CheckoutItem.java @@ -1,6 +1,7 @@ package com.yas.order.model; import com.fasterxml.jackson.annotation.JsonBackReference; +import com.yas.order.viewmodel.enumeration.DimensionUnit; import com.yas.commonlibrary.model.AbstractAuditEntity; import jakarta.persistence.Column; @@ -12,7 +13,6 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; -import jakarta.validation.constraints.Positive; import java.math.BigDecimal; import lombok.AllArgsConstructor; import lombok.Builder; @@ -62,6 +62,24 @@ public class CheckoutItem extends AbstractAuditEntity { @JsonBackReference private Checkout checkout; + @SuppressWarnings("unused") + private Long taxClassId; + + @SuppressWarnings("unused") + private Double weight; + + @SuppressWarnings("unused") + private DimensionUnit dimensionUnit; + + @SuppressWarnings("unused") + private Double length; + + @SuppressWarnings("unused") + private Double width; + + @SuppressWarnings("unused") + private Double height; + @Override public boolean equals(Object o) { if (this == o) { diff --git a/java_codebase/order/src/main/java/com/yas/order/repository/CheckoutAddressRepository.java b/java_codebase/order/src/main/java/com/yas/order/repository/CheckoutAddressRepository.java new file mode 100644 index 0000000..e9f5837 --- /dev/null +++ b/java_codebase/order/src/main/java/com/yas/order/repository/CheckoutAddressRepository.java @@ -0,0 +1,13 @@ +package com.yas.order.repository; + +import com.yas.order.model.CheckoutAddress; +import com.yas.order.viewmodel.enumeration.CheckoutAddressType; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface CheckoutAddressRepository extends JpaRepository { + @Query("SELECT ca FROM CheckoutAddress ca WHERE ca.checkout.id = :checkoutId AND ca.type = :type") + Optional findByCheckoutIdAndType(@Param("checkoutId") String checkoutId, @Param("type") CheckoutAddressType type); +} \ No newline at end of file diff --git a/java_codebase/order/src/main/java/com/yas/order/service/CheckoutService.java b/java_codebase/order/src/main/java/com/yas/order/service/CheckoutService.java index fe8d369..1bf04a0 100644 --- a/java_codebase/order/src/main/java/com/yas/order/service/CheckoutService.java +++ b/java_codebase/order/src/main/java/com/yas/order/service/CheckoutService.java @@ -1,29 +1,50 @@ package com.yas.order.service; +import static com.yas.order.utils.Constants.ErrorCode.ADDRESS_NOT_AVAILABLE; +import static com.yas.order.utils.Constants.ErrorCode.CHECKOUT_IS_EXPIRED; import static com.yas.order.utils.Constants.ErrorCode.CHECKOUT_NOT_FOUND; +import static com.yas.order.utils.Constants.ErrorCode.PAYMENT_NOT_AVAILABLE; import com.yas.commonlibrary.constants.ApiConstant; import com.yas.commonlibrary.constants.MessageCode; +import com.yas.commonlibrary.exception.BadRequestException; import com.yas.commonlibrary.exception.ForbiddenException; import com.yas.commonlibrary.exception.NotFoundException; import com.yas.commonlibrary.utils.AuthenticationUtils; +import com.yas.order.mapper.AddressMapper; import com.yas.order.mapper.CheckoutMapper; import com.yas.order.model.Checkout; +import com.yas.order.model.CheckoutAddress; import com.yas.order.model.CheckoutItem; import com.yas.order.model.Order; import com.yas.order.model.enumeration.CheckoutState; +import com.yas.order.repository.CheckoutAddressRepository; import com.yas.order.repository.CheckoutRepository; +import com.yas.order.repository.OrderAddressRepository; import com.yas.order.utils.Constants; import com.yas.order.viewmodel.checkout.CheckoutItemPostVm; import com.yas.order.viewmodel.checkout.CheckoutItemVm; +import com.yas.order.viewmodel.checkout.CheckoutPatchVm; import com.yas.order.viewmodel.checkout.CheckoutPaymentMethodPutVm; import com.yas.order.viewmodel.checkout.CheckoutPostVm; import com.yas.order.viewmodel.checkout.CheckoutStatusPutVm; import com.yas.order.viewmodel.checkout.CheckoutVm; +import com.yas.order.viewmodel.customer.ActiveAddressVm; +import com.yas.order.viewmodel.enumeration.CheckoutAddressType; +import com.yas.order.viewmodel.payment.PaymentProviderVm; import com.yas.order.viewmodel.product.ProductCheckoutListVm; +import com.yas.order.viewmodel.promotion.DiscountType; +import com.yas.order.viewmodel.promotion.PromotionVerifyResultDto; +import com.yas.order.viewmodel.promotion.PromotionVerifyVm; +import com.yas.order.viewmodel.tax.TaxRateVm; import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.Duration; +import java.time.ZonedDateTime; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; @@ -37,11 +58,17 @@ @Transactional @RequiredArgsConstructor public class CheckoutService { - + private final CheckoutAddressRepository checkoutAddressRepository; private final CheckoutRepository checkoutRepository; private final OrderService orderService; private final ProductService productService; + private final PromotionService promotionService; + private final PaymentService paymentService; + private final CustomerService customerService; + private final ShipmentProviderService shipmentProviderService; + private final TaxService taxService; private final CheckoutMapper checkoutMapper; + private final AddressMapper addressMapper; /** * Creates a new {@link Checkout} object in a PENDING state. @@ -58,6 +85,7 @@ public CheckoutVm createCheckout(CheckoutPostVm checkoutPostVm) { checkout = checkoutRepository.save(checkout); CheckoutVm checkoutVm = checkoutMapper.toVm(checkout); + Set checkoutItemVms = checkout.getCheckoutItems() .stream() .map(checkoutMapper::toVm) @@ -65,6 +93,7 @@ public CheckoutVm createCheckout(CheckoutPostVm checkoutPostVm) { log.info(Constants.MessageCode.CREATE_CHECKOUT, checkout.getId(), checkout.getCustomerId()); return checkoutVm.toBuilder() .checkoutItemVms(checkoutItemVms) + .shippingAddressDetail(addressMapper.toVm(checkout.getCheckoutAddress())) .build(); } @@ -104,6 +133,12 @@ private List enrichCheckoutItemsWithProductDetails( } return item.toBuilder() .productName(product.getName()) + .taxClassId(product.getTaxClassId()) + .height(product.getHeight()) + .width(product.getWidth()) + .length(product.getLength()) + .weight(product.getWeight()) + .dimensionUnit(product.getDimensionUnit()) .productPrice(BigDecimal.valueOf(product.getPrice())) .build(); }).toList(); @@ -166,4 +201,211 @@ public void updateCheckoutPaymentMethod(String id, CheckoutPaymentMethodPutVm ch private boolean isNotOwnedByCurrentUser(Checkout checkout) { return !checkout.getCreatedBy().equals(AuthenticationUtils.extractUserId()); } + + public CheckoutVm updateCheckoutByFields(String id, CheckoutPatchVm checkoutPatchVm) { + Checkout existingCheckout = checkoutRepository.findById(id) + .orElseThrow(() -> new NotFoundException(CHECKOUT_NOT_FOUND, id)); + + if (isNotOwnedByCurrentUser(existingCheckout)) { + throw new ForbiddenException(ApiConstant.FORBIDDEN, "You are not authorized to update this checkout"); + } + + validateExpiredCheckout(existingCheckout); + updateAndValidateCheckout(existingCheckout, checkoutPatchVm); + reCalculateCheckoutAmount(existingCheckout); + + CheckoutVm checkoutVm = checkoutMapper.toVm(checkoutRepository.save(existingCheckout)); + Set checkoutItemVms = existingCheckout.getCheckoutItems() + .stream() + .map(checkoutMapper::toVm) + .collect(Collectors.toSet()); + return checkoutVm.toBuilder() + .checkoutItemVms(checkoutItemVms) + .shippingAddressDetail(addressMapper.toVm(existingCheckout.getCheckoutAddress())) + .build(); + } + + private void validateExpiredCheckout(Checkout existingCheckout) { + ZonedDateTime now = ZonedDateTime.now(); + Duration duration = Duration.between(existingCheckout.getCreatedOn(), now); + if (duration.toSeconds() > 3600) { + throw new BadRequestException(CHECKOUT_IS_EXPIRED); + } + } + + private void updateAndValidateCheckout(Checkout existingCheckout, CheckoutPatchVm checkoutPatchVm) { + if (checkoutPatchVm.paymentMethodId() != null) { + updatePaymentMethod(existingCheckout, checkoutPatchVm.paymentMethodId()); + } + if (checkoutPatchVm.shippingAddressId() != null) { + updateAddress(existingCheckout, checkoutPatchVm.shippingAddressId()); + } + if (checkoutPatchVm.shipmentMethodId() != null || existingCheckout.getShipmentMethodId() != null) { + String value = checkoutPatchVm.shipmentMethodId() != null + ? checkoutPatchVm.shipmentMethodId() + : existingCheckout.getShipmentMethodId(); + updateShipmentProvider(existingCheckout, value); + } + if (checkoutPatchVm.promotionCode() != null || existingCheckout.getPromotionCode() != null) { + String promoCode = checkoutPatchVm.promotionCode() != null + ? checkoutPatchVm.promotionCode() + : existingCheckout.getPromotionCode(); + updatePromotionCode(existingCheckout, promoCode); + } + } + + public void updatePaymentMethod(Checkout existingCheckout, String value) { + Optional paymentProvider = paymentService.getPaymentProviders() + .stream().filter(payment -> Objects.equals(payment.id(), value)) + .findFirst(); + if (paymentProvider.isEmpty()) { + throw new NotFoundException(PAYMENT_NOT_AVAILABLE, value); + } + existingCheckout.setPaymentMethodId(value); + } + + public void updateAddress(Checkout existingCheckout, String value) { + Optional address = customerService.getUserAddresses() + .stream().filter(add -> Objects.equals(add.id(), Long.valueOf(value))) + .findFirst(); + if (address.isEmpty()) { + throw new NotFoundException(ADDRESS_NOT_AVAILABLE, value); + } + + existingCheckout.setShippingAddressId(Long.parseLong(value)); + + CheckoutAddress existedCheckoutAddress = checkoutAddressRepository + .findByCheckoutIdAndType(existingCheckout.getId(), CheckoutAddressType.SHIPPING) + .map(checkoutAddress -> addressMapper.updateModel(checkoutAddress, address.get())) + .orElseGet(() -> { + CheckoutAddress checkoutAddress = addressMapper.toModel(address.get()); + checkoutAddress.setCheckout(existingCheckout); + return checkoutAddress; + }); + + existedCheckoutAddress.setType(CheckoutAddressType.SHIPPING); + existingCheckout.setCheckoutAddress(existedCheckoutAddress); + + List taxClassIds = existingCheckout.getCheckoutItems() + .stream() + .map(CheckoutItem::getTaxClassId) + .collect(Collectors.toSet()) + .stream().toList(); + + updateTax(existingCheckout, taxClassIds); + } + + private void updateTax(Checkout existingCheckout, List taxClassId) { + List taxRateVmList = taxService.getTaxRate(taxClassId, + existingCheckout.getCheckoutAddress().getCountryId(), + existingCheckout.getCheckoutAddress().getStateOrProvinceId(), + existingCheckout.getCheckoutAddress().getZipCode()); + + existingCheckout.getCheckoutItems().forEach(item -> + taxRateVmList.forEach(tax -> { + if (Objects.equals(item.getTaxClassId(), tax.taxClassId())) { + item.setTaxAmount(calculateTaxAmount(tax.rate(), item.getProductPrice())); + } + }) + ); + } + + // This function is using mock data + public void updateShipmentProvider(Checkout existingCheckout, String value) { + if (shipmentProviderService.checkShipmentProviderAvailable(value)) { + existingCheckout.setShipmentMethodId(value); + } + if (existingCheckout.getShippingAddressId() != null) { + // Mock data + existingCheckout.getCheckoutItems().forEach(item -> { + item.setShipmentFee(new BigDecimal(5000)); + item.setShipmentTax(new BigDecimal(500)); + }); + } + } + + public void updatePromotionCode(Checkout existingCheckout, String value) { + if (Objects.equals(value, "")) { + existingCheckout.getCheckoutItems().forEach(item -> item.setDiscountAmount(BigDecimal.ZERO)); + existingCheckout.setPromotionCode(null); + return; + } + + List productIds = existingCheckout.getCheckoutItems() + .stream() + .map(CheckoutItem::getProductId) + .collect(Collectors.toSet()) + .stream().toList(); + + PromotionVerifyVm promotionVerifyVm = PromotionVerifyVm + .builder() + .orderPrice(existingCheckout.getTotalAmount().longValue()) + .couponCode(value) + .productIds(productIds) + .build(); + + PromotionVerifyResultDto promotion = promotionService.validateCouponCode(promotionVerifyVm); + + existingCheckout.getCheckoutItems().forEach(item -> { + if (Objects.equals(item.getProductId(), promotion.productId())) { + BigDecimal discount = DiscountType.FIXED.equals(promotion.discountType()) + ? BigDecimal.valueOf(promotion.discountValue()) + : calculateDiscount(promotion.discountValue(), item.getProductPrice()); + item.setDiscountAmount(discount); + existingCheckout.setPromotionCode(value); + } + }); + } + + private void reCalculateCheckoutAmount(Checkout existingCheckout) { + BigDecimal totalShipmentFee = BigDecimal.ZERO; + BigDecimal totalShipmentTax = BigDecimal.ZERO; + BigDecimal totalTax = BigDecimal.ZERO; + BigDecimal totalDiscountAmount = BigDecimal.ZERO; + BigDecimal totalProductAmount = BigDecimal.ZERO; + + for (CheckoutItem item : existingCheckout.getCheckoutItems()) { + int quantity = item.getQuantity(); + BigDecimal quantityAsBigDecimal = BigDecimal.valueOf(quantity); + + totalShipmentFee = totalShipmentFee.add(safeValue(item.getShipmentFee())); + totalShipmentTax = totalShipmentTax.add(safeValue(item.getShipmentTax())); + totalTax = totalTax.add(safeValue(item.getTaxAmount()).multiply(quantityAsBigDecimal)); + totalDiscountAmount = totalDiscountAmount.add(safeValue(item.getDiscountAmount()).multiply(quantityAsBigDecimal)); + totalProductAmount = totalProductAmount.add(safeValue(item.getProductPrice()).multiply(quantityAsBigDecimal)); + } + + BigDecimal totalAmount = totalProductAmount + .add(totalShipmentFee) + .add(totalShipmentTax) + .add(totalTax) + .subtract(totalDiscountAmount); + + existingCheckout.setTotalShipmentFee(totalShipmentFee); + existingCheckout.setTotalShipmentTax(totalShipmentTax); + existingCheckout.setTotalTax(totalTax); + existingCheckout.setTotalDiscountAmount(totalDiscountAmount); + existingCheckout.setTotalAmount(totalAmount); + } + + public BigDecimal calculateDiscount(Long discountValue, BigDecimal productPrice) { + BigDecimal valueDecimal = BigDecimal.valueOf(discountValue); + BigDecimal discountPercentage = BigDecimal.valueOf(100); + + return valueDecimal + .multiply(productPrice) + .divide(discountPercentage, 2, RoundingMode.HALF_UP); + } + + public BigDecimal calculateTaxAmount(Double taxRate, BigDecimal productPrice) { + BigDecimal taxRateDecimal = BigDecimal.valueOf(taxRate); + + return taxRateDecimal + .multiply(productPrice) + .divide(BigDecimal.valueOf(100), RoundingMode.HALF_UP); + } + + private BigDecimal safeValue(BigDecimal value) { + return value != null ? value : BigDecimal.ZERO; + } } \ No newline at end of file diff --git a/java_codebase/order/src/main/java/com/yas/order/service/CustomerService.java b/java_codebase/order/src/main/java/com/yas/order/service/CustomerService.java index 1b9e413..2be3c08 100644 --- a/java_codebase/order/src/main/java/com/yas/order/service/CustomerService.java +++ b/java_codebase/order/src/main/java/com/yas/order/service/CustomerService.java @@ -1,11 +1,15 @@ package com.yas.order.service; import com.yas.order.config.ServiceUrlConfig; +import com.yas.order.viewmodel.customer.ActiveAddressVm; import com.yas.order.viewmodel.customer.CustomerVm; +import com.yas.order.viewmodel.orderaddress.OrderAddressVm; import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker; import io.github.resilience4j.retry.annotation.Retry; import java.net.URI; +import java.util.List; import lombok.RequiredArgsConstructor; +import org.springframework.core.ParameterizedTypeReference; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.stereotype.Service; @@ -38,4 +42,27 @@ public CustomerVm getCustomer() { protected CustomerVm handleCustomerFallback(Throwable throwable) throws Throwable { return handleTypedFallback(throwable); } -} + + @Retry(name = "restApi") + @CircuitBreaker(name = "restCircuitBreaker", fallbackMethod = "handleCustomerListFallback") + public List getUserAddresses() { + final String jwt = ((Jwt) SecurityContextHolder.getContext().getAuthentication().getPrincipal()) + .getTokenValue(); + final URI url = UriComponentsBuilder + .fromHttpUrl(serviceUrlConfig.customer()) + .path("/storefront/user-address") + .buildAndExpand() + .toUri(); + return restClient.get() + .uri(url) + .headers(h -> h.setBearerAuth(jwt)) + .retrieve() + .toEntity(new ParameterizedTypeReference>() { + }) + .getBody(); + } + + protected List handleCustomerListFallback(Throwable throwable) throws Throwable { + return handleTypedFallback(throwable); + } +} \ No newline at end of file diff --git a/java_codebase/order/src/main/java/com/yas/order/service/PaymentService.java b/java_codebase/order/src/main/java/com/yas/order/service/PaymentService.java new file mode 100644 index 0000000..205a70e --- /dev/null +++ b/java_codebase/order/src/main/java/com/yas/order/service/PaymentService.java @@ -0,0 +1,46 @@ +package com.yas.order.service; + +import com.yas.order.config.ServiceUrlConfig; +import com.yas.order.viewmodel.payment.PaymentProviderVm; +import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker; +import io.github.resilience4j.retry.annotation.Retry; +import java.net.URI; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestClient; +import org.springframework.web.util.UriComponentsBuilder; + +@Service +@RequiredArgsConstructor +public class PaymentService extends AbstractCircuitBreakFallbackHandler{ + private final RestClient restClient; + private final ServiceUrlConfig serviceUrlConfig; + + @Retry(name = "restApi") + @CircuitBreaker(name = "restCircuitBreaker", fallbackMethod = "handleListFallback") + public List getPaymentProviders() { + final String jwt = ((Jwt) SecurityContextHolder.getContext().getAuthentication().getPrincipal()) + .getTokenValue(); + final URI url = UriComponentsBuilder + .fromHttpUrl(serviceUrlConfig.payment()) + .path("/storefront/payment-providers") + .buildAndExpand() + .toUri(); + + return restClient.get() + .uri(url) + .headers(h -> h.setBearerAuth(jwt)) + .retrieve() + .toEntity(new ParameterizedTypeReference>() { + }) + .getBody(); + } + + protected List handleListFallback(Throwable throwable) throws Throwable { + return handleTypedFallback(throwable); + } +} \ No newline at end of file diff --git a/java_codebase/order/src/main/java/com/yas/order/service/PromotionService.java b/java_codebase/order/src/main/java/com/yas/order/service/PromotionService.java index c9db8e7..dcb91d8 100644 --- a/java_codebase/order/src/main/java/com/yas/order/service/PromotionService.java +++ b/java_codebase/order/src/main/java/com/yas/order/service/PromotionService.java @@ -1,12 +1,17 @@ package com.yas.order.service; +import com.yas.commonlibrary.exception.BadRequestException; import com.yas.order.config.ServiceUrlConfig; import com.yas.order.viewmodel.promotion.PromotionUsageVm; +import com.yas.order.viewmodel.promotion.PromotionVerifyResultDto; +import com.yas.order.viewmodel.promotion.PromotionVerifyVm; import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker; import io.github.resilience4j.retry.annotation.Retry; import java.net.URI; import java.util.List; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatusCode; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.stereotype.Service; @@ -14,6 +19,7 @@ import org.springframework.web.util.UriComponentsBuilder; +@Slf4j @Service @RequiredArgsConstructor public class PromotionService extends AbstractCircuitBreakFallbackHandler { @@ -37,4 +43,37 @@ public void updateUsagePromotion(List promotionUsageVms) { .body(promotionUsageVms) .retrieve(); } -} + + @Retry(name = "restApi") + @CircuitBreaker(name = "restCircuitBreaker", fallbackMethod = "handleVerifyFallback") + public PromotionVerifyResultDto validateCouponCode(PromotionVerifyVm promotionVerifyVm) { + final String jwt = ((Jwt) SecurityContextHolder.getContext().getAuthentication().getPrincipal()) + .getTokenValue(); + final URI url = UriComponentsBuilder + .fromHttpUrl(serviceUrlConfig.promotion()) + .path("/storefront/promotions/verify") + .buildAndExpand() + .toUri(); + + PromotionVerifyResultDto promotionVerifyResultDto = restClient.post() + .uri(url) + .headers(h -> h.setBearerAuth(jwt)) + .body(promotionVerifyVm) + .retrieve() + .onStatus( + HttpStatusCode::isError, + (request, response) -> { + throw new BadRequestException("Failed to apply promotion code: {}", promotionVerifyVm.couponCode()); + } + ) + .body(PromotionVerifyResultDto.class); + + log.info("Promotion verify in service: {}", promotionVerifyResultDto); + + return promotionVerifyResultDto; + } + + protected PromotionVerifyResultDto handleVerifyFallback(Throwable throwable) throws Throwable { + return handleTypedFallback(throwable); + } +} \ No newline at end of file diff --git a/java_codebase/order/src/main/java/com/yas/order/service/ShipmentProviderService.java b/java_codebase/order/src/main/java/com/yas/order/service/ShipmentProviderService.java new file mode 100644 index 0000000..44eec59 --- /dev/null +++ b/java_codebase/order/src/main/java/com/yas/order/service/ShipmentProviderService.java @@ -0,0 +1,14 @@ +package com.yas.order.service; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +public class ShipmentProviderService { + public boolean checkShipmentProviderAvailable(String shipmentProviderId) { + // This is mock data + log.info("Get shipment Id: {}", shipmentProviderId); + return true; + } +} \ No newline at end of file diff --git a/java_codebase/order/src/main/java/com/yas/order/service/TaxService.java b/java_codebase/order/src/main/java/com/yas/order/service/TaxService.java index 385e4f6..efa3d18 100644 --- a/java_codebase/order/src/main/java/com/yas/order/service/TaxService.java +++ b/java_codebase/order/src/main/java/com/yas/order/service/TaxService.java @@ -1,10 +1,13 @@ package com.yas.order.service; import com.yas.order.config.ServiceUrlConfig; +import com.yas.order.viewmodel.tax.TaxRateVm; import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker; import io.github.resilience4j.retry.annotation.Retry; import java.net.URI; +import java.util.List; import lombok.RequiredArgsConstructor; +import org.springframework.core.ParameterizedTypeReference; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.stereotype.Service; @@ -40,4 +43,32 @@ public Double getTaxPercentByAddress(Long taxClassId, Long countryId, Long state protected Double handleDoubleFallback(Throwable throwable) throws Throwable { return handleTypedFallback(throwable); } -} + + @Retry(name = "restApi") + @CircuitBreaker(name = "restCircuitBreaker", fallbackMethod = "handleListFallback") + public List getTaxRate(List taxClassIds, Long countryId, Long stateOrProvinceId, String zipCode) { + final URI url = UriComponentsBuilder.fromHttpUrl(serviceUrlConfig.tax()) + .path("/backoffice/tax-rates/location-based-batch") + .queryParam("taxClassIds", taxClassIds) + .queryParam("countryId", countryId) + .queryParam("stateOrProvinceId", stateOrProvinceId) + .queryParam("zipCode", zipCode) + .build().toUri(); + + final String jwt = ((Jwt) SecurityContextHolder.getContext().getAuthentication().getPrincipal()) + .getTokenValue(); + + return webClient.get() + .uri(url) + .headers(h -> h.setBearerAuth(jwt)) + .retrieve() + .toEntity(new ParameterizedTypeReference>() { + }) + .getBody(); + + } + + protected List handleListFallback(Throwable throwable) throws Throwable { + return handleTypedFallback(throwable); + } +} \ No newline at end of file diff --git a/java_codebase/order/src/main/java/com/yas/order/utils/Constants.java b/java_codebase/order/src/main/java/com/yas/order/utils/Constants.java index 3d6c0e0..a861eb2 100644 --- a/java_codebase/order/src/main/java/com/yas/order/utils/Constants.java +++ b/java_codebase/order/src/main/java/com/yas/order/utils/Constants.java @@ -11,6 +11,10 @@ private ErrorCode() { public static final String CHECKOUT_NOT_FOUND = "CHECKOUT_NOT_FOUND"; public static final String CHECKOUT_ITEM_NOT_EMPTY = "CHECKOUT_ITEM_NOT_EMPTY"; public static final String SIGN_IN_REQUIRED = "SIGN_IN_REQUIRED"; + public static final String PAYMENT_NOT_AVAILABLE = "PAYMENT_NOT_AVAILABLE"; + public static final String ADDRESS_NOT_AVAILABLE = "ADDRESS_NOT_AVAILABLE"; + public static final String CHECKOUT_IS_EXPIRED = "CHECKOUT_IS_EXPIRED"; + public static final String CHECKOUT_UPDATE_ILLEGAL_FIELD = "CHECKOUT_UPDATE_ILLEGAL_FIELD"; } public final class MessageCode { @@ -47,4 +51,4 @@ private Column() { public static final String ORDER_ITEM_PRODUCT_NAME_COLUMN = "productName"; } -} +} \ No newline at end of file diff --git a/java_codebase/order/src/main/java/com/yas/order/viewmodel/checkout/CheckoutItemPostVm.java b/java_codebase/order/src/main/java/com/yas/order/viewmodel/checkout/CheckoutItemPostVm.java index dece67a..6152618 100644 --- a/java_codebase/order/src/main/java/com/yas/order/viewmodel/checkout/CheckoutItemPostVm.java +++ b/java_codebase/order/src/main/java/com/yas/order/viewmodel/checkout/CheckoutItemPostVm.java @@ -7,5 +7,4 @@ public record CheckoutItemPostVm( String description, @Positive int quantity) { - -} +} \ No newline at end of file diff --git a/java_codebase/order/src/main/java/com/yas/order/viewmodel/checkout/CheckoutItemVm.java b/java_codebase/order/src/main/java/com/yas/order/viewmodel/checkout/CheckoutItemVm.java index 1b43a5b..48084fa 100644 --- a/java_codebase/order/src/main/java/com/yas/order/viewmodel/checkout/CheckoutItemVm.java +++ b/java_codebase/order/src/main/java/com/yas/order/viewmodel/checkout/CheckoutItemVm.java @@ -1,9 +1,10 @@ package com.yas.order.viewmodel.checkout; +import com.yas.order.viewmodel.enumeration.DimensionUnit; import java.math.BigDecimal; import lombok.Builder; -@Builder +@Builder(toBuilder = true) public record CheckoutItemVm( Long id, Long productId, @@ -15,6 +16,11 @@ public record CheckoutItemVm( BigDecimal discountAmount, BigDecimal shipmentFee, BigDecimal shipmentTax, - String checkoutId) { - -} + String checkoutId, + Long taxClassId, + DimensionUnit dimensionUnit, + Double weight, + Double length, + Double width, + Double height) { +} \ No newline at end of file diff --git a/java_codebase/order/src/main/java/com/yas/order/viewmodel/checkout/CheckoutPatchVm.java b/java_codebase/order/src/main/java/com/yas/order/viewmodel/checkout/CheckoutPatchVm.java new file mode 100644 index 0000000..4f1d744 --- /dev/null +++ b/java_codebase/order/src/main/java/com/yas/order/viewmodel/checkout/CheckoutPatchVm.java @@ -0,0 +1,9 @@ +package com.yas.order.viewmodel.checkout; + +public record CheckoutPatchVm ( + String shipmentMethodId, + String paymentMethodId, + String shippingAddressId, + String promotionCode +) { +} \ No newline at end of file diff --git a/java_codebase/order/src/main/java/com/yas/order/viewmodel/checkout/CheckoutPostVm.java b/java_codebase/order/src/main/java/com/yas/order/viewmodel/checkout/CheckoutPostVm.java index 52d65cd..4d309f1 100644 --- a/java_codebase/order/src/main/java/com/yas/order/viewmodel/checkout/CheckoutPostVm.java +++ b/java_codebase/order/src/main/java/com/yas/order/viewmodel/checkout/CheckoutPostVm.java @@ -2,15 +2,17 @@ import jakarta.validation.constraints.NotEmpty; import java.util.List; +import lombok.Builder; +@Builder(toBuilder = true) public record CheckoutPostVm( String email, String note, String promotionCode, String shipmentMethodId, + String shipmentServiceId, String paymentMethodId, String shippingAddressId, @NotEmpty(message = "Checkout Items must not be empty") List checkoutItemPostVms) { - -} +} \ No newline at end of file diff --git a/java_codebase/order/src/main/java/com/yas/order/viewmodel/checkout/CheckoutVm.java b/java_codebase/order/src/main/java/com/yas/order/viewmodel/checkout/CheckoutVm.java index be3d3b4..a4ed8d2 100644 --- a/java_codebase/order/src/main/java/com/yas/order/viewmodel/checkout/CheckoutVm.java +++ b/java_codebase/order/src/main/java/com/yas/order/viewmodel/checkout/CheckoutVm.java @@ -1,6 +1,9 @@ package com.yas.order.viewmodel.checkout; +import com.yas.order.model.CheckoutAddress; import com.yas.order.model.enumeration.CheckoutState; +import com.yas.order.viewmodel.customer.ActiveAddressVm; +import com.yas.order.viewmodel.orderaddress.OrderAddressVm; import java.math.BigDecimal; import java.util.Set; import lombok.Builder; @@ -19,8 +22,9 @@ public record CheckoutVm( BigDecimal totalTax, BigDecimal totalDiscountAmount, String shipmentMethodId, + String shipmentServiceId, String paymentMethodId, Long shippingAddressId, + ActiveAddressVm shippingAddressDetail, Set checkoutItemVms) { - -} +} \ No newline at end of file diff --git a/java_codebase/order/src/main/java/com/yas/order/viewmodel/customer/ActiveAddressVm.java b/java_codebase/order/src/main/java/com/yas/order/viewmodel/customer/ActiveAddressVm.java new file mode 100644 index 0000000..dc47f27 --- /dev/null +++ b/java_codebase/order/src/main/java/com/yas/order/viewmodel/customer/ActiveAddressVm.java @@ -0,0 +1,21 @@ +package com.yas.order.viewmodel.customer; + +public record ActiveAddressVm( + Long id, + String contactName, + String phone, + String addressLine1, + String city, + String zipCode, + Long districtId, + String districtName, + Long stateOrProvinceId, + String stateOrProvinceName, + String stateOrProvinceCode, + Long countryId, + String countryName, + String countryCode2, + String countryCode3 +) { + +} \ No newline at end of file diff --git a/java_codebase/order/src/main/java/com/yas/order/viewmodel/enumeration/CheckoutAddressType.java b/java_codebase/order/src/main/java/com/yas/order/viewmodel/enumeration/CheckoutAddressType.java new file mode 100644 index 0000000..961b8bb --- /dev/null +++ b/java_codebase/order/src/main/java/com/yas/order/viewmodel/enumeration/CheckoutAddressType.java @@ -0,0 +1,15 @@ +package com.yas.order.viewmodel.enumeration; + +import lombok.Getter; + +@Getter +public enum CheckoutAddressType { + SHIPPING("shipping"), + BILLING("billing"); + + private final String name; + + CheckoutAddressType(String name) { + this.name = name; + } +} \ No newline at end of file diff --git a/java_codebase/order/src/main/java/com/yas/order/viewmodel/enumeration/DimensionUnit.java b/java_codebase/order/src/main/java/com/yas/order/viewmodel/enumeration/DimensionUnit.java new file mode 100644 index 0000000..d0f33e0 --- /dev/null +++ b/java_codebase/order/src/main/java/com/yas/order/viewmodel/enumeration/DimensionUnit.java @@ -0,0 +1,16 @@ +package com.yas.order.viewmodel.enumeration; + +import lombok.Getter; + +@Getter +public enum DimensionUnit { + CM("cm"), + INCH("inch"); + + private final String name; + + DimensionUnit(String name) { + this.name = name; + } + +} \ No newline at end of file diff --git a/java_codebase/order/src/main/java/com/yas/order/viewmodel/enumeration/DiscountType.java b/java_codebase/order/src/main/java/com/yas/order/viewmodel/enumeration/DiscountType.java new file mode 100644 index 0000000..5c92a28 --- /dev/null +++ b/java_codebase/order/src/main/java/com/yas/order/viewmodel/enumeration/DiscountType.java @@ -0,0 +1,5 @@ +package com.yas.order.viewmodel.enumeration; + +public enum DiscountType { + PERCENTAGE, FIXED; +} \ No newline at end of file diff --git a/java_codebase/order/src/main/java/com/yas/order/viewmodel/orderaddress/OrderAddressVm.java b/java_codebase/order/src/main/java/com/yas/order/viewmodel/orderaddress/OrderAddressVm.java index 3bec9e2..2554049 100644 --- a/java_codebase/order/src/main/java/com/yas/order/viewmodel/orderaddress/OrderAddressVm.java +++ b/java_codebase/order/src/main/java/com/yas/order/viewmodel/orderaddress/OrderAddressVm.java @@ -4,7 +4,7 @@ import lombok.Builder; -@Builder +@Builder(toBuilder = true) public record OrderAddressVm( Long id, String contactName, @@ -36,4 +36,4 @@ public static OrderAddressVm fromModel(OrderAddress orderAddress) { .countryName(orderAddress.getCountryName()) .build(); } -} +} \ No newline at end of file diff --git a/java_codebase/order/src/main/java/com/yas/order/viewmodel/payment/PaymentProviderVm.java b/java_codebase/order/src/main/java/com/yas/order/viewmodel/payment/PaymentProviderVm.java new file mode 100644 index 0000000..62cf6be --- /dev/null +++ b/java_codebase/order/src/main/java/com/yas/order/viewmodel/payment/PaymentProviderVm.java @@ -0,0 +1,12 @@ +package com.yas.order.viewmodel.payment; + +import lombok.Builder; + +@Builder(toBuilder = true) +public record PaymentProviderVm ( + String id, + String name, + String configureUrl, + String additionalSettings +) { +} \ No newline at end of file diff --git a/java_codebase/order/src/main/java/com/yas/order/viewmodel/product/ProductCheckoutListVm.java b/java_codebase/order/src/main/java/com/yas/order/viewmodel/product/ProductCheckoutListVm.java index d6b5bcf..cea72e5 100644 --- a/java_codebase/order/src/main/java/com/yas/order/viewmodel/product/ProductCheckoutListVm.java +++ b/java_codebase/order/src/main/java/com/yas/order/viewmodel/product/ProductCheckoutListVm.java @@ -1,5 +1,6 @@ package com.yas.order.viewmodel.product; +import com.yas.order.viewmodel.enumeration.DimensionUnit; import lombok.Builder; import lombok.Data; @@ -10,4 +11,9 @@ public class ProductCheckoutListVm { String name; Double price; Long taxClassId; + Double weight; + DimensionUnit dimensionUnit; + Double length; + Double width; + Double height; } \ No newline at end of file diff --git a/java_codebase/order/src/main/java/com/yas/order/viewmodel/promotion/DiscountType.java b/java_codebase/order/src/main/java/com/yas/order/viewmodel/promotion/DiscountType.java new file mode 100644 index 0000000..c80be6c --- /dev/null +++ b/java_codebase/order/src/main/java/com/yas/order/viewmodel/promotion/DiscountType.java @@ -0,0 +1,5 @@ +package com.yas.order.viewmodel.promotion; + +public enum DiscountType { + PERCENTAGE, FIXED; +} \ No newline at end of file diff --git a/java_codebase/order/src/main/java/com/yas/order/viewmodel/promotion/PromotionVerifyResultDto.java b/java_codebase/order/src/main/java/com/yas/order/viewmodel/promotion/PromotionVerifyResultDto.java new file mode 100644 index 0000000..e7f5d6c --- /dev/null +++ b/java_codebase/order/src/main/java/com/yas/order/viewmodel/promotion/PromotionVerifyResultDto.java @@ -0,0 +1,9 @@ +package com.yas.order.viewmodel.promotion; + +public record PromotionVerifyResultDto( + boolean isValid, + Long productId, + String couponCode, + DiscountType discountType, + Long discountValue) { +} \ No newline at end of file diff --git a/java_codebase/order/src/main/java/com/yas/order/viewmodel/promotion/PromotionVerifyVm.java b/java_codebase/order/src/main/java/com/yas/order/viewmodel/promotion/PromotionVerifyVm.java new file mode 100644 index 0000000..08b8297 --- /dev/null +++ b/java_codebase/order/src/main/java/com/yas/order/viewmodel/promotion/PromotionVerifyVm.java @@ -0,0 +1,12 @@ +package com.yas.order.viewmodel.promotion; + +import java.util.List; +import lombok.Builder; + +@Builder(toBuilder = true) +public record PromotionVerifyVm( + String couponCode, + Long orderPrice, + List productIds +) { +} \ No newline at end of file diff --git a/java_codebase/order/src/main/java/com/yas/order/viewmodel/tax/TaxRateVm.java b/java_codebase/order/src/main/java/com/yas/order/viewmodel/tax/TaxRateVm.java new file mode 100644 index 0000000..0f9ac17 --- /dev/null +++ b/java_codebase/order/src/main/java/com/yas/order/viewmodel/tax/TaxRateVm.java @@ -0,0 +1,5 @@ +package com.yas.order.viewmodel.tax; + +public record TaxRateVm(Long id, Double rate, String zipCode, Long taxClassId, Long stateOrProvinceId, Long countryId) { + +} \ No newline at end of file diff --git a/java_codebase/order/src/main/resources/db/changelog/ddl/changelog-0017.sql b/java_codebase/order/src/main/resources/db/changelog/ddl/changelog-0017.sql new file mode 100644 index 0000000..3a87cc0 --- /dev/null +++ b/java_codebase/order/src/main/resources/db/changelog/ddl/changelog-0017.sql @@ -0,0 +1,35 @@ +ALTER TABLE IF EXISTS "checkout" +ADD COLUMN checkout_address_id BIGINT, +ADD COLUMN shipment_service_id VARCHAR(255); + +ALTER TABLE IF EXISTS "checkout_item" +ADD COLUMN tax_class_id BIGINT, +ADD COLUMN weight DOUBLE PRECISION, +ADD COLUMN dimension_unit VARCHAR(50), +ADD COLUMN length DOUBLE PRECISION, +ADD COLUMN width DOUBLE PRECISION, +ADD COLUMN height DOUBLE PRECISION; + +CREATE TABLE IF NOT EXISTS "checkout_address" ( + id bigserial not null, + contact_name varchar(255) not null, + phone varchar(255) not null, + address_line1 varchar(255) not null, + city varchar(255), + zip_code varchar(255), + district_id bigserial, + district_name varchar(255), + state_or_province_id bigserial, + state_or_province_name varchar(255), + state_or_province_code varchar(10), + country_id bigserial, + country_name varchar(255), + country_code2 varchar(10), + country_code3 varchar(10), + type VARCHAR(50), + checkout_id VARCHAR(255), + PRIMARY KEY (id) +); + +alter table if exists "checkout" +ADD CONSTRAINT FK_CheckoutAddress FOREIGN KEY (checkout_address_id) REFERENCES checkout_address (id); \ No newline at end of file diff --git a/java_codebase/order/src/main/resources/messages/messages.properties b/java_codebase/order/src/main/resources/messages/messages.properties index f00590f..8388006 100644 --- a/java_codebase/order/src/main/resources/messages/messages.properties +++ b/java_codebase/order/src/main/resources/messages/messages.properties @@ -2,4 +2,8 @@ ORDER_NOT_FOUND=Order {} is not found CHECKOUT_NOT_FOUND=Checkout {} is not found SUCCESS_MESSAGE=Success SIGN_IN_REQUIRED=Authentication required -FORBIDDEN=You don't have permission to access this page \ No newline at end of file +FORBIDDEN=You don't have permission to access this page +PAYMENT_NOT_AVAILABLE=Payment {} is not available +ADDRESS_NOT_AVAILABLE=Address {} is not available +CHECKOUT_IS_EXPIRED=Check out is expired +CHECKOUT_UPDATE_ILLEGAL_FIELD=Checkout field is not allow to update \ No newline at end of file diff --git a/java_codebase/order/src/test/java/com/yas/order/controller/CheckoutControllerTest.java b/java_codebase/order/src/test/java/com/yas/order/controller/CheckoutControllerTest.java index 8987935..01e7b7a 100644 --- a/java_codebase/order/src/test/java/com/yas/order/controller/CheckoutControllerTest.java +++ b/java_codebase/order/src/test/java/com/yas/order/controller/CheckoutControllerTest.java @@ -65,13 +65,16 @@ void testCreateCheckout_whenRequestIsValid_thenReturnCheckoutVm() throws Excepti when(checkoutService.createCheckout(any(CheckoutPostVm.class))).thenReturn(response); List items = getCheckoutItemPostVms(); - CheckoutPostVm request = new CheckoutPostVm( - "customer@example.com", - "Please deliver before noon.", - "SUMMER2024", - null, null, null, - items - ); + CheckoutPostVm request = CheckoutPostVm.builder() + .email("customer@example.com") + .note("Please deliver before noon.") + .promotionCode("SUMMER2024") + .shipmentMethodId(null) + .shipmentServiceId(null) + .paymentMethodId(null) + .shippingAddressId(null) + .checkoutItemPostVms(items) + .build(); mockMvc.perform(post("/storefront/checkouts") .contentType(MediaType.APPLICATION_JSON) @@ -169,20 +172,24 @@ private CheckoutVm getCheckoutVm() { checkoutItemVms.add(item1); checkoutItemVms.add(item2); - return new CheckoutVm( - "014476b3-243a-4111-9f2a-a25661aea89c", - "user@example.com", - "Please deliver after 5 PM", - "DISCOUNT20", - CheckoutState.CHECKED_OUT, - "Inprogress", - BigDecimal.valueOf(900), - BigDecimal.ZERO, - BigDecimal.ZERO, - BigDecimal.ZERO, - BigDecimal.ZERO, - null, null, null, - checkoutItemVms - ); + return CheckoutVm.builder() + .id("014476b3-243a-4111-9f2a-a25661aea89c") + .email("user@example.com") + .note("Please deliver after 5 PM") + .promotionCode("DISCOUNT20") + .checkoutState(CheckoutState.CHECKED_OUT) + .progress("Inprogress") + .totalAmount(BigDecimal.valueOf(900)) + .totalShipmentFee(BigDecimal.ZERO) + .totalShipmentTax(BigDecimal.ZERO) + .totalTax(BigDecimal.ZERO) + .totalDiscountAmount(BigDecimal.ZERO) + .shipmentMethodId(null) + .shipmentServiceId(null) + .paymentMethodId(null) + .shippingAddressId(null) + .shippingAddressDetail(null) + .checkoutItemVms(checkoutItemVms) + .build(); } } \ No newline at end of file diff --git a/java_codebase/order/src/test/java/com/yas/order/service/CheckoutServiceTest.java b/java_codebase/order/src/test/java/com/yas/order/service/CheckoutServiceTest.java index d70d225..b6d2226 100644 --- a/java_codebase/order/src/test/java/com/yas/order/service/CheckoutServiceTest.java +++ b/java_codebase/order/src/test/java/com/yas/order/service/CheckoutServiceTest.java @@ -12,7 +12,6 @@ import com.yas.commonlibrary.exception.ForbiddenException; import com.yas.commonlibrary.exception.NotFoundException; -import com.yas.order.mapper.CheckoutMapperImpl; import com.yas.order.model.Checkout; import com.yas.order.model.CheckoutItem; import com.yas.order.model.enumeration.CheckoutState; @@ -45,7 +44,7 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; @ExtendWith(SpringExtension.class) -@SpringBootTest(classes = {CheckoutMapperImpl.class, CheckoutService.class}) +@SpringBootTest class CheckoutServiceTest { @MockBean diff --git a/java_codebase/product/src/main/java/com/yas/product/service/ProductService.java b/java_codebase/product/src/main/java/com/yas/product/service/ProductService.java index 0f36692..741f71f 100644 --- a/java_codebase/product/src/main/java/com/yas/product/service/ProductService.java +++ b/java_codebase/product/src/main/java/com/yas/product/service/ProductService.java @@ -1173,6 +1173,15 @@ public ProductGetCheckoutListVm getProductCheckoutList(int pageNo, int pageSize, List productCheckoutListVms = productPage.getContent() .stream().map(product -> { String thumbnailUrl = mediaService.getMedia(product.getThumbnailMediaId()).url(); + if (product.getParent() != null) { + product.setWeight(product.getParent().getWeight()); + product.setHeight(product.getParent().getHeight()); + product.setLength(product.getParent().getLength()); + product.setWidth(product.getParent().getWidth()); + product.setBrand(product.getParent().getBrand()); + product.setDimensionUnit(product.getParent().getDimensionUnit()); + product.setTaxClassId(product.getParent().getTaxClassId()); + } ProductCheckoutListVm productCheckoutListVm = ProductCheckoutListVm.fromModel(product); if (StringUtils.isNotEmpty(thumbnailUrl)) { return productCheckoutListVm.toBuilder().thumbnailUrl(thumbnailUrl).build(); @@ -1188,4 +1197,4 @@ public ProductGetCheckoutListVm getProductCheckoutList(int pageNo, int pageSize, productPage.isLast() ); } -} +} \ No newline at end of file diff --git a/java_codebase/product/src/main/java/com/yas/product/viewmodel/product/ProductCheckoutListVm.java b/java_codebase/product/src/main/java/com/yas/product/viewmodel/product/ProductCheckoutListVm.java index 75aa33e..65963a5 100644 --- a/java_codebase/product/src/main/java/com/yas/product/viewmodel/product/ProductCheckoutListVm.java +++ b/java_codebase/product/src/main/java/com/yas/product/viewmodel/product/ProductCheckoutListVm.java @@ -1,6 +1,7 @@ package com.yas.product.viewmodel.product; import com.yas.product.model.Product; +import com.yas.product.model.enumeration.DimensionUnit; import java.time.ZonedDateTime; import java.util.Objects; import lombok.Builder; @@ -19,7 +20,12 @@ public record ProductCheckoutListVm(Long id, ZonedDateTime createdOn, String createdBy, ZonedDateTime lastModifiedOn, - String lastModifiedBy) { + String lastModifiedBy, + Double weight, + DimensionUnit dimensionUnit, + Double length, + Double width, + Double height) { public static ProductCheckoutListVm fromModel(Product product) { return new ProductCheckoutListVm( product.getId(), @@ -35,7 +41,12 @@ public static ProductCheckoutListVm fromModel(Product product) { product.getCreatedOn(), product.getCreatedBy(), product.getLastModifiedOn(), - product.getLastModifiedBy() + product.getLastModifiedBy(), + product.getWeight(), + product.getDimensionUnit(), + product.getLength(), + product.getWidth(), + product.getHeight() ); } -} +} \ No newline at end of file