Skip to content

Commit 3517d31

Browse files
authored
feat: collection mapper introduced (#13)
- Offloaded custom logic to collection voucher mapper from collection voucher service - Added request validation with jakarka - GSON dependency removed - Minor contract changes for pending-bill / collection voucher --------- Signed-off-by: Rajdeep Roy Chowdhury <[email protected]>
1 parent 5e03eaa commit 3517d31

File tree

12 files changed

+200
-178
lines changed

12 files changed

+200
-178
lines changed

build.gradle.kts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ dependencies {
4141
implementation(libs.jaxb.api)
4242
implementation(libs.spring.boot.starter.data.jpa)
4343
implementation(libs.postgres.connector)
44-
implementation(libs.gson)
4544
implementation(libs.spring.boot.starter.freemarker)
4645
implementation(libs.openhtml)
4746
implementation(libs.spring.boot.starter.validation)

gradle/libs.versions.toml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ springboot = "3.0.6"
33
jjwt = "0.9.1"
44
jaxb = "2.4.0-b180830.0359"
55
postgres = "42.7.8"
6-
gson = "2.10"
76
lombok = "1.18.30" # put actual version you use
87
lombokmapstruct = "0.2.0"
98
mapstruct = "1.6.3"
@@ -29,7 +28,6 @@ spring-boot-starter-validation = { module = "org.springframework.boot:spring-boo
2928
jjwt = { module = "io.jsonwebtoken:jjwt", version.ref = "jjwt" }
3029
jaxb-api = { module = "javax.xml.bind:jaxb-api", version.ref = "jaxb" }
3130
postgres-connector = { module = "org.postgresql:postgresql", version.ref = "postgres" }
32-
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
3331
lombok = { module = "org.projectlombok:lombok", version.ref = "lombok" }
3432
lombok-mapstruct-binding = { module = "org.projectlombok:lombok-mapstruct-binding", version.ref = "lombokmapstruct" }
3533

src/main/java/com/razdeep/konsignapi/KonsignApiApplication.java

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
import org.springframework.boot.autoconfigure.SpringBootApplication;
77
import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration;
88
import org.springframework.cache.annotation.EnableCaching;
9-
import org.springframework.context.annotation.Bean;
10-
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
119

1210
@SpringBootApplication(exclude = {UserDetailsServiceAutoConfiguration.class})
1311
@EnableCaching
@@ -18,11 +16,6 @@ public static void main(String[] args) {
1816
SpringApplication.run(KonsignApiApplication.class, args);
1917
}
2018

21-
@Bean
22-
public BCryptPasswordEncoder bCryptPasswordEncoder() {
23-
return new BCryptPasswordEncoder();
24-
}
25-
2619
@Override
2720
public void run(String... args) {}
2821
}

src/main/java/com/razdeep/konsignapi/config/SecurityConfig.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
1414
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
1515
import org.springframework.security.config.http.SessionCreationPolicy;
16+
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
1617
import org.springframework.security.web.SecurityFilterChain;
1718
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
1819
import org.springframework.web.cors.CorsConfiguration;
@@ -49,6 +50,11 @@ public AuthenticationManager authenticationManager(AuthenticationConfiguration a
4950
return authenticationConfiguration.getAuthenticationManager();
5051
}
5152

53+
@Bean
54+
public BCryptPasswordEncoder bCryptPasswordEncoder() {
55+
return new BCryptPasswordEncoder();
56+
}
57+
5258
@Bean
5359
public CorsConfigurationSource corsConfigurationSource() {
5460
CorsConfiguration configuration = new CorsConfiguration();
Lines changed: 31 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,110 +1,67 @@
11
package com.razdeep.konsignapi.controller;
22

3-
import com.google.gson.Gson;
43
import com.razdeep.konsignapi.constant.KonsignConstant;
5-
import com.razdeep.konsignapi.entity.BuyerEntity;
64
import com.razdeep.konsignapi.model.CollectionVoucher;
5+
import com.razdeep.konsignapi.model.KonsignApiResponse;
76
import com.razdeep.konsignapi.model.PendingBill;
8-
import com.razdeep.konsignapi.service.BuyerService;
97
import com.razdeep.konsignapi.service.CollectionVoucherService;
108
import io.micrometer.core.annotation.Timed;
11-
import java.util.HashMap;
9+
import jakarta.validation.Valid;
1210
import java.util.List;
13-
import java.util.Map;
14-
import org.springframework.http.HttpStatus;
15-
import org.springframework.http.MediaType;
11+
import org.apache.coyote.BadRequestException;
1612
import org.springframework.http.ResponseEntity;
1713
import org.springframework.web.bind.annotation.*;
1814

1915
@RestController
2016
@RequestMapping(KonsignConstant.CONTROLLER_API_PREFIX + "/collection-vouchers")
2117
public class CollectionVoucherController {
2218

23-
private final Gson gson;
24-
2519
private final CollectionVoucherService collectionVoucherService;
26-
private final BuyerService buyerService;
2720

28-
public CollectionVoucherController(
29-
Gson gson, CollectionVoucherService collectionVoucherService, BuyerService buyerService) {
30-
this.gson = gson;
21+
public CollectionVoucherController(CollectionVoucherService collectionVoucherService) {
3122
this.collectionVoucherService = collectionVoucherService;
32-
this.buyerService = buyerService;
3323
}
3424

3525
@Timed
3626
@GetMapping("/{voucherNo}")
37-
public ResponseEntity<String> getCollectionVoucher(@PathVariable String voucherNo) {
38-
ResponseEntity<String> response;
27+
public ResponseEntity<KonsignApiResponse> getCollectionVoucher(@PathVariable String voucherNo) {
3928
CollectionVoucher collectionVoucher = collectionVoucherService.getVoucherByVoucherNo(voucherNo);
40-
if (collectionVoucher == null) {
41-
return new ResponseEntity<>("{}", HttpStatus.NOT_FOUND);
42-
}
43-
44-
response = new ResponseEntity<>(gson.toJson(collectionVoucher), HttpStatus.OK);
45-
return response;
29+
return ResponseEntity.ok(KonsignApiResponse.builder()
30+
.data(collectionVoucher)
31+
.success(true)
32+
.build());
4633
}
4734

4835
@Timed
4936
@PostMapping
50-
public ResponseEntity<String> addCollectionVoucher(@RequestBody CollectionVoucher collectionVoucher) {
51-
Map<String, String> body = new HashMap<>();
52-
ResponseEntity<String> response;
53-
if (collectionVoucherService.addCollectionVoucher(collectionVoucher)) {
54-
body.put("message", "Successfully added collection voucher");
55-
response = new ResponseEntity<>(gson.toJson(body), HttpStatus.OK);
56-
} else {
57-
body.put("message", "Saving collection voucher failed");
58-
response = new ResponseEntity<>(gson.toJson(body), HttpStatus.NOT_ACCEPTABLE);
59-
}
60-
return response;
37+
public ResponseEntity<KonsignApiResponse> addCollectionVoucher(
38+
@Valid @RequestBody CollectionVoucher collectionVoucher) {
39+
collectionVoucherService.addCollectionVoucher(collectionVoucher);
40+
return ResponseEntity.ok(KonsignApiResponse.builder()
41+
.success(true)
42+
.message("Successfully added collection voucher")
43+
.build());
6144
}
6245

6346
@Timed
6447
@DeleteMapping("/{voucherNo}")
65-
ResponseEntity<String> deleteBuyer(@PathVariable String voucherNo) {
66-
String message;
67-
if (collectionVoucherService.deleteVoucher(voucherNo)) {
68-
message = "Successfully deleted Collection Voucher Id: " + voucherNo;
69-
} else {
70-
message = voucherNo + " is already deleted";
71-
}
72-
Map<String, String> responseMap = new HashMap<>();
73-
responseMap.put("message", message);
74-
return new ResponseEntity<>(gson.toJson(responseMap), HttpStatus.OK);
48+
ResponseEntity<KonsignApiResponse> deleteBuyer(@PathVariable String voucherNo) {
49+
String message = collectionVoucherService.deleteVoucher(voucherNo)
50+
? "Successfully deleted Collection Voucher Id: " + voucherNo
51+
: voucherNo + " is already deleted";
52+
53+
return ResponseEntity.ok(
54+
KonsignApiResponse.builder().message(message).success(true).build());
7555
}
7656

7757
@Timed
78-
@GetMapping(value = "/pending-bills", produces = MediaType.APPLICATION_JSON_VALUE)
79-
ResponseEntity<Map<String, Object>> getPendingBillsToBeCollected(
80-
@RequestParam(required = false) String buyerId, @RequestParam(required = false) String buyerName) {
81-
List<PendingBill> pendingBills;
82-
if (buyerId != null && !buyerId.isEmpty()) {
83-
pendingBills = collectionVoucherService.getPendingBillsToBeCollected(buyerId);
84-
} else if (buyerName != null && !buyerName.isEmpty()) {
85-
BuyerEntity retrievedBuyerEntity = buyerService.getBuyerByBuyerName(buyerName);
86-
if (retrievedBuyerEntity == null) {
87-
String message = "Buyer name not found in database";
88-
Map<String, Object> responseMap = new HashMap<>();
89-
responseMap.put("message", message);
90-
return new ResponseEntity<>(responseMap, HttpStatus.BAD_REQUEST);
91-
}
92-
String retriedBuyerId = retrievedBuyerEntity.getBuyerId();
93-
if (retriedBuyerId == null) {
94-
String message = "Buyer name not found in database";
95-
Map<String, Object> responseMap = new HashMap<>();
96-
responseMap.put("message", message);
97-
return new ResponseEntity<>(responseMap, HttpStatus.BAD_REQUEST);
98-
}
99-
pendingBills = collectionVoucherService.getPendingBillsToBeCollected(retriedBuyerId);
100-
} else {
101-
String message = "Either buyerId or buyerName must be present the request param";
102-
Map<String, Object> responseMap = new HashMap<>();
103-
responseMap.put("message", message);
104-
return new ResponseEntity<>(responseMap, HttpStatus.BAD_REQUEST);
105-
}
106-
Map<String, Object> responseMap = new HashMap<>();
107-
responseMap.put("pendingBills", pendingBills);
108-
return new ResponseEntity<>(responseMap, HttpStatus.OK);
58+
@GetMapping(value = "/pending-bills")
59+
ResponseEntity<KonsignApiResponse> getPendingBillsToBeCollected(
60+
@RequestParam(required = false) String buyerId, @RequestParam(required = false) String buyerName)
61+
throws BadRequestException {
62+
List<PendingBill> pendingBills = collectionVoucherService.getPendingBillsToBeCollected(buyerId, buyerName);
63+
64+
return ResponseEntity.ok(
65+
KonsignApiResponse.builder().success(true).data(pendingBills).build());
10966
}
11067
}

src/main/java/com/razdeep/konsignapi/exception/GlobalExceptionHandler.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.razdeep.konsignapi.model.KonsignApiResponse;
44
import java.util.ArrayList;
55
import java.util.List;
6+
import org.apache.coyote.BadRequestException;
67
import org.slf4j.Logger;
78
import org.slf4j.LoggerFactory;
89
import org.springframework.http.HttpStatus;
@@ -91,4 +92,12 @@ public ResponseEntity<KonsignApiResponse> handleValidationErrors(SaveResourceExc
9192

9293
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new KonsignApiResponse(false, ex.getMessage(), null));
9394
}
95+
96+
@ExceptionHandler(BadRequestException.class)
97+
public ResponseEntity<KonsignApiResponse> handleValidationErrors(BadRequestException ex) {
98+
99+
LOG.info("Bad request exception", ex);
100+
101+
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new KonsignApiResponse(false, ex.getMessage(), null));
102+
}
94103
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.razdeep.konsignapi.mapper;
2+
3+
import com.razdeep.konsignapi.entity.CollectionVoucherItemEntity;
4+
import com.razdeep.konsignapi.model.CollectionVoucherItem;
5+
import java.util.List;
6+
import org.mapstruct.Mapper;
7+
import org.mapstruct.Mapping;
8+
9+
@Mapper(
10+
componentModel = "spring",
11+
uses = {LrPmMapper.class})
12+
public interface CollectionVoucherItemMapper {
13+
14+
@Mapping(source = "bill.billNo", target = "billNo")
15+
@Mapping(source = "bill.supplierEntity.supplierName", target = "supplierName")
16+
@Mapping(source = "bill.billAmount", target = "billAmount")
17+
CollectionVoucherItem toModel(CollectionVoucherItemEntity entity);
18+
19+
List<CollectionVoucherItem> toModelList(List<CollectionVoucherItemEntity> entities);
20+
21+
CollectionVoucherItemEntity toEntity(CollectionVoucherItem model);
22+
23+
List<CollectionVoucherItemEntity> toEntityList(List<CollectionVoucherItem> model);
24+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package com.razdeep.konsignapi.mapper;
2+
3+
import com.razdeep.konsignapi.entity.CollectionVoucherEntity;
4+
import com.razdeep.konsignapi.model.CollectionVoucher;
5+
import com.razdeep.konsignapi.tenant.TenantContext;
6+
import java.util.concurrent.atomic.AtomicInteger;
7+
import org.mapstruct.AfterMapping;
8+
import org.mapstruct.Mapper;
9+
import org.mapstruct.Mapping;
10+
import org.mapstruct.MappingTarget;
11+
12+
@Mapper(
13+
componentModel = "spring",
14+
uses = {CollectionVoucherItemMapper.class})
15+
public interface CollectionVoucherMapper {
16+
17+
@Mapping(source = "buyer.buyerName", target = "buyerName")
18+
@Mapping(source = "collectionVoucherItemEntityList", target = "collectionVoucherItemList")
19+
CollectionVoucher toModel(CollectionVoucherEntity entity);
20+
21+
@Mapping(source = "collectionVoucherItemList", target = "collectionVoucherItemEntityList")
22+
CollectionVoucherEntity toEntity(CollectionVoucher model);
23+
24+
@AfterMapping
25+
default void linkChildren(@MappingTarget CollectionVoucherEntity collectionVoucherEntity) {
26+
27+
// TODO make sure the tenant id is set using an EntityListener
28+
collectionVoucherEntity.setTenantId(TenantContext.getTenantId());
29+
30+
if (collectionVoucherEntity.getCollectionVoucherItemEntityList() == null) return;
31+
32+
AtomicInteger collectionVoucherItemIndex = new AtomicInteger();
33+
collectionVoucherEntity.getCollectionVoucherItemEntityList().forEach(item -> {
34+
item.setCollectionVoucher(collectionVoucherEntity);
35+
item.setCollectionVoucherItemId(
36+
collectionVoucherEntity.getVoucherNo() + "_" + collectionVoucherItemIndex.getAndIncrement());
37+
item.setTenantId(TenantContext.getTenantId());
38+
});
39+
}
40+
}
Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,33 @@
11
package com.razdeep.konsignapi.model;
22

3+
import jakarta.validation.Valid;
4+
import jakarta.validation.constraints.NotBlank;
5+
import jakarta.validation.constraints.NotEmpty;
6+
import jakarta.validation.constraints.NotNull;
7+
import jakarta.validation.constraints.Size;
8+
import java.time.LocalDate;
39
import java.util.List;
410
import lombok.Builder;
5-
import lombok.Data;
11+
import lombok.Getter;
12+
import lombok.Setter;
613

7-
@Data
14+
@Getter
15+
@Setter
816
@Builder
917
public class CollectionVoucher {
1018

19+
@NotBlank(message = "Voucher number must not be blank")
20+
@Size(max = 10, message = "Voucher number must be at most 10 characters")
1121
private String voucherNo;
1222

13-
// TODO find a way to use LocalDate here
14-
// @JsonFormat(pattern="yyyy-MM-dd")
15-
private String voucherDate;
23+
@NotNull(message = "Voucher date is required")
24+
private LocalDate voucherDate;
1625

26+
@NotBlank(message = "Buyer name must not be blank")
27+
@Size(max = 30, message = "Buyer name must be at most 30 characters")
1728
private String buyerName;
1829

30+
@NotEmpty(message = "At least one voucher item is required")
31+
@Valid
1932
private List<CollectionVoucherItem> collectionVoucherItemList;
2033
}
Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,41 @@
11
package com.razdeep.konsignapi.model;
22

3+
import jakarta.validation.constraints.*;
34
import java.math.BigDecimal;
5+
import java.time.LocalDate;
46
import lombok.Builder;
5-
import lombok.Data;
7+
import lombok.Getter;
8+
import lombok.Setter;
69

7-
@Data
10+
@Setter
11+
@Getter
812
@Builder
913
public class CollectionVoucherItem {
1014

11-
String billNo;
15+
@NotBlank(message = "Bill number must not be blank")
16+
@Size(max = 10, message = "Bill number must be at most 10 characters")
17+
private String billNo;
1218

13-
String supplierName;
19+
@NotBlank(message = "Supplier name must not be blank")
20+
@Size(max = 30, message = "Supplier name must be at most 30 characters")
21+
private String supplierName;
1422

15-
BigDecimal billAmount;
23+
@NotNull(message = "Bill amount is required")
24+
@Positive(message = "Bill amount must be greater than zero")
25+
private BigDecimal billAmount;
1626

17-
BigDecimal pendingBillAmount;
27+
private BigDecimal pendingBillAmount;
1828

19-
BigDecimal amountCollected;
29+
@NotNull(message = "Collected amount is required")
30+
@PositiveOrZero(message = "Collected amount cannot be negative")
31+
private BigDecimal amountCollected;
2032

21-
String bank;
33+
@Size(max = 10, message = "Bank name must be at most 10 characters")
34+
private String bank;
2235

23-
String ddNo;
36+
@Size(max = 10, message = "DD number must be at most 10 characters")
37+
private String ddNo;
2438

25-
// @JsonFormat(pattern="yyyy-MM-dd")
26-
String ddDate;
39+
@NotNull(message = "dd date is required")
40+
private LocalDate ddDate;
2741
}

0 commit comments

Comments
 (0)