updates on batch logic

This commit is contained in:
Prince
2026-06-17 01:11:48 +02:00
parent 5d3c9a46a3
commit 0eda74414e
16 changed files with 288 additions and 254 deletions

1
.gitignore vendored
View File

@@ -33,3 +33,4 @@ build/
.vscode/
logs/
data/
reports/

View File

@@ -9,6 +9,7 @@ import org.springframework.data.jpa.domain.Specification;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import zw.qantra.tm.domain.dtos.GroupBatchCreateRequest;
import zw.qantra.tm.domain.dtos.GroupBatchItemUpdateRequest;
import zw.qantra.tm.domain.dtos.GroupBatchUpdateRequest;
import zw.qantra.tm.domain.dtos.GroupBatchTriggerRequest;
import zw.qantra.tm.domain.models.GroupBatch;
@@ -16,6 +17,7 @@ import zw.qantra.tm.domain.models.GroupBatchItem;
import zw.qantra.tm.domain.services.BatchReportService;
import zw.qantra.tm.domain.services.GroupBatchService;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@@ -93,6 +95,16 @@ public class GroupBatchController {
return ResponseEntity.ok(groupBatchService.getBatchItems(spec, pageable));
}
@PutMapping("/{batchId}/items")
public ResponseEntity<List<GroupBatchItem>> updateBatchItems(
@PathVariable UUID batchId,
@RequestBody GroupBatchItemUpdateRequest.ListWrapper wrapper) {
return ResponseEntity.ok(groupBatchService.updateBatchItems(
batchId,
wrapper.getUserId(),
wrapper.getItems()));
}
@DeleteMapping("/{batchId}")
public ResponseEntity<Void> deleteBatch(@PathVariable UUID batchId,
@RequestParam String userId) {

View File

@@ -0,0 +1,29 @@
package zw.qantra.tm.domain.dtos;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
import java.util.UUID;
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class GroupBatchItemUpdateRequest {
private UUID id;
private BigDecimal amount;
private String recipientName;
private String recipientPhone;
private String billClientId;
private String billName;
private String billProductName;
private String providerImage;
private String providerLabel;
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public static class ListWrapper {
private List<GroupBatchItemUpdateRequest> items;
private String userId;
}
}

View File

@@ -58,5 +58,11 @@ public class GroupBatchItem extends BaseEntity {
@ManyToOne
@JoinColumn(name = "transaction_id")
private Transaction transaction;
private String billClientId;
private String billName;
private String billProductName;
private String providerImage;
private String providerLabel;
}

View File

@@ -34,6 +34,7 @@ public class Provider extends BaseEntity {
private Boolean requiresAmount;
private Boolean requiresPhone;
private Boolean requiresAccount;
private Boolean requiresProducts;
private double percentageCommission;
@Column(columnDefinition = "TEXT")
private String meta;

View File

@@ -81,6 +81,7 @@ public class Transaction extends BaseEntity {
private String paymentProcessorName;
private String providerImage;
private String paymentProcessorImage;
private UUID batchId;
@Enumerated(EnumType.STRING)
private Status authorizationStatus;

View File

@@ -13,6 +13,7 @@ import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import zw.qantra.tm.domain.dtos.GroupBatchCreateRequest;
import zw.qantra.tm.domain.dtos.GroupBatchItemUpdateRequest;
import zw.qantra.tm.domain.dtos.GroupBatchUpdateRequest;
import zw.qantra.tm.domain.dtos.StateResponse;
import zw.qantra.tm.domain.enums.AuthType;
@@ -30,10 +31,13 @@ import zw.qantra.tm.exceptions.ApiException;
import zw.qantra.tm.utils.Utils;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
@@ -43,7 +47,8 @@ public class GroupBatchService {
private static final String BATCH_WORKFLOW_TYPE = "batchWorkflow";
private static final Set<GroupBatchStatus> UPDATABLE_STATUSES = Set.of(
GroupBatchStatus.CREATED
GroupBatchStatus.CREATED,
GroupBatchStatus.CONFIRM_FAILED
);
private static final Set<GroupBatchStatus> DELETABLE_STATUSES = Set.of(
@@ -74,9 +79,6 @@ public class GroupBatchService {
if (members.isEmpty()) {
throw new ApiException("Recipient group has no members");
}
if (request.getTransactionTemplate() == null) {
throw new ApiException("transactionTemplate is required");
}
GroupBatch batch = GroupBatch.builder()
.recipientGroupId(group.getId())
@@ -327,6 +329,77 @@ public class GroupBatchService {
return groupBatchRepository.save(batch);
}
@Transactional
public List<GroupBatchItem> updateBatchItems(UUID batchId, String userId,
List<GroupBatchItemUpdateRequest> items) {
GroupBatch batch = getBatch(batchId, userId);
if (!UPDATABLE_STATUSES.contains(batch.getStatus())) {
throw new ApiException(
"Batch items cannot be updated at current status: " + batch.getStatus()
+ ". Only batches in CREATED status can have items updated."
);
}
List<GroupBatchItem> existingItems = groupBatchItemRepository.findAllByGroupBatchId(batchId);
Map<UUID, GroupBatchItem> itemMap = existingItems.stream()
.collect(Collectors.toMap(GroupBatchItem::getId, item -> item));
List<GroupBatchItem> updatedItems = new ArrayList<>();
for (GroupBatchItemUpdateRequest update : items) {
if (update.getId() == null) {
throw new ApiException("Item id is required for each item update");
}
GroupBatchItem item = itemMap.get(update.getId());
if (item == null) {
throw new ApiException("Group batch item not found: " + update.getId());
}
if (update.getAmount() != null) {
item.setAmount(update.getAmount());
}
if (update.getRecipientName() != null) {
item.setRecipientName(update.getRecipientName());
}
if (update.getRecipientPhone() != null) {
item.setRecipientPhone(update.getRecipientPhone());
}
if (update.getBillClientId() != null) {
item.setBillClientId(update.getBillClientId());
}
if (update.getBillName() != null) {
item.setBillName(update.getBillName());
}
if (update.getBillProductName() != null) {
item.setBillProductName(update.getBillProductName());
}
if (update.getProviderImage() != null) {
item.setProviderImage(update.getProviderImage());
}
if (update.getProviderLabel() != null) {
item.setProviderLabel(update.getProviderLabel());
}
updatedItems.add(groupBatchItemRepository.save(item));
}
// Recalculate batch totals if any amount changed
boolean amountChanged = items.stream().anyMatch(u -> u.getAmount() != null);
if (amountChanged) {
List<GroupBatchItem> allItems = groupBatchItemRepository.findAllByGroupBatchId(batchId);
BigDecimal newTotalValue = allItems.stream()
.map(GroupBatchItem::getAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
batch.setValue(newTotalValue);
batch.setCount(allItems.size());
groupBatchRepository.save(batch);
}
return updatedItems;
}
@Transactional
public void deleteBatch(UUID batchId, String userId) {
GroupBatch batch = getBatch(batchId, userId);

View File

@@ -10,6 +10,7 @@ import zw.qantra.tm.domain.repositories.GroupBatchRepository;
import zw.qantra.tm.domain.repositories.RecipientRepository;
import zw.qantra.tm.domain.services.processors.workflows.handlers.*;
import zw.qantra.tm.exceptions.ApiException;
import zw.qantra.tm.utils.SecurityUtils;
import zw.qantra.tm.utils.Utils;
import java.math.BigDecimal;
@@ -32,6 +33,7 @@ public class GroupBatchWorkflowService {
private final IntegrationHandler integrationHandler;
private final CalculateChargesHandler calculateChargesHandler;
private final CleanupHandler cleanUpHandler;
private final UserService userService;
@Transactional
public GroupBatch processConfirm(UUID batchId) {
@@ -49,15 +51,10 @@ public class GroupBatchWorkflowService {
batch.setConfirmStartedAt(LocalDateTime.now());
groupBatchRepository.save(batch);
Transaction template = Utils.fromJson(batch.getTransactionTemplate(), Transaction.class);
if (template == null) {
throw new ApiException("Batch transaction template is missing");
}
List<GroupBatchItem> items = groupBatchItemRepository.findAllByGroupBatchId(batchId);
List<CompletableFuture<Void>> futures = new ArrayList<>();
for (GroupBatchItem item : items) {
futures.add(CompletableFuture.runAsync(() -> confirmItem(batch, item, template)));
futures.add(CompletableFuture.runAsync(() -> confirmItem(batch, item)));
}
futures.forEach(CompletableFuture::join);
@@ -98,17 +95,18 @@ public class GroupBatchWorkflowService {
throw new ApiException("No confirmed batch items available for payment request");
}
Transaction template = Utils.fromJson(batch.getTransactionTemplate(), Transaction.class);
if (template == null) {
throw new ApiException("Batch transaction template is missing");
}
BigDecimal total = confirmedItems.stream()
.map(item -> item.getAmount() == null ? BigDecimal.ZERO : item.getAmount())
.reduce(BigDecimal.ZERO, BigDecimal::add);
User user = userService.getUserRepository().findById(UUID.fromString(batch.getUserId())).orElseThrow();
Transaction transaction = Utils.deepCopy(template, Transaction.class);
Transaction transaction = new Transaction();
transaction.setId(null);
transaction.setDebitPhone(user.getPhone());
transaction.setDebitName(user.getFirstName());
transaction.setDebitCurrency(CurrencyType.valueOf(batch.getCurrency()));
transaction.setRegion("ZW");
transaction.setPartyType(PartyType.GROUP);
transaction.setType(RequestType.REQUEST);
transaction.setAuthType(authType == null ? AuthType.REMOTE : authType);
@@ -119,6 +117,7 @@ public class GroupBatchWorkflowService {
transaction.setPaymentProcessorLabel(batch.getPaymentProcessorLabel());
transaction.setPaymentProcessorName(batch.getPaymentProcessorName());
transaction.setPaymentProcessorImage(batch.getPaymentProcessorImage());
transaction.setBatchId(batch.getId());
transaction = calculateChargesHandler.process(transaction);
transaction = (Transaction) cleanUpHandler.process(transaction);
@@ -203,11 +202,23 @@ public class GroupBatchWorkflowService {
return groupBatchRepository.save(batch);
}
private void confirmItem(GroupBatch batch, GroupBatchItem item, Transaction template) {
private void confirmItem(GroupBatch batch, GroupBatchItem item) {
GroupBatchItem mutableItem = groupBatchItemRepository.findById(item.getId()).orElse(item);
try {
Transaction transaction = Utils.deepCopy(template, Transaction.class);
User user = userService.getUserRepository().findById(UUID.fromString(batch.getUserId())).orElseThrow();
Transaction transaction = new Transaction();
transaction.setId(null);
transaction.setDebitPhone(user.getPhone());
transaction.setDebitName(user.getFirstName());
transaction.setDebitCurrency(CurrencyType.valueOf(batch.getCurrency()));
transaction.setRegion("ZW");
transaction.setBillClientId(item.getBillClientId());
transaction.setBillName(item.getBillName());
transaction.setBillProductName(item.getBillProductName());
transaction.setProviderImage(item.getProviderImage());
transaction.setProviderLabel(item.getProviderLabel());
transaction.setUserId(batch.getUserId());
transaction.setType(RequestType.CONFIRM);
transaction.setPartyType(PartyType.BATCH);
@@ -241,6 +252,7 @@ public class GroupBatchWorkflowService {
mutableItem.setConfirmError(result.getErrorMessage());
}
} catch (Exception e) {
e.printStackTrace();
mutableItem.setConfirmStatus(GroupBatchItemConfirmStatus.FAILED);
mutableItem.setStatus(GroupBatchItemStatus.FAILED);
mutableItem.setIntegrationStatus(GroupBatchItemIntegrationStatus.SKIPPED);
@@ -278,6 +290,7 @@ public class GroupBatchWorkflowService {
mutableItem.setStatus(GroupBatchItemStatus.INTEGRATED);
mutableItem.setIntegrationError(null);
} catch (Exception e) {
e.printStackTrace();
mutableItem.setIntegrationStatus(GroupBatchItemIntegrationStatus.FAILED);
mutableItem.setStatus(GroupBatchItemStatus.FAILED);
mutableItem.setIntegrationError(e.getMessage());

View File

@@ -90,6 +90,10 @@ public class ProviderService {
if (provider == null) {
return Collections.emptyList();
}
if(provider.getRequiresProducts().equals(false)){
return Collections.emptyList();
}
Map<String, String> meta = Utils.fromJson(provider.getMeta(), Map.class);
String providerId = meta.get("hotProductId");

View File

@@ -16,7 +16,9 @@ import zw.qantra.tm.domain.dtos.RegisterRequest;
import zw.qantra.tm.domain.dtos.erp.*;
import zw.qantra.tm.domain.enums.CurrencyType;
import zw.qantra.tm.domain.enums.Status;
import zw.qantra.tm.domain.models.GroupBatchItem;
import zw.qantra.tm.domain.models.Transaction;
import zw.qantra.tm.domain.repositories.GroupBatchItemRepository;
import zw.qantra.tm.domain.repositories.TransactionRepository;
import zw.qantra.tm.configs.VelocityApiProperties;
import zw.qantra.tm.exceptions.AlreadyExistsException;
@@ -27,12 +29,14 @@ import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
@Service
@Slf4j
@RequiredArgsConstructor
public class VelocityPaymentService {
private final TransactionRepository transactionRepository;
private final GroupBatchItemRepository groupBatchItemRepository;
private final ObjectMapper objectMapper;
private final RestService restService;
private final VelocityApiProperties velocityApiProperties;
@@ -101,19 +105,47 @@ public class VelocityPaymentService {
public Transaction createSalesOrder(Transaction transaction) {
BigDecimal amount = transaction.getTotalAmount();
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd");
List<VelocitySalesOrderDto.ItemDto> items;
if (transaction.getBatchId() != null) {
// Fetch GroupBatchItems for this batch and summarize by providerLabel
List<GroupBatchItem> batchItems = groupBatchItemRepository.findAllByGroupBatchId(transaction.getBatchId());
Map<String, BigDecimal> totalsByProvider = batchItems.stream()
.filter(item -> item.getProviderLabel() != null && item.getAmount() != null)
.collect(Collectors.groupingBy(
GroupBatchItem::getProviderLabel,
Collectors.mapping(
GroupBatchItem::getAmount,
Collectors.reducing(BigDecimal.ZERO, BigDecimal::add)
)
));
items = totalsByProvider.entrySet().stream()
.map(entry -> VelocitySalesOrderDto.ItemDto.builder()
.name(transaction.getBillName())
.itemCode("VLT-" + entry.getKey())
.qty(BigDecimal.ONE)
.unitPrice(entry.getValue())
.amount(entry.getValue())
.build())
.collect(Collectors.toList());
} else {
items = Collections.singletonList(VelocitySalesOrderDto.ItemDto.builder()
.name(transaction.getBillName())
.itemCode("VLT-" + transaction.getProviderLabel())
.qty(BigDecimal.ONE)
.unitPrice(amount)
.amount(amount)
.build());
}
VelocitySalesOrderDto payload = VelocitySalesOrderDto.builder()
.currencyCodeString(transaction.getDebitCurrency().name())
.orderDate(LocalDate.now().format(fmt))
.dueDate(LocalDate.now().plusDays(7).format(fmt))
.notes(transaction.getReference())
.notes("Transaction trace - " + transaction.getTrace())
.authorized(Boolean.TRUE)
.items(Collections.singletonList(VelocitySalesOrderDto.ItemDto.builder()
.name(transaction.getBillName())
.itemCode("VLT-" + transaction.getProviderLabel())
.qty(BigDecimal.ONE)
.unitPrice(amount)
.amount(amount)
.build()))
.items(items)
.charges(Collections.emptyList())
.build();

View File

@@ -170,10 +170,11 @@ public class HotRechargeIntegrationProcessor implements TransactionProcessorInte
}
// do this for Econet bundles
if(providerId.equals("111")) {
Provider provider = providerService.getProviderRepository()
.findByClientId(transaction.getBillClientId());
String productCode = providerService.getExternalId(provider.getId().toString(), transaction.getBillProductName());
Provider provider = providerService.getProviderRepository()
.findByClientId(transaction.getBillClientId());
if(provider.getRequiresProducts().equals(true)) {
String productCode = providerService.getExternalId(
provider.getId().toString(), transaction.getBillProductName());
List<Map<String, String>> rechargeOptions = new ArrayList<>();
Map<String, String> option = new LinkedHashMap<>();
option.put("Name", "ProductCode");

View File

@@ -41,19 +41,21 @@ public class CleanupHandler implements HandlerInterface {
// UPDATE TRAN WITH CONFIRMATION HANDLER
// =======================
Provider provider = providerService.getProviderRepository()
.findByClientId(transaction.getBillClientId());
if(provider == null){
throw new ApiException("Provider not found for billClientId: " + transaction.getBillClientId());
}
String label =
transaction.getType() + "_" +
provider.getLabel() + "_" +
provider.getIntegrationProcessorLabel();
if(transaction.getType().equals(RequestType.CONFIRM)) {
Provider provider = providerService.getProviderRepository()
.findByClientId(transaction.getBillClientId());
if(provider == null){
throw new ApiException("Provider not found for billClientId: " + transaction.getBillClientId());
}
String label =
transaction.getType() + "_" +
provider.getLabel() + "_" +
provider.getIntegrationProcessorLabel();
transaction.setConfirmationProcessorLabel(label);
transaction.setProviderLabel(provider.getLabel());
logger.info("label: {}", label);
transaction.setConfirmationProcessorLabel(label);
transaction.setProviderLabel(provider.getLabel());
logger.info("label: {}", label);
}
// FORMAT FIELDS CORRECTLY
// =======================

View File

@@ -45,6 +45,25 @@ public class ProviderSeeder implements Seeder {
.meta("{\"hotProductId\": \"101\"}")
.uid(Utils.getUid())
.build(),
Provider.builder()
.name("Econet Airtime")
.description("Econet Airtime")
.clientId("econet_airtime_zwg")
.currency(CurrencyType.ZWG)
.label("ECONET")
.category("AIRTIME")
.image("econet.png")
.externalId("78f1f497-c9eb-401c-b22c-884756e68e41")
.integrationProcessorLabel("HOT")
.accountFieldName("Phone Number")
.percentageCommission(5)
.requiresAccount(false)
.requiresAmount(true)
.requiresPhone(true)
.priority(2)
.meta("{\"hotProductId\": \"7\"}")
.uid(Utils.getUid())
.build(),
Provider.builder()
.name("NetOne Airtime")
.description("NetOne Airtime")
@@ -120,6 +139,26 @@ public class ProviderSeeder implements Seeder {
.priority(4)
.meta("{\"hotProductId\": \"111\"}")
.uid(Utils.getUid())
.build(),
Provider.builder()
.name("Econet Bundles")
.description("Econet Bundles")
.clientId("econet_bundles_zwg")
.currency(CurrencyType.ZWG)
.label("ECONET_BUNDLES")
.category("AIRTIME")
.image("econet.png")
.externalId("287863e2-a791-4106-b629-79dadc9a6780")
.integrationProcessorLabel("HOT")
.accountFieldName("Phone Number")
.percentageCommission(5)
.requiresAccount(false)
.requiresAmount(false)
.requiresPhone(true)
.requiresProducts(true)
.priority(4)
.meta("{\"hotProductId\": \"16\"}")
.uid(Utils.getUid())
.build()
);

View File

@@ -0,0 +1,21 @@
package zw.qantra.tm.utils;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
public class SecurityUtils {
public static String getCurrentUsername() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.isAuthenticated()) {
Object principal = authentication.getPrincipal();
if (principal instanceof UserDetails) {
return ((UserDetails) principal).getUsername();
} else {
return principal.toString(); // For cases where principal might be a String
}
}
return null; // Or throw an exception if the user is expected to be logged in
}
}

View File

@@ -7,7 +7,7 @@ server.port=6950
server.servlet.context-path=/api
spring.jpa.hibernate.ddl-auto=create
spring.jpa.hibernate.ddl-auto=validate
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:5432/qpay?useLegacyDatetimeCode=false
@@ -38,8 +38,8 @@ powertel.clientid=powertel_zesa
ecocash.apikey=OUzGezwTgktLg_9v0I48I6WUM4LVLGFl
ecocash.url=https://developers.ecocash.co.zw/api/ecocash_pay/api/v2/payment/instant/c2b/sandbox
#velocity.api.base-url=https://api.velocityafrica.net
#velocity.api.api-key=z5WdSlItIhYL6nd7rxzf-jFdKN3jJyL1LubhDFBvXmc
velocity.api.base-url=https://api.velocityafrica.net
velocity.api.api-key=Ko7_hAZjDNAqj--VI1kpkgV4D6PsjS6eWWEIgcfhG8M
#velocity.api.base-url=http://localhost:6975
#velocity.api.api-key=gH13nirZdUQdqC9impgzTzqEqv-jdbLOTx_HD5fQr_A
velocity.api.cancel-url=http://localhost:9005/poll

View File

@@ -1,214 +1,13 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext" xmlns:pro="http://www.liquibase.org/xml/ns/pro" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd http://www.liquibase.org/xml/ns/pro http://www.liquibase.org/xml/ns/pro/liquibase-pro-latest.xsd http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<changeSet author="khoza (generated)" id="1781296665910-3">
<createIndex indexName="idx_additional_data_txn_id" tableName="additional_data">
<column name="transaction_id"/>
</createIndex>
<changeSet author="khoza (generated)" id="1781611630517-3">
<addColumn tableName="transaction">
<column name="batch_id" type="uuid"/>
</addColumn>
</changeSet>
<changeSet author="khoza (generated)" id="1781296665910-4">
<createIndex indexName="idx_additional_data_txn_type" tableName="additional_data">
<column name="transaction_id"/>
<column name="request_type"/>
</createIndex>
</changeSet>
<changeSet author="khoza (generated)" id="1781296665910-5">
<createIndex indexName="idx_category_name" tableName="category">
<column name="name"/>
</createIndex>
</changeSet>
<changeSet author="khoza (generated)" id="1781296665910-6">
<createIndex indexName="idx_charge_condition_codes" tableName="charge_condition">
<column name="codes"/>
</createIndex>
</changeSet>
<changeSet author="khoza (generated)" id="1781296665910-7">
<createIndex indexName="idx_charge_currency" tableName="charge">
<column name="currency"/>
</createIndex>
</changeSet>
<changeSet author="khoza (generated)" id="1781296665910-8">
<createIndex indexName="idx_charge_label_currency" tableName="charge">
<column name="charge_label"/>
<column name="currency"/>
</createIndex>
</changeSet>
<changeSet author="khoza (generated)" id="1781296665910-9">
<createIndex indexName="idx_currency_iso_code" tableName="currency">
<column name="iso_code"/>
</createIndex>
</changeSet>
<changeSet author="khoza (generated)" id="1781296665910-10">
<createIndex indexName="idx_gbi_batch_confirm" tableName="group_batch_item">
<column name="group_batch_id"/>
<column name="confirm_status"/>
</createIndex>
</changeSet>
<changeSet author="khoza (generated)" id="1781296665910-11">
<createIndex indexName="idx_gbi_batch_id" tableName="group_batch_item">
<column name="group_batch_id"/>
</createIndex>
</changeSet>
<changeSet author="khoza (generated)" id="1781296665910-12">
<createIndex indexName="idx_gbi_batch_integ" tableName="group_batch_item">
<column name="group_batch_id"/>
<column name="integration_status"/>
</createIndex>
</changeSet>
<changeSet author="khoza (generated)" id="1781296665910-13">
<createIndex indexName="idx_gbi_txn_id" tableName="group_batch_item">
<column name="transaction_id"/>
</createIndex>
</changeSet>
<changeSet author="khoza (generated)" id="1781296665910-14">
<createIndex indexName="idx_group_batch_created_at" tableName="group_batch">
<column name="created_at"/>
</createIndex>
</changeSet>
<changeSet author="khoza (generated)" id="1781296665910-15">
<createIndex indexName="idx_group_batch_payment_ref" tableName="group_batch">
<column name="payment_reference"/>
</createIndex>
</changeSet>
<changeSet author="khoza (generated)" id="1781296665910-16">
<createIndex indexName="idx_group_batch_recipient_group_id" tableName="group_batch">
<column name="recipient_group_id"/>
</createIndex>
</changeSet>
<changeSet author="khoza (generated)" id="1781296665910-17">
<createIndex indexName="idx_group_batch_status" tableName="group_batch">
<column name="status"/>
</createIndex>
</changeSet>
<changeSet author="khoza (generated)" id="1781296665910-18">
<createIndex indexName="idx_group_batch_user_id" tableName="group_batch">
<column name="user_id"/>
</createIndex>
</changeSet>
<changeSet author="khoza (generated)" id="1781296665910-19">
<createIndex indexName="idx_otp_username_type" tableName="otp">
<column name="username"/>
<column name="otp_type"/>
</createIndex>
</changeSet>
<changeSet author="khoza (generated)" id="1781296665910-20">
<createIndex indexName="idx_payment_processor_label" tableName="payment_processor">
<column name="label"/>
</createIndex>
</changeSet>
<changeSet author="khoza (generated)" id="1781296665910-21">
<createIndex indexName="idx_provider_category" tableName="provider">
<column name="category"/>
</createIndex>
</changeSet>
<changeSet author="khoza (generated)" id="1781296665910-22">
<createIndex indexName="idx_provider_client_id" tableName="provider">
<column name="client_id"/>
</createIndex>
</changeSet>
<changeSet author="khoza (generated)" id="1781296665910-23">
<createIndex indexName="idx_recipient_account_user" tableName="recipient">
<column name="account"/>
<column name="user_id"/>
</createIndex>
</changeSet>
<changeSet author="khoza (generated)" id="1781296665910-24">
<createIndex indexName="idx_recipient_email" tableName="recipient">
<column name="email"/>
</createIndex>
</changeSet>
<changeSet author="khoza (generated)" id="1781296665910-25">
<createIndex indexName="idx_recipient_group_user_id" tableName="recipient_group">
<column name="user_id"/>
</createIndex>
</changeSet>
<changeSet author="khoza (generated)" id="1781296665910-26">
<createIndex indexName="idx_recipient_phone" tableName="recipient">
<column name="phone_number"/>
</createIndex>
</changeSet>
<changeSet author="khoza (generated)" id="1781296665910-27">
<createIndex indexName="idx_recipient_user_id" tableName="recipient">
<column name="user_id"/>
</createIndex>
</changeSet>
<changeSet author="khoza (generated)" id="1781296665910-28">
<createIndex indexName="idx_rgm_group_id" tableName="recipient_group_member">
<column name="recipient_group_id"/>
</createIndex>
</changeSet>
<changeSet author="khoza (generated)" id="1781296665910-29">
<createIndex indexName="idx_rgm_group_recipient" tableName="recipient_group_member">
<column name="recipient_group_id"/>
<column name="recipient_id"/>
</createIndex>
</changeSet>
<changeSet author="khoza (generated)" id="1781296665910-30">
<createIndex indexName="idx_setting_name" tableName="setting">
<column name="setting_name"/>
</createIndex>
</changeSet>
<changeSet author="khoza (generated)" id="1781296665910-31">
<createIndex indexName="idx_transaction_confirm_pay_integ" tableName="transaction">
<column name="confirmation_status"/>
<column name="payment_status"/>
<column name="integration_status"/>
</createIndex>
</changeSet>
<changeSet author="khoza (generated)" id="1781296665910-32">
<createIndex indexName="idx_transaction_created_at" tableName="transaction">
<column name="created_at"/>
</createIndex>
</changeSet>
<changeSet author="khoza (generated)" id="1781296665910-33">
<createIndex indexName="idx_transaction_credit_phone" tableName="transaction">
<column name="credit_phone"/>
</createIndex>
</changeSet>
<changeSet author="khoza (generated)" id="1781296665910-34">
<createIndex indexName="idx_transaction_debit_phone" tableName="transaction">
<column name="debit_phone"/>
</createIndex>
</changeSet>
<changeSet author="khoza (generated)" id="1781296665910-35">
<createIndex indexName="idx_transaction_pay_poll" tableName="transaction">
<column name="payment_status"/>
<column name="polling_status"/>
</createIndex>
</changeSet>
<changeSet author="khoza (generated)" id="1781296665910-36">
<createIndex indexName="idx_transaction_reference" tableName="transaction">
<column name="reference"/>
</createIndex>
</changeSet>
<changeSet author="khoza (generated)" id="1781296665910-37">
<createIndex indexName="idx_transaction_status" tableName="transaction">
<column name="status"/>
</createIndex>
</changeSet>
<changeSet author="khoza (generated)" id="1781296665910-38">
<createIndex indexName="idx_transaction_trace_type" tableName="transaction">
<column name="trace"/>
<column name="type"/>
</createIndex>
</changeSet>
<changeSet author="khoza (generated)" id="1781296665910-39">
<createIndex indexName="idx_transaction_user_id" tableName="transaction">
<column name="user_id"/>
</createIndex>
</changeSet>
<changeSet author="khoza (generated)" id="1781296665910-40">
<createIndex indexName="idx_transaction_workflow_id" tableName="transaction">
<column name="workflow_id"/>
</createIndex>
</changeSet>
<changeSet author="khoza (generated)" id="1781296665910-41">
<createIndex indexName="idx_txn_event_txn_id" tableName="transaction_event">
<column name="transaction_id"/>
</createIndex>
</changeSet>
<changeSet author="khoza (generated)" id="1781296665910-42">
<createIndex indexName="idx_user_phone" tableName="users">
<column name="phone"/>
</createIndex>
<changeSet author="khoza (generated)" id="1781633136006-3">
<addColumn tableName="provider">
<column name="requires_products" type="boolean"/>
</addColumn>
</changeSet>
</databaseChangeLog>