added erp integration
This commit is contained in:
@@ -0,0 +1,27 @@
|
|||||||
|
package zw.qantra.tm.domain.dtos.erp;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class JournalEntryAccountDto {
|
||||||
|
private String doctype;
|
||||||
|
private String account;
|
||||||
|
@JsonProperty("debit_in_account_currency")
|
||||||
|
private BigDecimal debitInAccountCurrency;
|
||||||
|
@JsonProperty("credit_in_account_currency")
|
||||||
|
private BigDecimal creditInAccountCurrency;
|
||||||
|
@JsonProperty("cost_center")
|
||||||
|
private String costCenter;
|
||||||
|
@JsonProperty("party_type")
|
||||||
|
private String partyType;
|
||||||
|
private String party;
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package zw.qantra.tm.domain.dtos.erp;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class JournalEntryDto {
|
||||||
|
private String doctype;
|
||||||
|
private String company;
|
||||||
|
@JsonProperty("posting_date")
|
||||||
|
private String postingDate;
|
||||||
|
private int docstatus;
|
||||||
|
private List<JournalEntryAccountDto> accounts;
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package zw.qantra.tm.domain.dtos.erp;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class PaymentEntryDto {
|
||||||
|
private String doctype;
|
||||||
|
private String company;
|
||||||
|
@JsonProperty("payment_type")
|
||||||
|
private String paymentType;
|
||||||
|
@JsonProperty("party_type")
|
||||||
|
private String partyType;
|
||||||
|
private String party;
|
||||||
|
@JsonProperty("paid_from")
|
||||||
|
private String paidFrom;
|
||||||
|
@JsonProperty("paid_to")
|
||||||
|
private String paidTo;
|
||||||
|
@JsonProperty("paid_amount")
|
||||||
|
private BigDecimal paidAmount;
|
||||||
|
@JsonProperty("received_amount")
|
||||||
|
private BigDecimal receivedAmount;
|
||||||
|
@JsonProperty("posting_date")
|
||||||
|
private String postingDate;
|
||||||
|
@JsonProperty("reference_no")
|
||||||
|
private String referenceNo;
|
||||||
|
@JsonProperty("reference_date")
|
||||||
|
private String referenceDate;
|
||||||
|
private List<PaymentReferenceDto> references;
|
||||||
|
private int docstatus;
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package zw.qantra.tm.domain.dtos.erp;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class PaymentReferenceDto {
|
||||||
|
@JsonProperty("reference_doctype")
|
||||||
|
private String referenceDoctype;
|
||||||
|
@JsonProperty("reference_name")
|
||||||
|
private String referenceName;
|
||||||
|
@JsonProperty("allocated_amount")
|
||||||
|
private BigDecimal allocatedAmount;
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package zw.qantra.tm.domain.dtos.erp;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class PurchaseInvoiceDto {
|
||||||
|
|
||||||
|
private String doctype;
|
||||||
|
private String supplier;
|
||||||
|
@JsonProperty("posting_date")
|
||||||
|
private String postingDate;
|
||||||
|
@JsonProperty("due_date")
|
||||||
|
private String dueDate;
|
||||||
|
private String company;
|
||||||
|
@JsonProperty("is_return")
|
||||||
|
private int isReturn;
|
||||||
|
private String currency;
|
||||||
|
@JsonProperty("conversion_rate")
|
||||||
|
private int conversionRate;
|
||||||
|
@JsonProperty("net_total")
|
||||||
|
private BigDecimal netTotal;
|
||||||
|
@JsonProperty("grand_total")
|
||||||
|
private BigDecimal grandTotal;
|
||||||
|
@JsonProperty("is_paid")
|
||||||
|
private int isPaid;
|
||||||
|
private int docstatus;
|
||||||
|
private List<PurchaseInvoiceItemDto> items;
|
||||||
|
private List<PurchaseTaxDto> taxes;
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package zw.qantra.tm.domain.dtos.erp;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class PurchaseInvoiceItemDto {
|
||||||
|
|
||||||
|
private String doctype;
|
||||||
|
@JsonProperty("item_code")
|
||||||
|
private String itemCode;
|
||||||
|
@JsonProperty("item_name")
|
||||||
|
private String itemName;
|
||||||
|
private BigDecimal qty;
|
||||||
|
private int rate;
|
||||||
|
private BigDecimal amount;
|
||||||
|
private String uom;
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package zw.qantra.tm.domain.dtos.erp;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class PurchaseTaxDto {
|
||||||
|
|
||||||
|
private String doctype;
|
||||||
|
@JsonProperty("charge_type")
|
||||||
|
private String chargeType;
|
||||||
|
@JsonProperty("account_head")
|
||||||
|
private String accountHead;
|
||||||
|
private BigDecimal rate;
|
||||||
|
@JsonProperty("cost_center")
|
||||||
|
private String costCenter;
|
||||||
|
@JsonProperty("tax_amount")
|
||||||
|
private BigDecimal taxAmount;
|
||||||
|
private BigDecimal total;
|
||||||
|
private String description;
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package zw.qantra.tm.domain.dtos.erp;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class SalesInvoiceDto {
|
||||||
|
private String doctype;
|
||||||
|
private String customer;
|
||||||
|
@JsonProperty("posting_date")
|
||||||
|
private String postingDate;
|
||||||
|
@JsonProperty("due_date")
|
||||||
|
private String dueDate;
|
||||||
|
private String company;
|
||||||
|
private int docstatus;
|
||||||
|
private List<SalesInvoiceItemDto> items;
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package zw.qantra.tm.domain.dtos.erp;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class SalesInvoiceItemDto {
|
||||||
|
@JsonProperty("item_code")
|
||||||
|
private String itemCode;
|
||||||
|
private int qty;
|
||||||
|
private BigDecimal rate;
|
||||||
|
private BigDecimal amount;
|
||||||
|
}
|
||||||
@@ -4,5 +4,10 @@ public enum RequestType {
|
|||||||
CONFIRM,
|
CONFIRM,
|
||||||
REQUEST,
|
REQUEST,
|
||||||
POLL,
|
POLL,
|
||||||
INTEGRATION
|
INTEGRATION,
|
||||||
|
SALES_INVOICE,
|
||||||
|
JOURNAL_ENTRY,
|
||||||
|
PURCHASE_PAYMENT,
|
||||||
|
PURCHASE_INVOICE,
|
||||||
|
SALES_PAYMENT
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ import org.springframework.stereotype.Repository;
|
|||||||
import zw.qantra.tm.domain.models.User;
|
import zw.qantra.tm.domain.models.User;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface UserRepository extends JpaRepository<User, Long> {
|
public interface UserRepository extends JpaRepository<User, Long> {
|
||||||
|
Optional<User> findById(String uid);
|
||||||
Optional<User> findByUsername(String username);
|
Optional<User> findByUsername(String username);
|
||||||
Optional<User> findByEmail(String email);
|
Optional<User> findByEmail(String email);
|
||||||
Optional<User> findByPhone(String phone);
|
Optional<User> findByPhone(String phone);
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ import zw.qantra.tm.domain.services.processors.handlers.FormattingHandler;
|
|||||||
import zw.qantra.tm.domain.services.processors.handlers.LogicPipeline;
|
import zw.qantra.tm.domain.services.processors.handlers.LogicPipeline;
|
||||||
import zw.qantra.tm.domain.services.processors.handlers.RecipientsUpdateHandler;
|
import zw.qantra.tm.domain.services.processors.handlers.RecipientsUpdateHandler;
|
||||||
import zw.qantra.tm.domain.services.processors.handlers.ValidationHandler;
|
import zw.qantra.tm.domain.services.processors.handlers.ValidationHandler;
|
||||||
|
import zw.qantra.tm.domain.services.processors.handlers.erp.JournalEntryHandler;
|
||||||
|
import zw.qantra.tm.domain.services.processors.handlers.erp.PaymentEntryHandler;
|
||||||
import zw.qantra.tm.domain.services.processors.handlers.erp.PurchaseInvoiceHandler;
|
import zw.qantra.tm.domain.services.processors.handlers.erp.PurchaseInvoiceHandler;
|
||||||
import zw.qantra.tm.domain.services.processors.handlers.erp.SalesInvoiceHandler;
|
import zw.qantra.tm.domain.services.processors.handlers.erp.SalesInvoiceHandler;
|
||||||
import zw.qantra.tm.domain.models.TransactionEvent;
|
import zw.qantra.tm.domain.models.TransactionEvent;
|
||||||
@@ -59,7 +61,9 @@ public class TransactionService {
|
|||||||
transactionEventService.save(transactionEvent);
|
transactionEventService.save(transactionEvent);
|
||||||
|
|
||||||
LogicPipeline<Transaction, Transaction> transactionLogic =
|
LogicPipeline<Transaction, Transaction> transactionLogic =
|
||||||
new LogicPipeline<>(new ValidationHandler());
|
new LogicPipeline<>(new ValidationHandler())
|
||||||
|
.addHandler(new JournalEntryHandler()) // todo: do this in a separate thread
|
||||||
|
.addHandler(new PaymentEntryHandler()); // todo: do this in a separate thread
|
||||||
transaction = transactionLogic.execute(transaction);
|
transaction = transactionLogic.execute(transaction);
|
||||||
|
|
||||||
Provider provider = providerService.getProviderRepository()
|
Provider provider = providerService.getProviderRepository()
|
||||||
@@ -90,7 +94,8 @@ public class TransactionService {
|
|||||||
|
|
||||||
// run post integration logic
|
// run post integration logic
|
||||||
LogicPipeline<Transaction, Transaction> postTransactionLogic =
|
LogicPipeline<Transaction, Transaction> postTransactionLogic =
|
||||||
new LogicPipeline<>(new PurchaseInvoiceHandler());
|
new LogicPipeline<>(new PurchaseInvoiceHandler()) // todo: do this in a separate thread
|
||||||
|
.addHandler(new PurchaseInvoiceHandler()); // todo: do this in a separate thread
|
||||||
transaction = postTransactionLogic.execute(transaction);
|
transaction = postTransactionLogic.execute(transaction);
|
||||||
|
|
||||||
transactionEvent.setStatus(transaction.getStatus());
|
transactionEvent.setStatus(transaction.getStatus());
|
||||||
@@ -199,7 +204,7 @@ public class TransactionService {
|
|||||||
// run post transaction logic
|
// run post transaction logic
|
||||||
LogicPipeline<Transaction, Transaction> postTransactionLogic =
|
LogicPipeline<Transaction, Transaction> postTransactionLogic =
|
||||||
new LogicPipeline<>(new RecipientsUpdateHandler(recipientService))
|
new LogicPipeline<>(new RecipientsUpdateHandler(recipientService))
|
||||||
.addHandler(new SalesInvoiceHandler());
|
.addHandler(new SalesInvoiceHandler());// todo: do this in a separate thread
|
||||||
transaction = postTransactionLogic.execute(transaction);
|
transaction = postTransactionLogic.execute(transaction);
|
||||||
|
|
||||||
transactionEvent.setStatus(transaction.getStatus());
|
transactionEvent.setStatus(transaction.getStatus());
|
||||||
|
|||||||
@@ -0,0 +1,169 @@
|
|||||||
|
package zw.qantra.tm.domain.services.processors.handlers.erp;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import zw.qantra.tm.domain.dtos.erp.JournalEntryAccountDto;
|
||||||
|
import zw.qantra.tm.domain.dtos.erp.JournalEntryDto;
|
||||||
|
import zw.qantra.tm.domain.dtos.erp.SalesInvoiceDto;
|
||||||
|
import zw.qantra.tm.domain.dtos.erp.SalesInvoiceItemDto;
|
||||||
|
import zw.qantra.tm.domain.enums.RequestType;
|
||||||
|
import zw.qantra.tm.domain.enums.Status;
|
||||||
|
import zw.qantra.tm.domain.models.Transaction;
|
||||||
|
import zw.qantra.tm.domain.models.TransactionEvent;
|
||||||
|
import zw.qantra.tm.domain.models.User;
|
||||||
|
import zw.qantra.tm.domain.services.TransactionEventService;
|
||||||
|
import zw.qantra.tm.domain.services.TransactionService;
|
||||||
|
import zw.qantra.tm.domain.services.UserService;
|
||||||
|
import zw.qantra.tm.domain.services.processors.handlers.LogicHandler;
|
||||||
|
import zw.qantra.tm.exceptions.ApiException;
|
||||||
|
import zw.qantra.tm.rest.RestService;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class JournalEntryHandler implements LogicHandler<Transaction, Transaction>{
|
||||||
|
Logger logger = LoggerFactory.getLogger(JournalEntryHandler.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RestService restService;
|
||||||
|
@Autowired
|
||||||
|
private UserService userService;
|
||||||
|
@Autowired
|
||||||
|
private TransactionEventService transactionEventService;
|
||||||
|
@Autowired
|
||||||
|
private TransactionService transactionService;
|
||||||
|
|
||||||
|
@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;
|
||||||
|
@Value("${erpnext.company}")
|
||||||
|
private String erpnextCompany;
|
||||||
|
@Value("${erpnext.accounts.gatewayexpense}")
|
||||||
|
private String gatewayExpenseAccount;
|
||||||
|
@Value("${erpnext.accounts.accountsreceivable}")
|
||||||
|
private String accountsReceivableAccount;
|
||||||
|
@Value("${erpnext.accounts.cash}")
|
||||||
|
private String cashAccount;
|
||||||
|
@Value("${erpnext.cost.center}")
|
||||||
|
private String costCenter;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Transaction process(Transaction transaction) {
|
||||||
|
logger.info("Processing journal entry for transaction: {}", transaction.getReference());
|
||||||
|
TransactionEvent transactionEvent = TransactionEvent.builder()
|
||||||
|
.transactionId(transaction.getId().toString())
|
||||||
|
.event(RequestType.JOURNAL_ENTRY.name())
|
||||||
|
.status(Status.PROCESSING)
|
||||||
|
.build();
|
||||||
|
transactionEventService.save(transactionEvent);
|
||||||
|
|
||||||
|
if(transaction.getStatus() == Status.SUCCESS){
|
||||||
|
try {
|
||||||
|
logger.info("Creating journal entry in ERPNext for successful transaction");
|
||||||
|
|
||||||
|
// login to erp
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.add("Content-Type", "application/json");
|
||||||
|
headers.add("Accept", "application/json");
|
||||||
|
|
||||||
|
Map<String, String> 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();
|
||||||
|
throw new ApiException("Failed to login to ERPNext");
|
||||||
|
}
|
||||||
|
|
||||||
|
// create customer in erp
|
||||||
|
String basicAuth = "Basic " + Base64.getEncoder().encodeToString(
|
||||||
|
(erpnextApiKey + ":" + erpnextApiSecret).getBytes(StandardCharsets.UTF_8));
|
||||||
|
headers.add("Authorization", basicAuth);
|
||||||
|
headers.add("Cookie", sid);
|
||||||
|
|
||||||
|
User user = userService.getUserRepository().findById(transaction.getUserId()).orElse(null);
|
||||||
|
if(user == null)
|
||||||
|
throw new ApiException("Could not find user");
|
||||||
|
|
||||||
|
JournalEntryAccountDto cashAccEntry = JournalEntryAccountDto.builder()
|
||||||
|
.doctype("Journal Entry Account")
|
||||||
|
.account(cashAccount)
|
||||||
|
.debitInAccountCurrency(transaction.getAmount())
|
||||||
|
.costCenter(costCenter)
|
||||||
|
.build();
|
||||||
|
JournalEntryAccountDto expenseEntry = JournalEntryAccountDto.builder()
|
||||||
|
.doctype("Journal Entry Account")
|
||||||
|
.account(gatewayExpenseAccount)
|
||||||
|
.debitInAccountCurrency(transaction.getGatewayCharge())
|
||||||
|
.costCenter(costCenter)
|
||||||
|
.build();
|
||||||
|
JournalEntryAccountDto accRecievable = JournalEntryAccountDto.builder()
|
||||||
|
.doctype("Journal Entry Account")
|
||||||
|
.account(accountsReceivableAccount)
|
||||||
|
.creditInAccountCurrency(transaction.getAmount().add(transaction.getGatewayCharge()))
|
||||||
|
.party("Customer")
|
||||||
|
.costCenter(costCenter)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
JournalEntryDto journalEntryDto = JournalEntryDto.builder()
|
||||||
|
.doctype("Journal Entry")
|
||||||
|
.company(erpnextCompany)
|
||||||
|
.postingDate(transaction.getCreatedAt().toLocalDate().toString())
|
||||||
|
.docstatus(1)
|
||||||
|
.accounts(Arrays.asList(cashAccEntry, expenseEntry, accRecievable))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
try {
|
||||||
|
LinkedHashMap response = restService.postAs(
|
||||||
|
erpnextUrl + "/v2/document/Journal Entry", journalEntryDto, headers, LinkedHashMap.class);
|
||||||
|
|
||||||
|
LinkedHashMap data = (LinkedHashMap) response.get("data");
|
||||||
|
String transactionId = (String) data.get("name");
|
||||||
|
|
||||||
|
transaction.setErpJournalRef(transactionId);
|
||||||
|
transactionEvent.setStatus(Status.SUCCESS);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
throw new ApiException("Failed to create journal entry in ERPNext");
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("journal entry created successfully in ERPNext");
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Failed to create journal entry in ERPNext: {}", e.getMessage(), e);
|
||||||
|
// Don't fail the transaction, just log the error
|
||||||
|
// The ERP invoice creation is a side effect, not critical to the payment flow
|
||||||
|
transactionEvent.setStatus(Status.FAILED);
|
||||||
|
transactionEvent.setMessage(e.getMessage());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.info("Transaction status is not SUCCESS ({}), skipping journal entry creation", transaction.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
transactionEventService.save(transactionEvent);
|
||||||
|
|
||||||
|
return transactionService.save(transaction);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
package zw.qantra.tm.domain.services.processors.handlers.erp;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import zw.qantra.tm.domain.dtos.erp.PaymentEntryDto;
|
||||||
|
import zw.qantra.tm.domain.dtos.erp.PaymentReferenceDto;
|
||||||
|
import zw.qantra.tm.domain.enums.RequestType;
|
||||||
|
import zw.qantra.tm.domain.enums.Status;
|
||||||
|
import zw.qantra.tm.domain.models.Transaction;
|
||||||
|
import zw.qantra.tm.domain.models.TransactionEvent;
|
||||||
|
import zw.qantra.tm.domain.models.User;
|
||||||
|
import zw.qantra.tm.domain.services.TransactionEventService;
|
||||||
|
import zw.qantra.tm.domain.services.TransactionService;
|
||||||
|
import zw.qantra.tm.domain.services.UserService;
|
||||||
|
import zw.qantra.tm.domain.services.processors.handlers.LogicHandler;
|
||||||
|
import zw.qantra.tm.exceptions.ApiException;
|
||||||
|
import zw.qantra.tm.rest.RestService;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class PaymentEntryHandler implements LogicHandler<Transaction, Transaction>{
|
||||||
|
Logger logger = LoggerFactory.getLogger(PaymentEntryHandler.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RestService restService;
|
||||||
|
@Autowired
|
||||||
|
private UserService userService;
|
||||||
|
@Autowired
|
||||||
|
private TransactionEventService transactionEventService;
|
||||||
|
@Autowired
|
||||||
|
private TransactionService transactionService;
|
||||||
|
|
||||||
|
@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;
|
||||||
|
@Value("${erpnext.company}")
|
||||||
|
private String erpnextCompany;
|
||||||
|
@Value("${erpnext.accounts.accountsreceivable}")
|
||||||
|
private String accountsReceivableAccount;
|
||||||
|
@Value("${erpnext.accounts.cash}")
|
||||||
|
private String cashAccount;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Transaction process(Transaction transaction) {
|
||||||
|
logger.info("Processing payment entry for transaction: {}", transaction.getReference());
|
||||||
|
TransactionEvent transactionEvent = TransactionEvent.builder()
|
||||||
|
.transactionId(transaction.getId().toString())
|
||||||
|
.event(RequestType.SALES_PAYMENT.name())
|
||||||
|
.status(Status.PROCESSING)
|
||||||
|
.build();
|
||||||
|
transactionEventService.save(transactionEvent);
|
||||||
|
|
||||||
|
if(transaction.getStatus() == Status.SUCCESS){
|
||||||
|
try {
|
||||||
|
logger.info("Creating payment entry in ERPNext for successful transaction");
|
||||||
|
|
||||||
|
// login to erp
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.add("Content-Type", "application/json");
|
||||||
|
headers.add("Accept", "application/json");
|
||||||
|
|
||||||
|
Map<String, String> 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();
|
||||||
|
throw new ApiException("Failed to login to ERPNext");
|
||||||
|
}
|
||||||
|
|
||||||
|
// create customer in erp
|
||||||
|
String basicAuth = "Basic " + Base64.getEncoder().encodeToString(
|
||||||
|
(erpnextApiKey + ":" + erpnextApiSecret).getBytes(StandardCharsets.UTF_8));
|
||||||
|
headers.add("Authorization", basicAuth);
|
||||||
|
headers.add("Cookie", sid);
|
||||||
|
|
||||||
|
User user = userService.getUserRepository().findById(transaction.getUserId()).orElse(null);
|
||||||
|
if(user == null)
|
||||||
|
throw new ApiException("Could not find user");
|
||||||
|
|
||||||
|
BigDecimal amount = transaction.getAmount().add(transaction.getGatewayCharge());
|
||||||
|
|
||||||
|
PaymentReferenceDto paymentReferenceDto = PaymentReferenceDto.builder()
|
||||||
|
.referenceDoctype("Sales Invoice")
|
||||||
|
.referenceName(transaction.getErpSalesRef())
|
||||||
|
.allocatedAmount(amount)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
PaymentEntryDto paymentEntryDto = PaymentEntryDto.builder()
|
||||||
|
.doctype("Payment Entry")
|
||||||
|
.company(erpnextCompany)
|
||||||
|
.docstatus(1)
|
||||||
|
.paymentType("Receive")
|
||||||
|
.party(user.getErpCustomerRef())
|
||||||
|
.partyType("Customer")
|
||||||
|
.paidFrom(accountsReceivableAccount)
|
||||||
|
.paidTo(cashAccount)
|
||||||
|
.paidAmount(amount)
|
||||||
|
.receivedAmount(amount)
|
||||||
|
.referenceNo(transaction.getDebitRef())
|
||||||
|
.referenceDate(transaction.getCreatedAt().toLocalDate().toString())
|
||||||
|
.postingDate(transaction.getCreatedAt().toLocalDate().toString())
|
||||||
|
.references(Arrays.asList(paymentReferenceDto))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
try {
|
||||||
|
LinkedHashMap response = restService.postAs(
|
||||||
|
erpnextUrl + "/v2/document/Payment Entry", paymentEntryDto, headers, LinkedHashMap.class);
|
||||||
|
|
||||||
|
LinkedHashMap data = (LinkedHashMap) response.get("data");
|
||||||
|
String transactionId = (String) data.get("name");
|
||||||
|
|
||||||
|
transaction.setErpSalesPaymentRef(transactionId);
|
||||||
|
transactionEvent.setStatus(Status.SUCCESS);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
throw new ApiException("Failed to create payment entry in ERPNext");
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("payment entry created successfully in ERPNext");
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Failed to create payment entry in ERPNext: {}", e.getMessage(), e);
|
||||||
|
// Don't fail the transaction, just log the error
|
||||||
|
// The ERP invoice creation is a side effect, not critical to the payment flow
|
||||||
|
transactionEvent.setStatus(Status.FAILED);
|
||||||
|
transactionEvent.setMessage(e.getMessage());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.info("Transaction status is not SUCCESS ({}), skipping payment entry creation", transaction.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
transactionEventService.save(transactionEvent);
|
||||||
|
|
||||||
|
return transactionService.save(transaction);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,21 +1,169 @@
|
|||||||
package zw.qantra.tm.domain.services.processors.handlers.erp;
|
package zw.qantra.tm.domain.services.processors.handlers.erp;
|
||||||
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import zw.qantra.tm.domain.services.processors.handlers.LogicHandler;
|
|
||||||
import zw.qantra.tm.domain.models.Transaction;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import zw.qantra.tm.domain.dtos.erp.*;
|
||||||
|
import zw.qantra.tm.domain.enums.RequestType;
|
||||||
|
import zw.qantra.tm.domain.enums.Status;
|
||||||
|
import zw.qantra.tm.domain.models.Transaction;
|
||||||
|
import zw.qantra.tm.domain.models.TransactionEvent;
|
||||||
|
import zw.qantra.tm.domain.models.User;
|
||||||
|
import zw.qantra.tm.domain.services.TransactionEventService;
|
||||||
|
import zw.qantra.tm.domain.services.TransactionService;
|
||||||
|
import zw.qantra.tm.domain.services.UserService;
|
||||||
|
import zw.qantra.tm.domain.services.processors.handlers.LogicHandler;
|
||||||
|
import zw.qantra.tm.exceptions.ApiException;
|
||||||
|
import zw.qantra.tm.rest.RestService;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class PurchaseInvoiceHandler implements LogicHandler<Transaction, Transaction>{
|
public class PurchaseInvoiceHandler implements LogicHandler<Transaction, Transaction>{
|
||||||
Logger logger = LoggerFactory.getLogger(PurchaseInvoiceHandler.class);
|
Logger logger = LoggerFactory.getLogger(PurchaseInvoiceHandler.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RestService restService;
|
||||||
|
@Autowired
|
||||||
|
private UserService userService;
|
||||||
|
@Autowired
|
||||||
|
private TransactionEventService transactionEventService;
|
||||||
|
@Autowired
|
||||||
|
private TransactionService transactionService;
|
||||||
|
|
||||||
|
@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;
|
||||||
|
@Value("${erpnext.company}")
|
||||||
|
private String erpnextCompany;
|
||||||
|
@Value("${erpnext.cost.center}")
|
||||||
|
private String erpnextCostCenter;
|
||||||
|
@Value("${erpnext.accounts.commission}")
|
||||||
|
private String erpnextCommissionAccount;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Transaction process(Transaction transaction) {
|
public Transaction process(Transaction transaction) {
|
||||||
logger.info("processing purchase invoice");
|
logger.info("Processing purchase invoice for transaction: {}", transaction.getReference());
|
||||||
return transaction;
|
TransactionEvent transactionEvent = TransactionEvent.builder()
|
||||||
|
.transactionId(transaction.getId().toString())
|
||||||
|
.event(RequestType.PURCHASE_INVOICE.name())
|
||||||
|
.status(Status.PROCESSING)
|
||||||
|
.build();
|
||||||
|
transactionEventService.save(transactionEvent);
|
||||||
|
|
||||||
|
if(transaction.getStatus() == Status.SUCCESS){
|
||||||
|
try {
|
||||||
|
logger.info("Creating purchase invoice in ERPNext for successful transaction");
|
||||||
|
|
||||||
|
// login to erp
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.add("Content-Type", "application/json");
|
||||||
|
headers.add("Accept", "application/json");
|
||||||
|
|
||||||
|
Map<String, String> 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();
|
||||||
|
throw new ApiException("Failed to login to ERPNext");
|
||||||
|
}
|
||||||
|
|
||||||
|
// create customer in erp
|
||||||
|
String basicAuth = "Basic " + Base64.getEncoder().encodeToString(
|
||||||
|
(erpnextApiKey + ":" + erpnextApiSecret).getBytes(StandardCharsets.UTF_8));
|
||||||
|
headers.add("Authorization", basicAuth);
|
||||||
|
headers.add("Cookie", sid);
|
||||||
|
|
||||||
|
User user = userService.getUserRepository().findById(transaction.getUserId()).orElse(null);
|
||||||
|
if(user == null)
|
||||||
|
throw new ApiException("Could not find user");
|
||||||
|
|
||||||
|
PurchaseInvoiceItemDto purchaseInvoiceItemDto = PurchaseInvoiceItemDto.builder()
|
||||||
|
.doctype("Purchase Invoice Item")
|
||||||
|
.uom("Unit")
|
||||||
|
.itemCode(transaction.getBillName())
|
||||||
|
.itemName(transaction.getBillName())
|
||||||
|
.qty(transaction.getAmount())
|
||||||
|
.rate(1)
|
||||||
|
.amount(transaction.getAmount())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
PurchaseTaxDto purchaseTaxDto = PurchaseTaxDto.builder()
|
||||||
|
.doctype("Purchase Tax")
|
||||||
|
.chargeType("On Net Total")
|
||||||
|
.accountHead(erpnextCommissionAccount)
|
||||||
|
.rate(transaction.getGatewayCharge().negate())
|
||||||
|
.costCenter(erpnextCostCenter)
|
||||||
|
.taxAmount(transaction.getGatewayCharge().negate())
|
||||||
|
.total(transaction.getAmount().subtract(transaction.getGatewayCharge()))
|
||||||
|
.description("Discounts Received")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
PurchaseInvoiceDto purchaseInvoiceDto = PurchaseInvoiceDto.builder()
|
||||||
|
.doctype("Purchase Invoice")
|
||||||
|
.supplier("Steward Bank")
|
||||||
|
.company(erpnextCompany)
|
||||||
|
.postingDate(transaction.getCreatedAt().toLocalDate().toString())
|
||||||
|
.dueDate(transaction.getCreatedAt().toLocalDate().plusDays(1).toString())
|
||||||
|
.docstatus(1)
|
||||||
|
.isReturn(0)
|
||||||
|
.isPaid(0)
|
||||||
|
.currency(transaction.getDebitCurrency().toString())
|
||||||
|
.conversionRate(1)
|
||||||
|
.netTotal(transaction.getAmount().subtract(transaction.getGatewayCharge()))
|
||||||
|
.grandTotal(transaction.getAmount().subtract(transaction.getGatewayCharge()))
|
||||||
|
.items(Collections.singletonList(purchaseInvoiceItemDto))
|
||||||
|
.taxes(Collections.singletonList(purchaseTaxDto))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
try {
|
||||||
|
LinkedHashMap response = restService.postAs(
|
||||||
|
erpnextUrl + "/v2/document/Purchase Invoice", purchaseInvoiceDto, headers, LinkedHashMap.class);
|
||||||
|
|
||||||
|
LinkedHashMap data = (LinkedHashMap) response.get("data");
|
||||||
|
String transactionId = (String) data.get("name");
|
||||||
|
|
||||||
|
transaction.setErpPurchaseRef(transactionId);
|
||||||
|
transactionEvent.setStatus(Status.SUCCESS);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
throw new ApiException("Failed to create purchase invoice in ERPNext");
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("purchase invoice created successfully in ERPNext");
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Failed to create purchase invoice in ERPNext: {}", e.getMessage(), e);
|
||||||
|
// Don't fail the transaction, just log the error
|
||||||
|
// The ERP invoice creation is a side effect, not critical to the payment flow
|
||||||
|
transactionEvent.setStatus(Status.FAILED);
|
||||||
|
transactionEvent.setMessage(e.getMessage());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.info("Transaction status is not SUCCESS ({}), skipping purchase invoice creation", transaction.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
transactionEventService.save(transactionEvent);
|
||||||
|
|
||||||
|
return transactionService.save(transaction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,163 @@
|
|||||||
|
package zw.qantra.tm.domain.services.processors.handlers.erp;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import zw.qantra.tm.domain.dtos.erp.PaymentEntryDto;
|
||||||
|
import zw.qantra.tm.domain.dtos.erp.PaymentReferenceDto;
|
||||||
|
import zw.qantra.tm.domain.enums.RequestType;
|
||||||
|
import zw.qantra.tm.domain.enums.Status;
|
||||||
|
import zw.qantra.tm.domain.models.Transaction;
|
||||||
|
import zw.qantra.tm.domain.models.TransactionEvent;
|
||||||
|
import zw.qantra.tm.domain.models.User;
|
||||||
|
import zw.qantra.tm.domain.services.TransactionEventService;
|
||||||
|
import zw.qantra.tm.domain.services.TransactionService;
|
||||||
|
import zw.qantra.tm.domain.services.UserService;
|
||||||
|
import zw.qantra.tm.domain.services.processors.handlers.LogicHandler;
|
||||||
|
import zw.qantra.tm.exceptions.ApiException;
|
||||||
|
import zw.qantra.tm.rest.RestService;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class PurchasePaymentEntryHandler implements LogicHandler<Transaction, Transaction>{
|
||||||
|
Logger logger = LoggerFactory.getLogger(PurchasePaymentEntryHandler.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RestService restService;
|
||||||
|
@Autowired
|
||||||
|
private UserService userService;
|
||||||
|
@Autowired
|
||||||
|
private TransactionEventService transactionEventService;
|
||||||
|
@Autowired
|
||||||
|
private TransactionService transactionService;
|
||||||
|
|
||||||
|
@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;
|
||||||
|
@Value("${erpnext.company}")
|
||||||
|
private String erpnextCompany;
|
||||||
|
@Value("${erpnext.accounts.accountsreceivable}")
|
||||||
|
private String accountsReceivableAccount;
|
||||||
|
@Value("${erpnext.accounts.cash}")
|
||||||
|
private String cashAccount;
|
||||||
|
@Value("erpnext.accounts.payable")
|
||||||
|
private String payableAccount;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Transaction process(Transaction transaction) {
|
||||||
|
logger.info("Processing purchase payment entry for transaction: {}", transaction.getReference());
|
||||||
|
TransactionEvent transactionEvent = TransactionEvent.builder()
|
||||||
|
.transactionId(transaction.getId().toString())
|
||||||
|
.event(RequestType.PURCHASE_PAYMENT.name())
|
||||||
|
.status(Status.PROCESSING)
|
||||||
|
.build();
|
||||||
|
transactionEventService.save(transactionEvent);
|
||||||
|
|
||||||
|
if(transaction.getStatus() == Status.SUCCESS){
|
||||||
|
try {
|
||||||
|
logger.info("Creating purchase payment entry in ERPNext for successful transaction");
|
||||||
|
|
||||||
|
// login to erp
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.add("Content-Type", "application/json");
|
||||||
|
headers.add("Accept", "application/json");
|
||||||
|
|
||||||
|
Map<String, String> 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();
|
||||||
|
throw new ApiException("Failed to login to ERPNext");
|
||||||
|
}
|
||||||
|
|
||||||
|
// create customer in erp
|
||||||
|
String basicAuth = "Basic " + Base64.getEncoder().encodeToString(
|
||||||
|
(erpnextApiKey + ":" + erpnextApiSecret).getBytes(StandardCharsets.UTF_8));
|
||||||
|
headers.add("Authorization", basicAuth);
|
||||||
|
headers.add("Cookie", sid);
|
||||||
|
|
||||||
|
User user = userService.getUserRepository().findById(transaction.getUserId()).orElse(null);
|
||||||
|
if(user == null)
|
||||||
|
throw new ApiException("Could not find user");
|
||||||
|
|
||||||
|
BigDecimal amount = transaction.getAmount().subtract(transaction.getGatewayCharge());
|
||||||
|
|
||||||
|
PaymentReferenceDto paymentReferenceDto = PaymentReferenceDto.builder()
|
||||||
|
.referenceDoctype("Purchase Invoice")
|
||||||
|
.referenceName(transaction.getErpPurchaseRef())
|
||||||
|
.allocatedAmount(amount)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
PaymentEntryDto paymentEntryDto = PaymentEntryDto.builder()
|
||||||
|
.doctype("Payment Entry")
|
||||||
|
.company(erpnextCompany)
|
||||||
|
.docstatus(1)
|
||||||
|
.paymentType("Pay")
|
||||||
|
.party("Steward Bank")
|
||||||
|
.partyType("Supplier")
|
||||||
|
.paidFrom(cashAccount)
|
||||||
|
.paidTo(payableAccount)
|
||||||
|
.paidAmount(amount)
|
||||||
|
.receivedAmount(amount)
|
||||||
|
.referenceNo(transaction.getCreditRef())
|
||||||
|
.referenceDate(transaction.getCreatedAt().toLocalDate().toString())
|
||||||
|
.postingDate(transaction.getCreatedAt().toLocalDate().toString())
|
||||||
|
.references(Arrays.asList(paymentReferenceDto))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
try {
|
||||||
|
LinkedHashMap response = restService.postAs(
|
||||||
|
erpnextUrl + "/v2/document/Payment Entry", paymentEntryDto, headers, LinkedHashMap.class);
|
||||||
|
|
||||||
|
LinkedHashMap data = (LinkedHashMap) response.get("data");
|
||||||
|
String transactionId = (String) data.get("name");
|
||||||
|
|
||||||
|
transaction.setErpPurchasePaymentRef(transactionId);
|
||||||
|
transactionEvent.setStatus(Status.SUCCESS);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
throw new ApiException("Failed to create purchase payment entry in ERPNext");
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("purchase payment entry created successfully in ERPNext");
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Failed to create purchase payment entry in ERPNext: {}", e.getMessage(), e);
|
||||||
|
// Don't fail the transaction, just log the error
|
||||||
|
// The ERP invoice creation is a side effect, not critical to the payment flow
|
||||||
|
transactionEvent.setStatus(Status.FAILED);
|
||||||
|
transactionEvent.setMessage(e.getMessage());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.info("Transaction status is not SUCCESS ({}), skipping purchase payment entry creation", transaction.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
transactionEventService.save(transactionEvent);
|
||||||
|
|
||||||
|
return transactionService.save(transaction);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,38 +2,146 @@ package zw.qantra.tm.domain.services.processors.handlers.erp;
|
|||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import zw.qantra.tm.domain.dtos.erp.SalesInvoiceDto;
|
||||||
|
import zw.qantra.tm.domain.dtos.erp.SalesInvoiceItemDto;
|
||||||
|
import zw.qantra.tm.domain.enums.RequestType;
|
||||||
import zw.qantra.tm.domain.enums.Status;
|
import zw.qantra.tm.domain.enums.Status;
|
||||||
import zw.qantra.tm.domain.models.Transaction;
|
import zw.qantra.tm.domain.models.Transaction;
|
||||||
|
import zw.qantra.tm.domain.models.TransactionEvent;
|
||||||
|
import zw.qantra.tm.domain.models.User;
|
||||||
|
import zw.qantra.tm.domain.services.TransactionEventService;
|
||||||
|
import zw.qantra.tm.domain.services.TransactionService;
|
||||||
|
import zw.qantra.tm.domain.services.UserService;
|
||||||
import zw.qantra.tm.domain.services.processors.handlers.LogicHandler;
|
import zw.qantra.tm.domain.services.processors.handlers.LogicHandler;
|
||||||
|
import zw.qantra.tm.exceptions.ApiException;
|
||||||
|
import zw.qantra.tm.rest.RestService;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class SalesInvoiceHandler implements LogicHandler<Transaction, Transaction>{
|
public class SalesInvoiceHandler implements LogicHandler<Transaction, Transaction>{
|
||||||
Logger logger = LoggerFactory.getLogger(SalesInvoiceHandler.class);
|
Logger logger = LoggerFactory.getLogger(SalesInvoiceHandler.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RestService restService;
|
||||||
|
@Autowired
|
||||||
|
private UserService userService;
|
||||||
|
@Autowired
|
||||||
|
private TransactionEventService transactionEventService;
|
||||||
|
@Autowired
|
||||||
|
private TransactionService transactionService;
|
||||||
|
|
||||||
|
@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;
|
||||||
|
@Value("${erpnext.company}")
|
||||||
|
private String erpnextCompany;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Transaction process(Transaction transaction) {
|
public Transaction process(Transaction transaction) {
|
||||||
logger.info("Processing sales invoice for transaction: {}", transaction.getReference());
|
logger.info("Processing sales invoice for transaction: {}", transaction.getReference());
|
||||||
|
TransactionEvent transactionEvent = TransactionEvent.builder()
|
||||||
|
.transactionId(transaction.getId().toString())
|
||||||
|
.event(RequestType.SALES_INVOICE.name())
|
||||||
|
.status(Status.PROCESSING)
|
||||||
|
.build();
|
||||||
|
transactionEventService.save(transactionEvent);
|
||||||
|
|
||||||
if(transaction.getStatus() == Status.SUCCESS){
|
if(transaction.getStatus() == Status.SUCCESS){
|
||||||
try {
|
try {
|
||||||
logger.info("Creating sales invoice in ERPNext for successful transaction");
|
logger.info("Creating sales invoice in ERPNext for successful transaction");
|
||||||
|
|
||||||
|
// login to erp
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.add("Content-Type", "application/json");
|
||||||
|
headers.add("Accept", "application/json");
|
||||||
|
|
||||||
|
Map<String, String> 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();
|
||||||
|
throw new ApiException("Failed to login to ERPNext");
|
||||||
|
}
|
||||||
|
|
||||||
|
// create customer in erp
|
||||||
|
String basicAuth = "Basic " + Base64.getEncoder().encodeToString(
|
||||||
|
(erpnextApiKey + ":" + erpnextApiSecret).getBytes(StandardCharsets.UTF_8));
|
||||||
|
headers.add("Authorization", basicAuth);
|
||||||
|
headers.add("Cookie", sid);
|
||||||
|
|
||||||
|
User user = userService.getUserRepository().findById(transaction.getUserId()).orElse(null);
|
||||||
|
if(user == null)
|
||||||
|
throw new ApiException("Could not find user");
|
||||||
|
|
||||||
|
SalesInvoiceDto salesInvoiceDto = SalesInvoiceDto.builder()
|
||||||
|
.doctype("Sales Invoice")
|
||||||
|
.customer(user.getErpCustomerRef())
|
||||||
|
.postingDate(transaction.getCreatedAt().toLocalDate().toString())
|
||||||
|
.dueDate(transaction.getCreatedAt().toLocalDate().toString())
|
||||||
|
.company(erpnextCompany)
|
||||||
|
.docstatus(1)
|
||||||
|
.items(Arrays.asList(SalesInvoiceItemDto.builder()
|
||||||
|
.itemCode(transaction.getBillName())
|
||||||
|
.qty(1)
|
||||||
|
.rate(transaction.getAmount().add(transaction.getGatewayCharge()))
|
||||||
|
.amount(transaction.getAmount().add(transaction.getGatewayCharge()))
|
||||||
|
.build()))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
try {
|
||||||
|
LinkedHashMap response = restService.postAs(
|
||||||
|
erpnextUrl + "/v2/document/Sales Invoice", salesInvoiceDto, headers, LinkedHashMap.class);
|
||||||
|
|
||||||
|
LinkedHashMap data = (LinkedHashMap) response.get("data");
|
||||||
|
String transactionId = (String) data.get("name");
|
||||||
|
|
||||||
|
transaction.setErpSalesRef(transactionId);
|
||||||
|
transactionEvent.setStatus(Status.SUCCESS);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
throw new ApiException("Failed to create sales invoice in ERPNext");
|
||||||
|
}
|
||||||
|
|
||||||
logger.info("Sales invoice created successfully in ERPNext");
|
logger.info("Sales invoice created successfully in ERPNext");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Failed to create sales invoice in ERPNext: {}", e.getMessage(), e);
|
logger.error("Failed to create sales invoice in ERPNext: {}", e.getMessage(), e);
|
||||||
// Don't fail the transaction, just log the error
|
// Don't fail the transaction, just log the error
|
||||||
// The ERP invoice creation is a side effect, not critical to the payment flow
|
// The ERP invoice creation is a side effect, not critical to the payment flow
|
||||||
|
transactionEvent.setStatus(Status.FAILED);
|
||||||
|
transactionEvent.setMessage(e.getMessage());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.info("Transaction status is not SUCCESS ({}), skipping sales invoice creation", transaction.getStatus());
|
logger.info("Transaction status is not SUCCESS ({}), skipping sales invoice creation", transaction.getStatus());
|
||||||
}
|
}
|
||||||
|
|
||||||
return transaction;
|
transactionEventService.save(transactionEvent);
|
||||||
|
|
||||||
|
return transactionService.save(transaction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,9 +34,13 @@ erpnext.api.key=1102c6da0312f75
|
|||||||
erpnext.api.secret=e3d95692bcf16c7
|
erpnext.api.secret=e3d95692bcf16c7
|
||||||
erpnext.username=Administrator
|
erpnext.username=Administrator
|
||||||
erpnext.password=admin
|
erpnext.password=admin
|
||||||
erpnext.company=Qantra Payments
|
erpnext.company=Qantra
|
||||||
erpnext.item=Payment Service
|
erpnext.accounts.cash=Bank Account - Q
|
||||||
erpnext.income.account=5111 - Sales Income - Q
|
erpnext.accounts.gatewayexpense=Gateway Expenses - Q
|
||||||
|
erpnext.accounts.accountsreceivable=Debtors - Q
|
||||||
|
erpnext.accounts.commission=Discounts Received - Q
|
||||||
|
erpnext.accounts.payable=Creditors - Q
|
||||||
|
erpnext.income.account=Sales Income - Q
|
||||||
erpnext.cost.center=Main - Q
|
erpnext.cost.center=Main - Q
|
||||||
erpnext.tax.account=VAT - Q
|
erpnext.tax.account=VAT - Q
|
||||||
erpnext.tax.rate=15.0
|
erpnext.tax.rate=15.0
|
||||||
|
|||||||
Reference in New Issue
Block a user