From 506d8a8e4129869438062183c3f73604b519d704 Mon Sep 17 00:00:00 2001 From: Vusumuzi Khoza Date: Mon, 27 Oct 2025 23:18:47 +0200 Subject: [PATCH] improving logical flow of transactions & onboarding --- .../controllers/OnboardingController.java | 4 +- .../tm/domain/dtos/RegisterRequest.java | 1 + .../qantra/tm/domain/models/BaseEntity.java | 2 +- .../java/zw/qantra/tm/domain/models/User.java | 2 + .../repositories/TransactionRepository.java | 2 + .../domain/services/TransactionService.java | 7 +++ .../tm/domain/services/UserService.java | 10 ++++ .../ZesaConfirmationProcessor.java | 4 +- .../workflows/RegistrationWorkflow.java | 54 ++++++++++++------- 9 files changed, 61 insertions(+), 25 deletions(-) diff --git a/src/main/java/zw/qantra/tm/domain/controllers/OnboardingController.java b/src/main/java/zw/qantra/tm/domain/controllers/OnboardingController.java index e92cdef..484c816 100644 --- a/src/main/java/zw/qantra/tm/domain/controllers/OnboardingController.java +++ b/src/main/java/zw/qantra/tm/domain/controllers/OnboardingController.java @@ -89,7 +89,7 @@ public class OnboardingController { .setId(request.getWorkflowId()) .setState("verifyOtp") // The state you want to transition to .setNextActivation(DateTime.now()) // Schedule immediate execution - .setStateVariables(new HashMap<>(){{ put("body", Utils.toJson(request)); }}) + .setStateVariables(new HashMap<>(){{ put("verifyRequest", Utils.toJson(request)); }}) .setStateText("Resumed from manual state") .build(); @@ -117,7 +117,7 @@ public class OnboardingController { .setId(otpRequest.getWorkflowId()) .setState("resendOtp") // The state you want to transition to .setNextActivation(DateTime.now()) // Schedule immediate execution - .setStateVariables(new HashMap<>(){{ put("body", Utils.toJson(otpRequest)); }}) + .setStateVariables(new HashMap<>(){{ put("resend", Utils.toJson(otpRequest)); }}) .setStateText("Resumed from manual state") .build(); diff --git a/src/main/java/zw/qantra/tm/domain/dtos/RegisterRequest.java b/src/main/java/zw/qantra/tm/domain/dtos/RegisterRequest.java index 037f34b..92601e9 100644 --- a/src/main/java/zw/qantra/tm/domain/dtos/RegisterRequest.java +++ b/src/main/java/zw/qantra/tm/domain/dtos/RegisterRequest.java @@ -13,6 +13,7 @@ public class RegisterRequest { private String password; private String photoUrl; private Long workflowId; + private String tempUid; @NotBlank(message = "Email is required") @Email(message = "Email should be valid") diff --git a/src/main/java/zw/qantra/tm/domain/models/BaseEntity.java b/src/main/java/zw/qantra/tm/domain/models/BaseEntity.java index 6b5202a..7d99302 100644 --- a/src/main/java/zw/qantra/tm/domain/models/BaseEntity.java +++ b/src/main/java/zw/qantra/tm/domain/models/BaseEntity.java @@ -41,5 +41,5 @@ public class BaseEntity { private LocalDateTime updatedAt; // Or Date, Timestamp @Column(columnDefinition = "BOOLEAN DEFAULT FALSE") - private Boolean deleted; + private Boolean deleted = false; } diff --git a/src/main/java/zw/qantra/tm/domain/models/User.java b/src/main/java/zw/qantra/tm/domain/models/User.java index 5047e81..62d8313 100644 --- a/src/main/java/zw/qantra/tm/domain/models/User.java +++ b/src/main/java/zw/qantra/tm/domain/models/User.java @@ -3,6 +3,7 @@ package zw.qantra.tm.domain.models; import jakarta.persistence.*; import lombok.Data; import lombok.EqualsAndHashCode; +import org.hibernate.annotations.SQLRestriction; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; @@ -14,6 +15,7 @@ import java.util.List; @Table(name = "users") @Data @EqualsAndHashCode(callSuper = true) +@SQLRestriction("deleted = false") public class User extends BaseEntity implements UserDetails { @Column(unique = true, nullable = false) diff --git a/src/main/java/zw/qantra/tm/domain/repositories/TransactionRepository.java b/src/main/java/zw/qantra/tm/domain/repositories/TransactionRepository.java index 7c5b4ff..c81e709 100644 --- a/src/main/java/zw/qantra/tm/domain/repositories/TransactionRepository.java +++ b/src/main/java/zw/qantra/tm/domain/repositories/TransactionRepository.java @@ -7,6 +7,7 @@ import org.springframework.stereotype.Repository; import zw.qantra.tm.domain.enums.RequestType; import zw.qantra.tm.domain.models.Transaction; +import java.util.List; import java.util.Optional; import java.util.UUID; @@ -14,4 +15,5 @@ import java.util.UUID; public interface TransactionRepository extends JpaRepository, JpaSpecificationExecutor { Transaction findByTraceAndType(String trace, RequestType type); Optional findByReference(String reference); + List findAllByUserId(String uid); } \ No newline at end of file diff --git a/src/main/java/zw/qantra/tm/domain/services/TransactionService.java b/src/main/java/zw/qantra/tm/domain/services/TransactionService.java index a5d3d09..217b21e 100644 --- a/src/main/java/zw/qantra/tm/domain/services/TransactionService.java +++ b/src/main/java/zw/qantra/tm/domain/services/TransactionService.java @@ -29,6 +29,13 @@ public class TransactionService { private final TransactionRepository transactionRepository; private final AdditionalDataService additionalDataService; + public void reassignTransactions(String oldUid, String newUid){ + List transactions = transactionRepository.findAllByUserId(oldUid); + transactions.forEach(transaction -> { + transaction.setUserId(newUid); + transactionRepository.save(transaction); + }); + } public Transaction findById(UUID id) { Transaction transaction = transactionRepository.findById(id).orElseThrow(() -> new ApiException("Transaction not found for ID: " + id)); diff --git a/src/main/java/zw/qantra/tm/domain/services/UserService.java b/src/main/java/zw/qantra/tm/domain/services/UserService.java index 7199859..ee94972 100644 --- a/src/main/java/zw/qantra/tm/domain/services/UserService.java +++ b/src/main/java/zw/qantra/tm/domain/services/UserService.java @@ -22,6 +22,7 @@ public class UserService implements UserDetailsService { private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; private final JwtUtils jwtUtils; + private final TransactionService transactionService; public User fetchUserByUsername(String username) { return userRepository.findByUsername(username) @@ -52,10 +53,19 @@ public class UserService implements UserDetailsService { user.setPhone(request.getPhone()); user.setWorkflowId(request.getWorkflowId()); user.setPhotoUrl(request.getPhotoUrl()); + user.setDeleted(false); User savedUser = userRepository.save(user); String token = jwtUtils.generateToken(savedUser); + // reassign anonymous transactions to the new user + // if we fail we move on, not a critical step + try { + transactionService.reassignTransactions(request.getTempUid(), savedUser.getId().toString()); + }catch (Exception e) { + e.printStackTrace(); + } + return new AuthResponse(token, "Bearer", savedUser.getUsername(), savedUser.getEmail(), savedUser.getPhone(), savedUser.getFirstName(), savedUser.getLastName(), savedUser.getId()); } diff --git a/src/main/java/zw/qantra/tm/domain/services/processors/confirmations/ZesaConfirmationProcessor.java b/src/main/java/zw/qantra/tm/domain/services/processors/confirmations/ZesaConfirmationProcessor.java index 36baee6..2fdce1d 100644 --- a/src/main/java/zw/qantra/tm/domain/services/processors/confirmations/ZesaConfirmationProcessor.java +++ b/src/main/java/zw/qantra/tm/domain/services/processors/confirmations/ZesaConfirmationProcessor.java @@ -66,7 +66,7 @@ public class ZesaConfirmationProcessor implements TransactionProcessorInterface transaction.setStatus(Status.SUCCESS); transaction.setResponseCode((String) body.get("responseCode")); transaction.setAdditionalData(body.get("additionalData")); - transactionService.save(transaction); + transaction.setCreditPhone(transaction.getDebitPhone()); AdditionalData additionalData = AdditionalData.builder() .transactionId(transaction.getId().toString()) @@ -81,7 +81,7 @@ public class ZesaConfirmationProcessor implements TransactionProcessorInterface transaction.setErrorMessage((String) body.get("errorMessage")); } - return transactionService.save(transaction); + return transaction; } @Override diff --git a/src/main/java/zw/qantra/tm/domain/workflows/RegistrationWorkflow.java b/src/main/java/zw/qantra/tm/domain/workflows/RegistrationWorkflow.java index efef450..79ed96c 100644 --- a/src/main/java/zw/qantra/tm/domain/workflows/RegistrationWorkflow.java +++ b/src/main/java/zw/qantra/tm/domain/workflows/RegistrationWorkflow.java @@ -30,10 +30,7 @@ import zw.qantra.tm.rest.RestService; import org.springframework.http.HttpHeaders; import java.nio.charset.StandardCharsets; -import java.util.Base64; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Set; +import java.util.*; @Component public class RegistrationWorkflow extends WorkflowDefinition { @@ -151,7 +148,7 @@ public class RegistrationWorkflow extends WorkflowDefinition { } public NextAction verifyOtp(StateExecution execution) { - VerifyRequest payload = execution.getVariable("body", VerifyRequest.class); + VerifyRequest payload = execution.getVariable("verifyRequest", VerifyRequest.class); boolean otpValid = otpService.verifyOtp(payload.getUsername(), payload.getOtpCode(), payload.getOtpType()); if(!otpValid) { @@ -162,7 +159,7 @@ public class RegistrationWorkflow extends WorkflowDefinition { } public void resendOtp(StateExecution execution) { - OtpRequest otpRequest = execution.getVariable("body", OtpRequest.class); + OtpRequest otpRequest = execution.getVariable("resend", OtpRequest.class); Otp otp = otpService.generateOtp(otpRequest.getUsername(), "REGISTRATION"); String string = String.format("Your Peak verification code is: %s", otp.getOtp()); emailService.sendSimpleMessage(otpRequest.getUsername(), "Peak verification code", string); @@ -174,7 +171,7 @@ public class RegistrationWorkflow extends WorkflowDefinition { log.info("OTP verified successfully"); log.info("Begin user registration"); - VerifyRequest request = execution.getVariable("body", VerifyRequest.class); + VerifyRequest request = execution.getVariable("verifyRequest", VerifyRequest.class); try { RegisterRequest registerRequest = execution.getVariable("registerRequest", RegisterRequest.class); @@ -220,19 +217,36 @@ public class RegistrationWorkflow extends WorkflowDefinition { headers.add("Authorization", basicAuth); headers.add("Cookie", sid); - CustomerDto customerDto = CustomerDto.builder() - .doctype("Customer") - .customerName(registerRequest.getFirstName() + " " + registerRequest.getLastName()) - .customerGroup("Commercial") - .territory("All Territories") - .customerType("Individual") - .language("en") - .mobileNo(registerRequest.getPhone()) - .emailId(registerRequest.getEmail()) - .docstatus(1) - .build(); - + // check if customer exists first + String customerName = registerRequest.getFirstName() + " " + registerRequest.getLastName(); try { + LinkedHashMap checkResponse = restService.getAs(erpnextUrl + "/v2/document/Customer" + + "?fields=[\"name\"]&filters=[[\"name\", \"=\", " + + "\"" + customerName + "\"]]", headers, LinkedHashMap.class); + List dataList = (List) checkResponse.get("data"); + + if(!dataList.isEmpty()){ + User user = userService.fetchUserByUsername(registerRequest.getUsername()); + user.setErpCustomerRef(customerName); + userService.getUserRepository().save(user); + + return NextAction.stopInState(DONE, "Customer already exists in ERP. Ignoring update: " + + registerRequest.getUsername()); + } + + // if not proceed + CustomerDto customerDto = CustomerDto.builder() + .doctype("Customer") + .customerName(customerName) + .customerGroup("Commercial") + .territory("All Territories") + .customerType("Individual") + .language("en") + .mobileNo(registerRequest.getPhone()) + .emailId(registerRequest.getEmail()) + .docstatus(1) + .build(); + LinkedHashMap response = restService.postAs( erpnextUrl + "/v2/document/Customer", customerDto, headers, LinkedHashMap.class); @@ -245,7 +259,7 @@ public class RegistrationWorkflow extends WorkflowDefinition { } catch (Exception e) { e.printStackTrace(); - return NextAction.retryAfter(DateTime.now().plusSeconds(30), "Failed to update ERP: " + e.getMessage()); + return NextAction.retryAfter(DateTime.now().plusMinutes(5), "Failed to update ERP: " + e.getMessage()); } return NextAction.stopInState(DONE, "ERP updated successfully for: " + registerRequest.getUsername());