improving logical flow of transactions & onboarding

This commit is contained in:
2025-10-27 23:18:47 +02:00
parent b5e891afed
commit 506d8a8e41
9 changed files with 61 additions and 25 deletions

View File

@@ -89,7 +89,7 @@ public class OnboardingController {
.setId(request.getWorkflowId()) .setId(request.getWorkflowId())
.setState("verifyOtp") // The state you want to transition to .setState("verifyOtp") // The state you want to transition to
.setNextActivation(DateTime.now()) // Schedule immediate execution .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") .setStateText("Resumed from manual state")
.build(); .build();
@@ -117,7 +117,7 @@ public class OnboardingController {
.setId(otpRequest.getWorkflowId()) .setId(otpRequest.getWorkflowId())
.setState("resendOtp") // The state you want to transition to .setState("resendOtp") // The state you want to transition to
.setNextActivation(DateTime.now()) // Schedule immediate execution .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") .setStateText("Resumed from manual state")
.build(); .build();

View File

@@ -13,6 +13,7 @@ public class RegisterRequest {
private String password; private String password;
private String photoUrl; private String photoUrl;
private Long workflowId; private Long workflowId;
private String tempUid;
@NotBlank(message = "Email is required") @NotBlank(message = "Email is required")
@Email(message = "Email should be valid") @Email(message = "Email should be valid")

View File

@@ -41,5 +41,5 @@ public class BaseEntity {
private LocalDateTime updatedAt; // Or Date, Timestamp private LocalDateTime updatedAt; // Or Date, Timestamp
@Column(columnDefinition = "BOOLEAN DEFAULT FALSE") @Column(columnDefinition = "BOOLEAN DEFAULT FALSE")
private Boolean deleted; private Boolean deleted = false;
} }

View File

@@ -3,6 +3,7 @@ package zw.qantra.tm.domain.models;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.hibernate.annotations.SQLRestriction;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
@@ -14,6 +15,7 @@ import java.util.List;
@Table(name = "users") @Table(name = "users")
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@SQLRestriction("deleted = false")
public class User extends BaseEntity implements UserDetails { public class User extends BaseEntity implements UserDetails {
@Column(unique = true, nullable = false) @Column(unique = true, nullable = false)

View File

@@ -7,6 +7,7 @@ import org.springframework.stereotype.Repository;
import zw.qantra.tm.domain.enums.RequestType; import zw.qantra.tm.domain.enums.RequestType;
import zw.qantra.tm.domain.models.Transaction; import zw.qantra.tm.domain.models.Transaction;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
@@ -14,4 +15,5 @@ import java.util.UUID;
public interface TransactionRepository extends JpaRepository<Transaction, UUID>, JpaSpecificationExecutor<Transaction> { public interface TransactionRepository extends JpaRepository<Transaction, UUID>, JpaSpecificationExecutor<Transaction> {
Transaction findByTraceAndType(String trace, RequestType type); Transaction findByTraceAndType(String trace, RequestType type);
Optional<Transaction> findByReference(String reference); Optional<Transaction> findByReference(String reference);
List<Transaction> findAllByUserId(String uid);
} }

View File

@@ -29,6 +29,13 @@ public class TransactionService {
private final TransactionRepository transactionRepository; private final TransactionRepository transactionRepository;
private final AdditionalDataService additionalDataService; private final AdditionalDataService additionalDataService;
public void reassignTransactions(String oldUid, String newUid){
List<Transaction> transactions = transactionRepository.findAllByUserId(oldUid);
transactions.forEach(transaction -> {
transaction.setUserId(newUid);
transactionRepository.save(transaction);
});
}
public Transaction findById(UUID id) { public Transaction findById(UUID id) {
Transaction transaction = transactionRepository.findById(id).orElseThrow(() -> new ApiException("Transaction not found for ID: " + id)); Transaction transaction = transactionRepository.findById(id).orElseThrow(() -> new ApiException("Transaction not found for ID: " + id));

View File

@@ -22,6 +22,7 @@ public class UserService implements UserDetailsService {
private final UserRepository userRepository; private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder; private final PasswordEncoder passwordEncoder;
private final JwtUtils jwtUtils; private final JwtUtils jwtUtils;
private final TransactionService transactionService;
public User fetchUserByUsername(String username) { public User fetchUserByUsername(String username) {
return userRepository.findByUsername(username) return userRepository.findByUsername(username)
@@ -52,10 +53,19 @@ public class UserService implements UserDetailsService {
user.setPhone(request.getPhone()); user.setPhone(request.getPhone());
user.setWorkflowId(request.getWorkflowId()); user.setWorkflowId(request.getWorkflowId());
user.setPhotoUrl(request.getPhotoUrl()); user.setPhotoUrl(request.getPhotoUrl());
user.setDeleted(false);
User savedUser = userRepository.save(user); User savedUser = userRepository.save(user);
String token = jwtUtils.generateToken(savedUser); 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(), return new AuthResponse(token, "Bearer", savedUser.getUsername(),
savedUser.getEmail(), savedUser.getPhone(), savedUser.getFirstName(), savedUser.getLastName(), savedUser.getId()); savedUser.getEmail(), savedUser.getPhone(), savedUser.getFirstName(), savedUser.getLastName(), savedUser.getId());
} }

View File

@@ -66,7 +66,7 @@ public class ZesaConfirmationProcessor implements TransactionProcessorInterface
transaction.setStatus(Status.SUCCESS); transaction.setStatus(Status.SUCCESS);
transaction.setResponseCode((String) body.get("responseCode")); transaction.setResponseCode((String) body.get("responseCode"));
transaction.setAdditionalData(body.get("additionalData")); transaction.setAdditionalData(body.get("additionalData"));
transactionService.save(transaction); transaction.setCreditPhone(transaction.getDebitPhone());
AdditionalData additionalData = AdditionalData.builder() AdditionalData additionalData = AdditionalData.builder()
.transactionId(transaction.getId().toString()) .transactionId(transaction.getId().toString())
@@ -81,7 +81,7 @@ public class ZesaConfirmationProcessor implements TransactionProcessorInterface
transaction.setErrorMessage((String) body.get("errorMessage")); transaction.setErrorMessage((String) body.get("errorMessage"));
} }
return transactionService.save(transaction); return transaction;
} }
@Override @Override

View File

@@ -30,10 +30,7 @@ import zw.qantra.tm.rest.RestService;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Base64; import java.util.*;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
@Component @Component
public class RegistrationWorkflow extends WorkflowDefinition { public class RegistrationWorkflow extends WorkflowDefinition {
@@ -151,7 +148,7 @@ public class RegistrationWorkflow extends WorkflowDefinition {
} }
public NextAction verifyOtp(StateExecution execution) { 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()); boolean otpValid = otpService.verifyOtp(payload.getUsername(), payload.getOtpCode(), payload.getOtpType());
if(!otpValid) { if(!otpValid) {
@@ -162,7 +159,7 @@ public class RegistrationWorkflow extends WorkflowDefinition {
} }
public void resendOtp(StateExecution execution) { 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"); Otp otp = otpService.generateOtp(otpRequest.getUsername(), "REGISTRATION");
String string = String.format("Your Peak verification code is: %s", otp.getOtp()); String string = String.format("Your Peak verification code is: %s", otp.getOtp());
emailService.sendSimpleMessage(otpRequest.getUsername(), "Peak verification code", string); emailService.sendSimpleMessage(otpRequest.getUsername(), "Peak verification code", string);
@@ -174,7 +171,7 @@ public class RegistrationWorkflow extends WorkflowDefinition {
log.info("OTP verified successfully"); log.info("OTP verified successfully");
log.info("Begin user registration"); log.info("Begin user registration");
VerifyRequest request = execution.getVariable("body", VerifyRequest.class); VerifyRequest request = execution.getVariable("verifyRequest", VerifyRequest.class);
try { try {
RegisterRequest registerRequest = execution.getVariable("registerRequest", RegisterRequest.class); RegisterRequest registerRequest = execution.getVariable("registerRequest", RegisterRequest.class);
@@ -220,19 +217,36 @@ public class RegistrationWorkflow extends WorkflowDefinition {
headers.add("Authorization", basicAuth); headers.add("Authorization", basicAuth);
headers.add("Cookie", sid); headers.add("Cookie", sid);
CustomerDto customerDto = CustomerDto.builder() // check if customer exists first
.doctype("Customer") String customerName = registerRequest.getFirstName() + " " + registerRequest.getLastName();
.customerName(registerRequest.getFirstName() + " " + registerRequest.getLastName())
.customerGroup("Commercial")
.territory("All Territories")
.customerType("Individual")
.language("en")
.mobileNo(registerRequest.getPhone())
.emailId(registerRequest.getEmail())
.docstatus(1)
.build();
try { try {
LinkedHashMap checkResponse = restService.getAs(erpnextUrl + "/v2/document/Customer" +
"?fields=[\"name\"]&filters=[[\"name\", \"=\", " +
"\"" + customerName + "\"]]", headers, LinkedHashMap.class);
List<LinkedHashMap> 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( LinkedHashMap response = restService.postAs(
erpnextUrl + "/v2/document/Customer", customerDto, headers, LinkedHashMap.class); erpnextUrl + "/v2/document/Customer", customerDto, headers, LinkedHashMap.class);
@@ -245,7 +259,7 @@ public class RegistrationWorkflow extends WorkflowDefinition {
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); 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()); return NextAction.stopInState(DONE, "ERP updated successfully for: " + registerRequest.getUsername());