Update template email

This commit is contained in:
aditya.siregar
2025-03-08 00:35:23 +07:00
parent 3c80b710af
commit 18003313dd
54 changed files with 2309 additions and 199 deletions
@@ -0,0 +1,144 @@
package order
import (
"enaklo-pos-be/internal/common/errors"
"enaklo-pos-be/internal/common/logger"
"enaklo-pos-be/internal/common/mycontext"
"enaklo-pos-be/internal/constants"
"enaklo-pos-be/internal/entity"
"go.uber.org/zap"
)
func (s *orderSvc) CreateOrderInquiry(ctx mycontext.Context,
req *entity.OrderRequest) (*entity.OrderInquiryResponse, error) {
productIDs, filteredItems, err := s.validateOrderItems(ctx, req.OrderItems)
if err != nil {
return nil, err
}
req.OrderItems = filteredItems
productDetails, err := s.product.GetProductDetails(ctx, productIDs, req.PartnerID)
if err != nil {
logger.ContextLogger(ctx).Error("failed to get product details", zap.Error(err))
return nil, err
}
orderCalculation, err := s.calculateOrderTotals(ctx, req.OrderItems, productDetails, req.Source)
if err != nil {
return nil, err
}
customerID, err := s.customer.ResolveCustomer(ctx, &entity.CustomerResolutionRequest{
ID: req.CustomerID,
Name: req.CustomerName,
Email: req.CustomerEmail,
PhoneNumber: req.CustomerPhoneNumber,
})
if err != nil {
logger.ContextLogger(ctx).Error("failed to resolve customer", zap.Error(err))
return nil, err
}
inquiry := entity.NewOrderInquiry(
req.PartnerID,
customerID,
orderCalculation.Subtotal,
orderCalculation.Fee,
orderCalculation.Total,
req.PaymentMethod,
req.Source,
req.CreatedBy,
req.CustomerName,
req.CustomerPhoneNumber,
req.CustomerEmail,
)
for _, item := range req.OrderItems {
product := productDetails.Products[item.ProductID]
inquiry.AddOrderItem(item, product)
}
savedInquiry, err := s.repo.CreateInquiry(ctx, inquiry)
if err != nil {
logger.ContextLogger(ctx).Error("failed to create order inquiry", zap.Error(err))
return nil, err
}
token, err := s.crypt.GenerateJWTOrderInquiry(savedInquiry)
if err != nil {
logger.ContextLogger(ctx).Error("failed to generate token", zap.Error(err))
return nil, err
}
return &entity.OrderInquiryResponse{
OrderInquiry: savedInquiry,
Token: token,
}, nil
}
func (s *orderSvc) validateOrderItems(ctx mycontext.Context, items []entity.OrderItemRequest) ([]int64, []entity.OrderItemRequest, error) {
var productIDs []int64
var filteredItems []entity.OrderItemRequest
for _, item := range items {
if item.Quantity <= 0 {
continue
}
productIDs = append(productIDs, item.ProductID)
filteredItems = append(filteredItems, item)
}
if len(productIDs) == 0 {
return nil, nil, errors.ErrorBadRequest
}
return productIDs, filteredItems, nil
}
func (s *orderSvc) calculateOrderTotals(
ctx mycontext.Context,
items []entity.OrderItemRequest,
productDetails *entity.ProductDetails,
source string,
) (*entity.OrderCalculation, error) {
subtotal := 0.0
for _, item := range items {
product, ok := productDetails.Products[item.ProductID]
if !ok {
return nil, errors.NewError(errors.ErrorInvalidRequest.ErrorType(), "product not found")
}
subtotal += product.Price * float64(item.Quantity)
}
fee := s.cfg.GetOrderFee(source)
return &entity.OrderCalculation{
Subtotal: subtotal,
Fee: fee,
Total: subtotal + fee,
}, nil
}
func (s *orderSvc) validateInquiry(ctx mycontext.Context, token string) (*entity.OrderInquiry, error) {
partnerID, inquiryID, err := s.crypt.ValidateJWTOrderInquiry(token)
if err != nil {
return nil, errors.NewError(errors.ErrorInvalidRequest.ErrorType(), "inquiry is not valid or expired")
}
if partnerID != *ctx.GetPartnerID() {
return nil, errors.NewError(errors.ErrorInvalidRequest.ErrorType(), "invalid request")
}
inquiry, err := s.repo.FindInquiryByID(ctx, inquiryID)
if err != nil {
logger.ContextLogger(ctx).Error("error when finding inquiry", zap.Error(err))
return nil, err
}
if inquiry.Status != constants.StatusPending {
return nil, errors.NewError(errors.ErrorInvalidRequest.ErrorType(), "inquiry is no longer pending")
}
return inquiry, nil
}
+173
View File
@@ -0,0 +1,173 @@
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"
"go.uber.org/zap"
)
func (s *orderSvc) ExecuteOrderInquiry(ctx mycontext.Context,
token string, paymentMethod string) (*entity.OrderResponse, error) {
inquiry, err := s.validateInquiry(ctx, token)
if err != nil {
return nil, err
}
order := inquiry.ToOrder(paymentMethod)
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) 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.addCustomerPoints(ctx, *order.CustomerID, int(order.Total))
if err != nil {
logger.ContextLogger(ctx).Error("error when adding points", zap.Error(err))
}
}
s.sendTransactionReceipt(ctx, order, trx, "CASH")
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) addCustomerPoints(ctx mycontext.Context, customerID int64, points int) error {
return s.customer.AddPoints(ctx, customerID, points)
}
func (s *orderSvc) sendTransactionReceipt(ctx mycontext.Context, order *entity.Order, transaction *entity.Transaction, paymentMethod string) error {
if order.CustomerID == nil || *order.CustomerID == 0 {
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 := fmt.Sprintf("https://enaklo.co.id/transaction/%s", transaction.ID)
emailData := map[string]interface{}{
"UserName": customer.Name,
"BranchName": branchName,
"TransactionNumber": order.ID,
"TransactionDate": transactionDate,
"PaymentMethod": formatPaymentMethod(paymentMethod),
"Items": itemsData,
"TotalPayment": fmt.Sprintf("Rp %s", formatCurrency(order.Total)),
"ViewTransactionLink": viewTransactionLink,
}
return s.notification.SendEmailTransactional(ctx, entity.SendEmailNotificationParam{
Sender: "noreply@enaklo.co.id",
Recipient: customer.Email,
Subject: "Enaklo - Resi Pembelian",
TemplateName: "transaction_receipt",
TemplatePath: "templates/transaction_receipt.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
}
+80
View File
@@ -0,0 +1,80 @@
package order
import (
"context"
"enaklo-pos-be/internal/common/mycontext"
"enaklo-pos-be/internal/entity"
)
type Repository interface {
Create(ctx mycontext.Context, order *entity.Order) (*entity.Order, error)
FindByID(ctx mycontext.Context, id int64) (*entity.Order, error)
CreateInquiry(ctx mycontext.Context, inquiry *entity.OrderInquiry) (*entity.OrderInquiry, error)
FindInquiryByID(ctx mycontext.Context, id string) (*entity.OrderInquiry, error)
UpdateInquiryStatus(ctx mycontext.Context, id string, status string) error
}
type ProductService interface {
GetProductDetails(ctx mycontext.Context, productIDs []int64, partnerID int64) (*entity.ProductDetails, error)
GetProductsByIDs(ctx mycontext.Context, ids []int64, partnerID int64) ([]*entity.Product, error)
}
type CustomerService interface {
ResolveCustomer(ctx mycontext.Context, req *entity.CustomerResolutionRequest) (int64, error)
AddPoints(ctx mycontext.Context, customerID int64, points int) error
GetCustomer(ctx mycontext.Context, id int64) (*entity.Customer, error)
}
type TransactionService interface {
Create(ctx mycontext.Context, transaction *entity.Transaction) (*entity.Transaction, error)
}
type CryptService interface {
GenerateJWTOrderInquiry(inquiry *entity.OrderInquiry) (string, error)
ValidateJWTOrderInquiry(tokenString string) (int64, string, error)
}
type NotificationService interface {
SendEmailTransactional(ctx context.Context, param entity.SendEmailNotificationParam) error
}
type Service interface {
CreateOrderInquiry(ctx mycontext.Context,
req *entity.OrderRequest) (*entity.OrderInquiryResponse, error)
ExecuteOrderInquiry(ctx mycontext.Context,
token string, paymentMethod string) (*entity.OrderResponse, error)
}
type Config interface {
GetOrderFee(source string) float64
}
type orderSvc struct {
repo Repository
product ProductService
customer CustomerService
transaction TransactionService
crypt CryptService
cfg Config
notification NotificationService
}
func New(
repo Repository,
product ProductService,
customer CustomerService,
transaction TransactionService,
crypt CryptService,
cfg Config,
notification NotificationService,
) Service {
return &orderSvc{
repo: repo,
product: product,
customer: customer,
transaction: transaction,
crypt: crypt,
cfg: cfg,
notification: notification,
}
}