From 7b9e89eeffcbef1abdf822c58e95f67fcad311a1 Mon Sep 17 00:00:00 2001 From: Vusumuzi Khoza Date: Mon, 22 Sep 2025 22:24:01 +0200 Subject: [PATCH] completed nflow integration into onboarding --- pom.xml | 11 +- .../zw/qantra/tm/configs/JacksonConfig.java | 2 + .../controllers/OnboardingController.java | 77 ++++++++-- .../zw/qantra/tm/domain/dtos/OtpRequest.java | 10 ++ .../tm/domain/dtos/PasswordRequest.java | 1 + .../tm/domain/dtos/RegisterRequest.java | 5 +- .../qantra/tm/domain/dtos/StateResponse.java | 1 + .../tm/domain/dtos/WorkflowStatusDto.java | 18 +++ .../tm/domain/dtos/erp/CustomerDto.java | 38 +++++ .../java/zw/qantra/tm/domain/models/User.java | 5 +- .../tm/domain/services/UserService.java | 9 ++ .../workflows/RegistrationWorkflow.java | 140 +++++++++++++++--- .../tm/domain/workflows/WorkflowUtils.java | 17 ++- .../java/zw/qantra/tm/rest/RestService.java | 7 + .../tm/rest/RestTemplateInterceptor.java | 2 + src/main/resources/application-lab.properties | 17 +-- .../resources/application-vusa.properties | 8 + src/main/resources/application.properties | 21 +-- 18 files changed, 332 insertions(+), 57 deletions(-) create mode 100644 src/main/java/zw/qantra/tm/domain/dtos/OtpRequest.java create mode 100644 src/main/java/zw/qantra/tm/domain/dtos/WorkflowStatusDto.java create mode 100644 src/main/java/zw/qantra/tm/domain/dtos/erp/CustomerDto.java diff --git a/pom.xml b/pom.xml index 004320d..afe601a 100644 --- a/pom.xml +++ b/pom.xml @@ -157,10 +157,15 @@ com.fasterxml.jackson.datatype - jackson-datatype-joda - 2.13.1 + jackson-datatype-jdk8 - + + com.googlecode.libphonenumber + libphonenumber + 8.13.36 + + + diff --git a/src/main/java/zw/qantra/tm/configs/JacksonConfig.java b/src/main/java/zw/qantra/tm/configs/JacksonConfig.java index 10af2b6..f146914 100644 --- a/src/main/java/zw/qantra/tm/configs/JacksonConfig.java +++ b/src/main/java/zw/qantra/tm/configs/JacksonConfig.java @@ -1,6 +1,7 @@ package zw.qantra.tm.configs; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fasterxml.jackson.datatype.joda.JodaModule; import org.springframework.context.annotation.Bean; @@ -16,6 +17,7 @@ public class JacksonConfig { ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerModule(new JavaTimeModule()); objectMapper.registerModule(new JodaModule()); + objectMapper.registerModule(new Jdk8Module()); return objectMapper; } 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 79acee7..009ab66 100644 --- a/src/main/java/zw/qantra/tm/domain/controllers/OnboardingController.java +++ b/src/main/java/zw/qantra/tm/domain/controllers/OnboardingController.java @@ -1,26 +1,25 @@ package zw.qantra.tm.domain.controllers; -import io.nflow.engine.service.WorkflowInstanceInclude; +import io.nflow.engine.internal.dao.WorkflowInstanceDao; import io.nflow.engine.service.WorkflowInstanceService; +import io.nflow.engine.workflow.instance.QueryWorkflowInstances; import io.nflow.engine.workflow.instance.WorkflowInstance; import io.nflow.engine.workflow.instance.WorkflowInstanceAction; import io.nflow.engine.workflow.instance.WorkflowInstanceFactory; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.joda.time.DateTime; +import org.joda.time.Instant; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import zw.qantra.tm.domain.dtos.AuthResponse; -import zw.qantra.tm.domain.dtos.LoginRequest; -import zw.qantra.tm.domain.dtos.RegisterRequest; -import zw.qantra.tm.domain.dtos.VerifyRequest; +import zw.qantra.tm.domain.dtos.*; +import zw.qantra.tm.domain.services.OtpService; import zw.qantra.tm.domain.services.UserService; import zw.qantra.tm.domain.workflows.RegistrationWorkflow; import zw.qantra.tm.domain.workflows.WorkflowUtils; import zw.qantra.tm.utils.Utils; -import java.util.EnumSet; -import java.util.HashMap; +import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; @@ -34,20 +33,46 @@ public class OnboardingController { private final WorkflowInstanceService workflowInstanceService; private final WorkflowInstanceFactory workflowInstanceFactory; private final WorkflowUtils workflowUtils; + private final OtpService otpService; + + @PostMapping("/check/{username}") + public ResponseEntity check(@PathVariable String username) { + QueryWorkflowInstances query = new QueryWorkflowInstances.Builder() + .setExternalId(username) + .setIncludeCurrentStateVariables(true) + .setIncludeActions(true) + .build(); + List workflowInstanceList = + new ArrayList<>(workflowInstanceService.listWorkflowInstances(query)); + if(!workflowInstanceList.isEmpty()){ + WorkflowInstance instance = workflowInstanceList.get(0); + WorkflowInstanceAction action = instance.actions.get(0); + WorkflowStatusDto statusDto = new WorkflowStatusDto(); + statusDto.setWorkflowId(instance.id); + statusDto.setState(instance.state); + statusDto.setStateText(instance.stateText); + statusDto.setStatus(instance.status.toString()); + statusDto.setStage(action.state); + return ResponseEntity.ok(statusDto); + } + + return ResponseEntity.notFound().build(); + } @PostMapping("/register") public ResponseEntity register(@Valid @RequestBody RegisterRequest request) throws ExecutionException, InterruptedException { var instance = workflowInstanceFactory.newWorkflowInstanceBuilder() .setType(RegistrationWorkflow.TYPE) - .setExternalId(request.getUsername() + "-" + System.currentTimeMillis()) + .setExternalId(request.getUsername() + "-" + new Instant().getMillis()) .putStateVariable("body", request) .build(); long workflowId = workflowInstanceService.insertWorkflowInstance(instance); CompletableFuture future = workflowUtils - .waitForState(workflowId, "sendOtp", 30000, 1000); + .waitForState(workflowId, new String[]{"sendOtp", "failed"}, + 30000, 1000); Object response = future.get(); return ResponseEntity.ok(response); @@ -56,6 +81,7 @@ public class OnboardingController { @PostMapping("/verify") public ResponseEntity verify(@Valid @RequestBody VerifyRequest request) throws ExecutionException, InterruptedException { + // Create an update to move the workflow to the next state WorkflowInstance update = new WorkflowInstance.Builder() .setId(request.getWorkflowId()) @@ -74,14 +100,43 @@ public class OnboardingController { workflowInstanceService.updateWorkflowInstance(update, action); CompletableFuture future = workflowUtils - .waitForState(request.getWorkflowId(), "verifyOtp", 30000, 1000); + .waitForState(request.getWorkflowId(), new String[]{"done", "failed"}, + 30000, 50); + Object response = future.get(); + + return ResponseEntity.ok(response); + } + + @PostMapping("/resend") + public ResponseEntity resend(@RequestBody OtpRequest otpRequest) + throws ExecutionException, InterruptedException { + // Create an update to move the workflow to the next state + WorkflowInstance update = new WorkflowInstance.Builder() + .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)); }}) + .setStateText("Resumed from manual state") + .build(); + + WorkflowInstanceAction action = new WorkflowInstanceAction.Builder(update) + .setType(WorkflowInstanceAction.WorkflowActionType.externalChange) + .setExecutionEnd(DateTime.now()) + .build(); + + // Apply the update + workflowInstanceService.updateWorkflowInstance(update, action); + + CompletableFuture future = workflowUtils + .waitForState(otpRequest.getWorkflowId(), new String[]{"resendOtp", "failed"}, + 30000, 50); Object response = future.get(); return ResponseEntity.ok(response); } @PostMapping("/login") - public ResponseEntity login(@Valid @RequestBody LoginRequest request) { + public ResponseEntity login(@Valid @RequestBody LoginRequest request) { AuthResponse response = userService.login(request); return ResponseEntity.ok(response); } diff --git a/src/main/java/zw/qantra/tm/domain/dtos/OtpRequest.java b/src/main/java/zw/qantra/tm/domain/dtos/OtpRequest.java new file mode 100644 index 0000000..4687517 --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/dtos/OtpRequest.java @@ -0,0 +1,10 @@ +package zw.qantra.tm.domain.dtos; + +import lombok.Data; + +@Data +public class OtpRequest { + private String username; + private String phone; + private Long workflowId; +} diff --git a/src/main/java/zw/qantra/tm/domain/dtos/PasswordRequest.java b/src/main/java/zw/qantra/tm/domain/dtos/PasswordRequest.java index d823019..a3ab243 100644 --- a/src/main/java/zw/qantra/tm/domain/dtos/PasswordRequest.java +++ b/src/main/java/zw/qantra/tm/domain/dtos/PasswordRequest.java @@ -14,4 +14,5 @@ public class PasswordRequest { @NotBlank(message = "Password is required") @Size(min = 6, message = "Password must be at least 6 characters") private String password; + private Long workflowId; } 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 1721ecc..037f34b 100644 --- a/src/main/java/zw/qantra/tm/domain/dtos/RegisterRequest.java +++ b/src/main/java/zw/qantra/tm/domain/dtos/RegisterRequest.java @@ -10,9 +10,10 @@ public class RegisterRequest { @NotBlank(message = "Username is required") @Size(min = 3, max = 50, message = "Username must be between 3 and 50 characters") private String username; - private String password; - + private String photoUrl; + private Long workflowId; + @NotBlank(message = "Email is required") @Email(message = "Email should be valid") private String email; diff --git a/src/main/java/zw/qantra/tm/domain/dtos/StateResponse.java b/src/main/java/zw/qantra/tm/domain/dtos/StateResponse.java index 9198910..7903380 100644 --- a/src/main/java/zw/qantra/tm/domain/dtos/StateResponse.java +++ b/src/main/java/zw/qantra/tm/domain/dtos/StateResponse.java @@ -11,6 +11,7 @@ import lombok.NoArgsConstructor; @NoArgsConstructor public class StateResponse { private String state; + private String status; private Object body; private String message; private String workflowId; diff --git a/src/main/java/zw/qantra/tm/domain/dtos/WorkflowStatusDto.java b/src/main/java/zw/qantra/tm/domain/dtos/WorkflowStatusDto.java new file mode 100644 index 0000000..3f048c6 --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/dtos/WorkflowStatusDto.java @@ -0,0 +1,18 @@ +package zw.qantra.tm.domain.dtos; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WorkflowStatusDto { + private Long workflowId; + private String state; + private String stateText; + private String status; + private String stage; +} diff --git a/src/main/java/zw/qantra/tm/domain/dtos/erp/CustomerDto.java b/src/main/java/zw/qantra/tm/domain/dtos/erp/CustomerDto.java new file mode 100644 index 0000000..76446bd --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/dtos/erp/CustomerDto.java @@ -0,0 +1,38 @@ +package zw.qantra.tm.domain.dtos.erp; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.*; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class CustomerDto { + @JsonProperty("doctype") + private String doctype; + + @JsonProperty("customer_name") + private String customerName; + + @JsonProperty("customer_group") + private String customerGroup; + + @JsonProperty("territory") + private String territory; + + @JsonProperty("customer_type") + private String customerType; + + @JsonProperty("language") + private String language; + + @JsonProperty("mobile_no") + private String mobileNo; + + @JsonProperty("email_id") + private String emailId; + + @JsonProperty("docstatus") + private Integer docstatus; +} \ No newline at end of file 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 b84f34a..5047e81 100644 --- a/src/main/java/zw/qantra/tm/domain/models/User.java +++ b/src/main/java/zw/qantra/tm/domain/models/User.java @@ -25,7 +25,10 @@ public class User extends BaseEntity implements UserDetails { @Column(unique = true, nullable = false) private String email; private String phone; - + private String erpCustomerRef; + private String photoUrl; + private Long workflowId; + @Column(name = "first_name") private String firstName; 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 0c6ed3e..cee309f 100644 --- a/src/main/java/zw/qantra/tm/domain/services/UserService.java +++ b/src/main/java/zw/qantra/tm/domain/services/UserService.java @@ -1,5 +1,6 @@ package zw.qantra.tm.domain.services; +import lombok.Getter; import lombok.RequiredArgsConstructor; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; @@ -17,10 +18,16 @@ import zw.qantra.tm.utils.JwtUtils; @RequiredArgsConstructor public class UserService implements UserDetailsService { + @Getter private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; private final JwtUtils jwtUtils; + public User fetchUserByUsername(String username) { + return userRepository.findByUsername(username) + .orElseThrow(() -> new UsernameNotFoundException("User not found with username: " + username)); + } + @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { return userRepository.findByUsername(username) @@ -43,6 +50,8 @@ public class UserService implements UserDetailsService { user.setFirstName(request.getFirstName()); user.setLastName(request.getLastName()); user.setPhone(request.getPhone()); + user.setWorkflowId(request.getWorkflowId()); + user.setPhotoUrl(request.getPhotoUrl()); User savedUser = userRepository.save(user); String token = jwtUtils.generateToken(savedUser); 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 b1c90b0..ba36cba 100644 --- a/src/main/java/zw/qantra/tm/domain/workflows/RegistrationWorkflow.java +++ b/src/main/java/zw/qantra/tm/domain/workflows/RegistrationWorkflow.java @@ -1,29 +1,37 @@ package zw.qantra.tm.domain.workflows; +import com.google.i18n.phonenumbers.NumberParseException; +import com.google.i18n.phonenumbers.PhoneNumberUtil; +import com.google.i18n.phonenumbers.Phonenumber; import io.nflow.engine.workflow.definition.WorkflowStateType; +import org.joda.time.DateTime; import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import io.nflow.engine.workflow.definition.NextAction; import io.nflow.engine.workflow.definition.StateExecution; import io.nflow.engine.workflow.definition.WorkflowDefinition; -import zw.qantra.tm.domain.dtos.EmailRequest; -import zw.qantra.tm.domain.dtos.VerifyRequest; +import zw.qantra.tm.domain.dtos.*; +import zw.qantra.tm.domain.dtos.erp.CustomerDto; import zw.qantra.tm.domain.models.Otp; +import zw.qantra.tm.domain.models.User; import zw.qantra.tm.domain.services.OtpService; import zw.qantra.tm.domain.services.UserService; import io.nflow.engine.workflow.curated.State; -import zw.qantra.tm.domain.dtos.AuthResponse; -import zw.qantra.tm.domain.dtos.RegisterRequest; import jakarta.validation.ConstraintViolation; import jakarta.validation.Validator; 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; @Component @@ -43,6 +51,16 @@ public class RegistrationWorkflow extends WorkflowDefinition { private String infobipUrl; @Value("${infobip.apikey}") private String infobipApiKey; + @Value("${erpnext.url}") + private String erpnextUrl; + @Value("${erpnext.api.key}") + private String erpnextApiKey; + @Value("${erpnext.api.secret}") + private String erpnextApiSecret; + @Value("${erpnext.username}") + private String erpnextUsername; + @Value("${erpnext.password}") + private String erpnextPassword; public static final String TYPE = "registrationWorkflow"; @@ -50,10 +68,9 @@ public class RegistrationWorkflow extends WorkflowDefinition { public static final State REGISTER = new State("register"); public static final State SEND_OTP = new State("sendOtp", WorkflowStateType.manual); public static final State VERIFY_OTP = new State("verifyOtp"); - public static final State OTP_SUCCESS = new State("otpSuccess", WorkflowStateType.manual); public static final State RESEND_OTP = new State("resendOtp", WorkflowStateType.manual); - public static final State SET_PIN = new State("setPin", WorkflowStateType.normal); - public static final State UPDATE_ERP = new State("updateErp", WorkflowStateType.normal); + public static final State OTP_SUCCESS = new State("otpSuccess"); + public static final State UPDATE_ERP = new State("updateErp"); public static final State FAILED = new State("failed", WorkflowStateType.manual); public static final State DONE = new State("done", WorkflowStateType.end); @@ -63,10 +80,9 @@ public class RegistrationWorkflow extends WorkflowDefinition { permit(BEGIN, REGISTER); permit(REGISTER, SEND_OTP); permit(SEND_OTP, VERIFY_OTP); + permit(RESEND_OTP, VERIFY_OTP); permit(VERIFY_OTP, OTP_SUCCESS, FAILED); - permit(RESEND_OTP, OTP_SUCCESS, FAILED); - permit(OTP_SUCCESS, SET_PIN, FAILED); - permit(SET_PIN, UPDATE_ERP); + permit(OTP_SUCCESS, UPDATE_ERP, FAILED); permit(UPDATE_ERP, DONE); } @@ -93,7 +109,19 @@ public class RegistrationWorkflow extends WorkflowDefinition { return NextAction.moveToState(FAILED, errorMessage.toString()); } - // todo: add this to the appropriate action + // update phone number format + PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance(); + try { + String phone = payload.getPhone(); + Phonenumber.PhoneNumber zimProto = phoneUtil.parse(phone, "ZW"); + phone = String.valueOf(zimProto.getCountryCode()) + String.valueOf(zimProto.getNationalNumber()); + payload.setPhone(phone); + } catch (NumberParseException e) { + System.err.println("NumberParseException was thrown: " + e.toString()); + return NextAction.moveToState(FAILED, "Invalid phone number"); + } + + execution.setVariable("registerRequest", payload); execution.setVariable("body", payload); return NextAction.moveToState(SEND_OTP, "Initial registration success for: " + payload.getUsername()); @@ -117,6 +145,7 @@ public class RegistrationWorkflow extends WorkflowDefinition { log.info("otp generated successfully: {}", otp.getOtp()); } catch (Exception e) { + e.printStackTrace(); log.error("Error sending OTP: {}", e.getMessage(), e); } } @@ -132,18 +161,93 @@ public class RegistrationWorkflow extends WorkflowDefinition { return NextAction.moveToState(OTP_SUCCESS, "OTP verified successfully"); } - public void otpSuccess(StateExecution execution) { - log.info("OTP verified successfully"); + public void resendOtp(StateExecution execution) { + OtpRequest otpRequest = execution.getVariable("body", OtpRequest.class); + Otp otp = otpService.generateOtp(otpRequest.getUsername(), "REGISTRATION"); + String string = getSMSString(otpRequest.getPhone(), otp.getOtp()); + + log.info("otp generated successfully: {}", otp.getOtp()); } - public NextAction setPin(StateExecution execution) { - RegisterRequest response = execution.getVariable("body", RegisterRequest.class); - return NextAction.stopInState(UPDATE_ERP, "PIN set successfully for: " + response.getUsername()); + public NextAction otpSuccess(StateExecution execution) { + log.info("OTP verified successfully"); + log.info("Begin user registration"); + + VerifyRequest request = execution.getVariable("body", VerifyRequest.class); + try { + RegisterRequest registerRequest = execution.getVariable("registerRequest", RegisterRequest.class); + + registerRequest.setWorkflowId(request.getWorkflowId()); + execution.setVariable("registerRequest", registerRequest); + + userService.register(registerRequest); + }catch (Exception e) { + e.printStackTrace(); + return NextAction.moveToState(FAILED, "Failed to register user: " + e.getMessage()); + } + + return NextAction.moveToState(UPDATE_ERP, "User creation successfully for: " + request.getUsername()); } public NextAction updateErp(StateExecution execution) { - RegisterRequest response = execution.getVariable("body", RegisterRequest.class); - return NextAction.stopInState(DONE, "ERP updated successfully for: " + response.getUsername()); + RegisterRequest registerRequest = execution.getVariable("registerRequest", RegisterRequest.class); + + // login to erp + HttpHeaders headers = new HttpHeaders(); + headers.add("Content-Type", "application/json"); + headers.add("Accept", "application/json"); + + Map loginRequest = Map.of( + "usr", erpnextUsername, + "pwd", erpnextPassword); + + String sid = ""; + + try { + ResponseEntity response = restService.postAsEntity( + erpnextUrl + "/method/login", loginRequest, headers, LinkedHashMap.class); + + sid = response.getHeaders().getFirst("Set-Cookie"); + } catch (Exception e) { + e.printStackTrace(); + return NextAction.retryAfter(DateTime.now().plusSeconds(30), "Failed to login to ERP: " + e.getMessage()); + } + + // create customer in erp + String basicAuth = "Basic " + Base64.getEncoder().encodeToString( + (erpnextApiKey + ":" + erpnextApiSecret).getBytes(StandardCharsets.UTF_8)); + 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(); + + try { + LinkedHashMap response = restService.postAs( + erpnextUrl + "/v2/document/Customer", customerDto, headers, LinkedHashMap.class); + + LinkedHashMap data = (LinkedHashMap) response.get("data"); + String customerId = (String) data.get("name"); + + User user = userService.fetchUserByUsername(registerRequest.getUsername()); + user.setErpCustomerRef(customerId); + userService.getUserRepository().save(user); + } catch (Exception e) { + e.printStackTrace(); + + return NextAction.retryAfter(DateTime.now().plusSeconds(30), "Failed to update ERP: " + e.getMessage()); + } + + return NextAction.stopInState(DONE, "ERP updated successfully for: " + registerRequest.getUsername()); } String getSMSString(String to, String otp) { diff --git a/src/main/java/zw/qantra/tm/domain/workflows/WorkflowUtils.java b/src/main/java/zw/qantra/tm/domain/workflows/WorkflowUtils.java index 1f91cd5..37315a6 100644 --- a/src/main/java/zw/qantra/tm/domain/workflows/WorkflowUtils.java +++ b/src/main/java/zw/qantra/tm/domain/workflows/WorkflowUtils.java @@ -1,10 +1,13 @@ package zw.qantra.tm.domain.workflows; +import java.util.Arrays; import java.util.EnumSet; +import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; import io.nflow.engine.service.WorkflowInstanceInclude.*; +import io.nflow.engine.workflow.instance.WorkflowInstanceAction; import org.springframework.stereotype.Component; import io.nflow.engine.service.WorkflowInstanceService; @@ -21,7 +24,7 @@ import static io.nflow.engine.service.WorkflowInstanceInclude.*; public class WorkflowUtils { private final WorkflowInstanceService workflowInstanceService; - public CompletableFuture waitForState(long workflowId, String state, int timeout, int pollingInterval) { + public CompletableFuture waitForState(long workflowId, String[] states, int timeout, int pollingInterval) { CompletableFuture future = new CompletableFuture<>(); while(!future.isDone()){ // check if the future is done @@ -31,8 +34,12 @@ public class WorkflowUtils { 1L); try { Thread.sleep(pollingInterval); - if(instance.state.equalsIgnoreCase(state) && - instance.status.equals(WorkflowInstance.WorkflowInstanceStatus.manual)){ + List stateList = Arrays.stream(states).toList(); + List statusList = + Arrays.asList(WorkflowInstance.WorkflowInstanceStatus.manual, + WorkflowInstance.WorkflowInstanceStatus.finished); + if(stateList.contains(instance.state) && + statusList.contains(instance.status)){ future.complete(getStateResponse(instance, "success")); } } catch (InterruptedException e) { @@ -48,10 +55,12 @@ public class WorkflowUtils { } public StateResponse getStateResponse(WorkflowInstance instance, String message){ + WorkflowInstanceAction action = instance.actions.get(instance.actions.size()-1); return StateResponse.builder() .state(instance.state) + .status(instance.status.toString()) .body(Utils.fromJson(instance.getStateVariable("body"), Map.class)) - .message(instance.getStateVariable("message")) + .message(action.stateText) .workflowId(instance.id.toString()) .externalId(instance.externalId) .build(); diff --git a/src/main/java/zw/qantra/tm/rest/RestService.java b/src/main/java/zw/qantra/tm/rest/RestService.java index 4c6802e..8bcfd06 100755 --- a/src/main/java/zw/qantra/tm/rest/RestService.java +++ b/src/main/java/zw/qantra/tm/rest/RestService.java @@ -139,6 +139,13 @@ public class RestService { return responseEntity; } + public ResponseEntity postAsEntity(String path, Object payload, HttpHeaders headers, Class clazz){ + // build the request + HttpEntity entity = new HttpEntity<>(payload, headers); + ResponseEntity responseEntity = this.restTemplate.exchange(path, HttpMethod.POST, entity, clazz); + return responseEntity; + } + public T postAs(String path, Object payload, HttpHeaders headers, Class clazz){ // build the request HttpEntity entity = new HttpEntity<>(payload, headers); diff --git a/src/main/java/zw/qantra/tm/rest/RestTemplateInterceptor.java b/src/main/java/zw/qantra/tm/rest/RestTemplateInterceptor.java index 54bd1ad..07597dd 100755 --- a/src/main/java/zw/qantra/tm/rest/RestTemplateInterceptor.java +++ b/src/main/java/zw/qantra/tm/rest/RestTemplateInterceptor.java @@ -33,6 +33,8 @@ public class RestTemplateInterceptor String requestBody = new String(reqBody, StandardCharsets.UTF_8); if(!isJSONValid(requestBody)) requestBody = toBasicJson(requestBody); + logger.info("Request Headers: {}", request.getHeaders().toString()); + // we don't log file data if(request.getHeaders().getContentLength() < 10000){ logger.info("Request: {}: {}\n{}", diff --git a/src/main/resources/application-lab.properties b/src/main/resources/application-lab.properties index 986f916..d5ace0b 100644 --- a/src/main/resources/application-lab.properties +++ b/src/main/resources/application-lab.properties @@ -9,8 +9,6 @@ server.servlet.context-path=/api spring.jpa.hibernate.ddl-auto=update spring.datasource.driver-class-name=org.postgresql.Driver -spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect -spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect spring.datasource.url=jdbc:postgresql://173.212.247.232:5532/qpay?useLegacyDatetimeCode=false spring.datasource.username=postgres @@ -31,15 +29,16 @@ sbz.aggregator.encryption-key=735470ce-8efb-4c0f-ba3f-7298f805ab33 steward.payment.processor.url=${sbz.merchant.url} # ERPNext Configuration -erpnext.url=http://localhost:8000 -erpnext.api.key=your_api_key_here -erpnext.api.secret=your_api_secret_here +erpnext.url=http://173.212.247.232:8085/api +erpnext.api.key=1102c6da0312f75 +erpnext.api.secret=e3d95692bcf16c7 +erpnext.username=Administrator +erpnext.password=admin erpnext.company=Qantra Payments -erpnext.customer=Default Customer erpnext.item=Payment Service -erpnext.income.account=5111 - Sales Income - QPAY -erpnext.cost.center=Main - QPAY -erpnext.tax.account=VAT - QPAY +erpnext.income.account=5111 - Sales Income - Q +erpnext.cost.center=Main - Q +erpnext.tax.account=VAT - Q erpnext.tax.rate=15.0 logging.level.root=WARN diff --git a/src/main/resources/application-vusa.properties b/src/main/resources/application-vusa.properties index a7af74a..0924579 100644 --- a/src/main/resources/application-vusa.properties +++ b/src/main/resources/application-vusa.properties @@ -1,2 +1,10 @@ # sbz.merchant.url=http://localhost:24000/v1 sbz.merchant.url=https://api.stewardpay.co.zw/lab/v2 + +spring.datasource.url=jdbc:postgresql://localhost:5432/qpay?useLegacyDatetimeCode=false +spring.datasource.username=postgres +spring.datasource.password=example + +nflow.db.postgresql.url=jdbc:postgresql://localhost:5432/qpay +nflow.db.postgresql.user=postgres +nflow.db.postgresql.password=example diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 2bdfb20..27e0a82 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -9,8 +9,6 @@ server.servlet.context-path=/api spring.jpa.hibernate.ddl-auto=update spring.datasource.driver-class-name=org.postgresql.Driver -spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect -spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect spring.datasource.url=jdbc:postgresql://173.212.247.232:5532/qpay?useLegacyDatetimeCode=false spring.datasource.username=postgres @@ -31,15 +29,16 @@ sbz.aggregator.encryption-key=f29b64d6-e44b-42ea-a0ea-9d9f6a806dbe steward.payment.processor.url=${sbz.merchant.url} # ERPNext Configuration -erpnext.url=http://localhost:8000 -erpnext.api.key=your_api_key_here -erpnext.api.secret=your_api_secret_here +erpnext.url=http://173.212.247.232:8085/api +erpnext.api.key=1102c6da0312f75 +erpnext.api.secret=e3d95692bcf16c7 +erpnext.username=Administrator +erpnext.password=admin erpnext.company=Qantra Payments -erpnext.customer=Default Customer erpnext.item=Payment Service -erpnext.income.account=5111 - Sales Income - QPAY -erpnext.cost.center=Main - QPAY -erpnext.tax.account=VAT - QPAY +erpnext.income.account=5111 - Sales Income - Q +erpnext.cost.center=Main - Q +erpnext.tax.account=VAT - Q erpnext.tax.rate=15.0 # JWT Configuration @@ -47,6 +46,10 @@ jwt.secret=your-super-secret-jwt-key-here-make-it-very-long-and-secure-in-produc jwt.expiration=86400000 logging.level.root=WARN +nflow.db.postgresql.driver=org.postgresql.Driver +nflow.db.postgresql.url=jdbc:postgresql://173.212.247.232:5532/qpay +nflow.db.postgresql.user=postgres +nflow.db.postgresql.password=zdDZMzq6F4B4L1IUl # Email Configuration infobip.url=https://z3m696.api.infobip.com/sms/2/text/advanced