322 lines
9.3 KiB
Go
322 lines
9.3 KiB
Go
package order
|
|
|
|
import (
|
|
"enaklo-pos-be/internal/common/logger"
|
|
"enaklo-pos-be/internal/common/mycontext"
|
|
"enaklo-pos-be/internal/constants"
|
|
"enaklo-pos-be/internal/entity"
|
|
"fmt"
|
|
"github.com/pkg/errors"
|
|
"go.uber.org/zap"
|
|
"time"
|
|
)
|
|
|
|
func (s *orderSvc) ExecuteOrderInquiry(ctx mycontext.Context,
|
|
token string, paymentMethod, paymentProvider string, inprogressOrderID int64) (*entity.OrderResponse, error) {
|
|
inquiry, err := s.validateInquiry(ctx, token)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
order := inquiry.ToOrder(paymentMethod, paymentProvider)
|
|
order.InProgressOrderID = inprogressOrderID
|
|
|
|
savedOrder, err := s.repo.Create(ctx, order)
|
|
if err != nil {
|
|
logger.ContextLogger(ctx).Error("failed to create order", zap.Error(err))
|
|
return nil, err
|
|
}
|
|
|
|
err = s.processPostOrderActions(ctx, savedOrder, inquiry.ID, paymentMethod)
|
|
if err != nil {
|
|
logger.ContextLogger(ctx).Warn("some post-order actions failed", zap.Error(err))
|
|
}
|
|
|
|
return &entity.OrderResponse{
|
|
Order: savedOrder,
|
|
}, nil
|
|
}
|
|
|
|
func (s *orderSvc) RefundRequest(ctx mycontext.Context, partnerID, orderID int64, reason string) error {
|
|
order, err := s.repo.FindByIDAndPartnerID(ctx, orderID, partnerID)
|
|
if err != nil {
|
|
logger.ContextLogger(ctx).Error("failed to find order for refund", zap.Error(err))
|
|
return err
|
|
}
|
|
|
|
if order.Status != "PAID" {
|
|
return errors.New("only paid order can be refund")
|
|
}
|
|
|
|
err = s.repo.UpdateOrder(ctx, order.ID, "REFUNDED", reason)
|
|
if err != nil {
|
|
logger.ContextLogger(ctx).Error("failed to update order status", zap.Error(err))
|
|
return err
|
|
}
|
|
|
|
refundTransaction, err := s.createRefundTransaction(ctx, order, reason)
|
|
if err != nil {
|
|
logger.ContextLogger(ctx).Error("failed to create refund transaction", zap.Error(err))
|
|
return err
|
|
}
|
|
|
|
if order.CustomerID != nil && *order.CustomerID > 0 {
|
|
err = s.reverseCustomerVouchers(ctx, *order.CustomerID, int64(order.Total), order.ID)
|
|
if err != nil {
|
|
logger.ContextLogger(ctx).Warn("failed to reverse customer vouchers", zap.Error(err))
|
|
}
|
|
}
|
|
|
|
logger.ContextLogger(ctx).Info("refund processed successfully",
|
|
zap.Int64("orderID", orderID),
|
|
zap.String("reason", reason),
|
|
zap.String("refundTransactionID", refundTransaction.ID))
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *orderSvc) processPostOrderActions(
|
|
ctx mycontext.Context,
|
|
order *entity.Order,
|
|
inquiryID string,
|
|
paymentMethod string,
|
|
) error {
|
|
err := s.repo.UpdateInquiryStatus(ctx, inquiryID, constants.StatusExecuted)
|
|
if err != nil {
|
|
logger.ContextLogger(ctx).Error("error when updating inquiry status", zap.Error(err))
|
|
}
|
|
|
|
trx, err := s.createTransaction(ctx, order, paymentMethod)
|
|
if err != nil {
|
|
logger.ContextLogger(ctx).Error("error when creating transaction", zap.Error(err))
|
|
}
|
|
|
|
if order.CustomerID != nil && *order.CustomerID > 0 {
|
|
err = s.addCustomerVouchers(ctx, *order.CustomerID, int64(order.Total), trx.OrderID)
|
|
if err != nil {
|
|
logger.ContextLogger(ctx).Error("error when adding points", zap.Error(err))
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *orderSvc) createTransaction(ctx mycontext.Context, order *entity.Order, paymentMethod string) (*entity.Transaction, error) {
|
|
transaction := &entity.Transaction{
|
|
ID: constants.GenerateUUID(),
|
|
OrderID: order.ID,
|
|
Amount: order.Total,
|
|
PaymentMethod: paymentMethod,
|
|
Status: "SUCCESS",
|
|
CreatedAt: constants.TimeNow(),
|
|
PartnerID: order.PartnerID,
|
|
TransactionType: "TRANSACTION",
|
|
}
|
|
|
|
_, err := s.transaction.Create(ctx, transaction)
|
|
|
|
return transaction, err
|
|
}
|
|
|
|
func (s *orderSvc) addCustomerVouchers(ctx mycontext.Context, customerID int64, total int64, reference int64) error {
|
|
undians, err := s.voucherUndianRepo.GetActiveUndianEvents(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
eligibleVoucher := []*entity.UndianVoucherDB{}
|
|
totalVouchersNeeded := 0
|
|
|
|
for _, v := range undians {
|
|
if total >= int64(v.MinimumPurchase) {
|
|
voucherCount := int(total / int64(v.MinimumPurchase))
|
|
totalVouchersNeeded += voucherCount
|
|
}
|
|
}
|
|
|
|
if totalVouchersNeeded == 0 {
|
|
return nil
|
|
}
|
|
|
|
startSequence, err := s.voucherUndianRepo.GetNextVoucherSequenceBatch(ctx, totalVouchersNeeded)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
currentSequence := startSequence
|
|
|
|
for _, v := range undians {
|
|
if total >= int64(v.MinimumPurchase) {
|
|
voucherCount := int(total / int64(v.MinimumPurchase))
|
|
|
|
for i := 0; i < voucherCount; i++ {
|
|
voucherCode := s.generateVoucherCode(v.ID, reference, currentSequence)
|
|
|
|
voucher := &entity.UndianVoucherDB{
|
|
UndianEventID: v.ID,
|
|
CustomerID: customerID,
|
|
VoucherCode: voucherCode,
|
|
VoucherNumber: &i,
|
|
IsWinner: false,
|
|
CreatedAt: time.Now(),
|
|
}
|
|
|
|
eligibleVoucher = append(eligibleVoucher, voucher)
|
|
currentSequence++
|
|
}
|
|
}
|
|
}
|
|
|
|
return s.voucherUndianRepo.CreateUndianVouchers(ctx, eligibleVoucher)
|
|
}
|
|
|
|
func (s *orderSvc) generateVoucherCode(eventID int64, reference int64, sequence int64) string {
|
|
eventPart := eventID % 100 // Last 2 digits of event ID
|
|
sequencePart := sequence % 100000 // Last 5 digits of sequence
|
|
orderPart := reference % 1000 // Last 3 digits of order ID
|
|
|
|
return fmt.Sprintf("%02d%05d%03d", eventPart, sequencePart, orderPart)
|
|
}
|
|
|
|
func (s *orderSvc) sendTransactionReceipt(ctx mycontext.Context, order *entity.Order, transaction *entity.Transaction, paymentMethod string) error {
|
|
newPoint := int(order.Total / 50000)
|
|
|
|
if newPoint <= 0 {
|
|
return nil
|
|
}
|
|
|
|
if order.CustomerID == nil || *order.CustomerID == 0 {
|
|
return nil
|
|
}
|
|
|
|
customerPoint, err := s.customer.GetCustomerPoints(ctx, *order.CustomerID)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
customer, err := s.customer.GetCustomer(ctx, *order.CustomerID)
|
|
if err != nil {
|
|
logger.ContextLogger(ctx).Error("error getting customer details", zap.Error(err))
|
|
return err
|
|
}
|
|
|
|
branchName := "Bakso 343 Rawamangun"
|
|
|
|
var productIDs []int64
|
|
productIDMap := make(map[int64]bool)
|
|
for _, item := range order.OrderItems {
|
|
if item.ItemID > 0 && !productIDMap[item.ItemID] {
|
|
productIDs = append(productIDs, item.ItemID)
|
|
productIDMap[item.ItemID] = true
|
|
}
|
|
}
|
|
|
|
productMap := make(map[int64]*entity.Product)
|
|
if len(productIDs) > 0 {
|
|
products, err := s.product.GetProductsByIDs(ctx, productIDs, order.PartnerID)
|
|
if err != nil {
|
|
logger.ContextLogger(ctx).Error("error fetching products", zap.Error(err))
|
|
} else {
|
|
for _, product := range products {
|
|
productMap[product.ID] = product
|
|
}
|
|
}
|
|
}
|
|
|
|
var itemsData []map[string]string
|
|
for _, item := range order.OrderItems {
|
|
itemName := "Item"
|
|
|
|
if product, exists := productMap[item.ItemID]; exists {
|
|
itemName = product.Name
|
|
}
|
|
|
|
itemsData = append(itemsData, map[string]string{
|
|
"ItemName": itemName,
|
|
"Quantity": fmt.Sprintf("%d", item.Quantity),
|
|
"Price": fmt.Sprintf("Rp %s", formatCurrency(item.Price)),
|
|
})
|
|
}
|
|
|
|
transactionDate := transaction.CreatedAt.Format("02 January 2006 15:04")
|
|
viewTransactionLink := "https://web.enaklo.co.id"
|
|
|
|
emailData := map[string]interface{}{
|
|
"UserName": customer.Name,
|
|
"PointsName": "Point",
|
|
"PointsBalance": newPoint,
|
|
"NewPoints": newPoint,
|
|
"TotalPoints": customerPoint.TotalPoints,
|
|
"RedeemLink": "web.enaklo.co.id",
|
|
"BranchName": branchName,
|
|
"TransactionNumber": order.ID,
|
|
"TransactionDate": transactionDate,
|
|
"PaymentMethod": formatPaymentMethod(paymentMethod),
|
|
"Items": itemsData,
|
|
"TotalPayment": fmt.Sprintf("Rp %s", formatCurrency(order.Total)),
|
|
"ViewTransactionLink": viewTransactionLink,
|
|
"ExpiryDate": order.CreatedAt.Format("02 January 2006"),
|
|
"UndianDate": "17 Mei 2025",
|
|
"WebURL": "https://web.enaklo.co.id/undian",
|
|
}
|
|
|
|
return s.notification.SendEmailTransactional(ctx, entity.SendEmailNotificationParam{
|
|
Sender: "noreply@enaklo.co.id",
|
|
Recipient: customer.Email,
|
|
Subject: "Hore, kamu dapat poin!",
|
|
TemplateName: "monthly_points",
|
|
TemplatePath: "/templates/monthly_points.html",
|
|
Data: emailData,
|
|
})
|
|
}
|
|
|
|
func formatCurrency(amount float64) string {
|
|
return fmt.Sprintf("%.2f", amount)
|
|
}
|
|
|
|
func formatPaymentMethod(method string) string {
|
|
methodMap := map[string]string{
|
|
"CASH": "Tunai",
|
|
"QRIS": "QRIS",
|
|
"CARD": "Kartu Kredit/Debit",
|
|
}
|
|
|
|
if displayName, exists := methodMap[method]; exists {
|
|
return displayName
|
|
}
|
|
return method
|
|
}
|
|
|
|
func (s *orderSvc) createRefundTransaction(ctx mycontext.Context, order *entity.Order, reason string) (*entity.Transaction, error) {
|
|
transaction := &entity.Transaction{
|
|
OrderID: order.ID,
|
|
Amount: -order.Total,
|
|
PaymentMethod: order.PaymentType,
|
|
Status: "REFUND",
|
|
CreatedAt: constants.TimeNow(),
|
|
PartnerID: order.PartnerID,
|
|
TransactionType: "REFUND",
|
|
CreatedBy: ctx.RequestedBy(),
|
|
UpdatedBy: ctx.RequestedBy(),
|
|
}
|
|
|
|
_, err := s.transaction.Create(ctx, transaction)
|
|
return transaction, err
|
|
}
|
|
|
|
func (s *orderSvc) reverseCustomerVouchers(ctx mycontext.Context, customerID int64, total int64, orderID int64) error {
|
|
// Find vouchers associated with this order and reverse them
|
|
// This is a simplified implementation - in production you might want to track voucher-order relationships
|
|
logger.ContextLogger(ctx).Info("reversing customer vouchers",
|
|
zap.Int64("customerID", customerID),
|
|
zap.Int64("orderID", orderID))
|
|
|
|
// TODO: Implement voucher reversal logic
|
|
// This would involve:
|
|
// 1. Finding vouchers created for this order
|
|
// 2. Marking them as reversed/cancelled
|
|
// 3. Optionally adjusting customer points
|
|
|
|
return nil
|
|
}
|